Mudanças em banco de dados
LidoEsta aula aborda como gerenciar mudanças em bancos de dados em um contexto DevOps, cobrindo migrations, versionamento de schema, backward compatibility e estratégias de deploy. O foco é garantir que alterações no banco sejam seguras, reversíveis e alinhadas com práticas de integração e entrega contínuas.
Em um ambiente DevOps, mudanças no banco de dados são tão críticas quanto mudanças no código da aplicação. Elas precisam ser versionadas, testadas e aplicadas de forma consistente entre ambientes. Nesta aula, vamos explorar os conceitos fundamentais de gerenciamento de mudanças em bancos de dados, desde migrations até estratégias avançadas, com exemplos práticos e boas práticas.
O objetivo é que você entenda como tratar o schema do banco como código, garantindo que todas as alterações sejam rastreáveis, automatizadas e seguras, minimizando riscos de indisponibilidade e inconsistência.
Migrations
Migrations são scripts que descrevem alterações estruturais no banco de dados, como criação de tabelas, adição de colunas, índices ou alterações em constraints. Elas são versionadas junto com o código da aplicação e aplicadas em ordem, geralmente por uma ferramenta como Flyway, Liquibase ou Alembic (para Python).
O princípio é simples: cada migration representa um passo incremental e reversível (ou pelo menos conhecido) na evolução do schema. Isso permite que qualquer ambiente seja atualizado de forma determinística, bastando executar as migrations pendentes.
Um exemplo de migration em SQL para criar uma tabela users seria:
-- migration V1__create_users.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);Em projetos reais, as migrations são executadas automaticamente durante o processo de deploy, garantindo que o schema esteja sempre em sincronia com a versão da aplicação.
Versionamento de schema
Versionar o schema significa manter um histórico de todas as mudanças estruturais, permitindo que você saiba exatamente em que estado o banco se encontra e possa reconstruí-lo do zero se necessário. Ferramentas de migration geralmente mantêm uma tabela de controle (ex.: flyway_schema_history) que registra quais migrations foram aplicadas e quando.
Além disso, é importante versionar o próprio arquivo de definição do schema (como um schema.sql ou arquivos de migration) em um repositório de controle de versão (Git). Isso garante que qualquer alteração seja revisada, testada e auditada.
Por exemplo, com Flyway, você pode gerar um diff entre o schema atual e o desejado, mas o mais comum é escrever migrations incrementais. Uma boa prática é usar nomes sequenciais ou baseados em data (ex.: V1__init.sql, V2__add_column_to_users.sql).
O versionamento também ajuda na colaboração entre times: diferentes desenvolvedores podem trabalhar em migrations diferentes, e o sistema de controle de versão resolve conflitos.
Backward compatibility
Backward compatibility refere-se à capacidade de uma mudança no banco de dados ser compatível com versões anteriores da aplicação. Isso é crucial em deployments contínuos, onde a nova versão da aplicação pode ser executada ao lado da antiga por um período de transição (blue-green, canário, etc.).
Para garantir backward compatibility, evite mudanças que quebrem a aplicação antiga, como remover colunas que ainda são usadas, alterar tipos de dados de forma incompatível ou adicionar constraints que possam falhar com dados existentes.
Estratégias para manter compatibilidade:
- Adicionar novas colunas sem torná-las obrigatórias (ou com valores padrão).
- Não remover colunas ou tabelas imediatamente; em vez disso, marque-as como obsoletas e remova em uma migration futura.
- Usar expansão-contração (expand and contract) para alterar colunas: primeiro adicione a nova estrutura, depois migre os dados, e só então remova a antiga.
Exemplo: em vez de alterar o tipo de uma coluna de INT para BIGINT diretamente, adicione uma nova coluna, copie os dados, ajuste a aplicação e depois remova a coluna original.
Estratégias
Existem várias estratégias para aplicar mudanças em bancos de dados em produção, cada uma com trade-offs entre risco, tempo de inatividade e complexidade.
1. Deploy direto (expand/contract)
É a estratégia mais simples: aplicar a migration diretamente no banco de produção antes ou depois do deploy da aplicação. Isso pode causar tempo de inatividade se a migration for demorada ou bloquear tabelas. Para minimizar riscos, execute migrations fora do horário de pico e monitore o desempenho.
2. Blue-Green com banco compartilhado
Nessa abordagem, você mantém dois ambientes (blue e green) com a mesma aplicação, mas compartilhando o mesmo banco. A migration é aplicada antes de alternar o tráfego, e a compatibilidade é garantida porque a aplicação antiga ainda funciona com o novo schema (se a migration for backward compatible). Isso requer que as migrations sejam não destrutivas.
3. Canary releases
Com canary, você libera a nova versão para uma pequena porcentagem de usuários, enquanto o restante usa a versão antiga. Isso exige que o banco suporte ambas as versões simultaneamente, o que reforça a necessidade de backward compatibility. Migrations devem ser aditivas e não quebradoras.
4. Feature flags
Em vez de aplicar a mudança diretamente, você pode usar feature flags para controlar a ativação de novas funcionalidades que dependem de mudanças no banco. A migration é aplicada, mas a aplicação só usa a nova estrutura quando o flag é ativado. Isso permite reverter rapidamente sem precisar reverter a migration.
Um exemplo de estratégia combinada: primeiro aplique uma migration aditiva (adicionar coluna), depois faça o deploy da aplicação com o novo código, e finalmente, em uma release futura, remova a coluna antiga.
Boas práticas e observações finais
Além das estratégias, aqui vão algumas boas práticas:
- Teste migrations em um ambiente de staging que replique produção.
- Automatize a execução de migrations no pipeline de CI/CD.
- Mantenha as migrations pequenas e frequentes para reduzir riscos.
- Sempre faça backup antes de aplicar migrations em produção.
- Documente cada migration e o motivo da mudança.
Mudanças em banco de dados são inevitáveis, mas com versionamento, testes e estratégias adequadas, você pode minimizar impactos e manter a confiabilidade do sistema.
Exercícios
- Explique o que são migrations e por que são importantes no contexto DevOps. Dê um exemplo de uma migration simples em SQL.
- Qual a diferença entre backward compatibility e forward compatibility? Por que a backward compatibility é crucial em deployments contínuos?
- Descreva a estratégia expand and contract (expandir e contrair) e dê um exemplo prático de como aplicá-la.
- Cite pelo menos três ferramentas de migration e explique como elas ajudam no versionamento de schema.
- Em um cenário de deploy canário, por que é importante que as migrations sejam backward compatible? Dê um exemplo de uma migration que quebraria a compatibilidade.
CREATE TABLE produtos (id SERIAL PRIMARY KEY, nome VARCHAR(100) NOT NULL);ALTER TABLE users DROP COLUMN age; se a versão antiga ainda usa age.