Olá, amigo!
Hoje, vamos falar sobre o que são migrações de banco de dados e por que elas são tão importantes. No mundo de hoje, não é surpresa que quaisquer mudanças em um banco de dados devem ser feitas com cuidado e de acordo com um processo específico. Idealmente, esses passos estariam integrados em nosso pipeline de CI/CD para que tudo funcione automaticamente.
Aqui está nossa agenda:
- Qual é o problema?
- Como resolvemos isso?
- Um exemplo simples
- Um exemplo mais complexo
- Recomendações
- Resultados
- Conclusão
Qual é o Problema?
Se sua equipe nunca lidou com migrações de banco de dados e você não tem certeza de por que elas são necessárias, vamos esclarecer isso. Se você já conhece o básico, sinta-se à vontade para pular.
Desafio Principal
Quando fazemos mudanças “planejadas” e “suaves” no banco de dados, precisamos manter a disponibilidade do serviço e cumprir os requisitos de SLA (para que os usuários não sofram com inatividade ou lentidão). Imagine que você quer mudar o tipo de uma coluna em uma tabela com 5 milhões de usuários. Se você fizer isso “de frente” (ou seja, simplesmente executar ALTER TABLE
sem preparação), a tabela pode ficar bloqueada por um tempo significativo — e seus usuários ficariam sem serviço.
Para evitar tais dores de cabeça, siga duas regras:
- Aplicar migrações de uma maneira que não bloqueie a tabela (ou pelo menos minimize os bloqueios).
- Se precisar alterar o tipo de uma coluna, muitas vezes é mais fácil criar primeiro uma nova coluna com o tipo correto e depois excluir a antiga.
Outro Problema: Controle de Versão e Rollbacks
Às vezes é necessário reverter uma migração.
Fazer isso manualmente — acessando o banco de dados de produção e mexendo nos dados — não é apenas arriscado, mas também provavelmente impossível se você não tiver acesso direto. É aí que as ferramentas de migração dedicadas são úteis. Elas permitem que você aplique alterações de forma limpa e as reverta, se necessário.
Como Resolver? Use as Ferramentas Certas
Cada linguagem e ecossistema tem suas próprias ferramentas de migração:
- Para Java, Liquibase ou Flyway são comuns.
- Para Go, uma escolha popular é goose (a que vamos ver aqui).
- E assim por diante.
Goose: O Que É e Por Que É Útil
Goose é uma utilitário Go leve que ajuda a gerenciar migrações automaticamente. Ele oferece:
- Simplicidade. Mínimas dependências e uma estrutura de arquivos transparente para migrações.
- Versatilidade. Suporta diversos drivers de BD (PostgreSQL, MySQL, SQLite, etc.).
- Flexibilidade. Escreva migrações em SQL ou código Go.
Instalando o Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
Como Funciona: Estrutura de Migração
Por padrão, o Goose procura por arquivos de migração em db/migrations
. Cada migração segue este formato:
NNN_migration_name.(sql|go)
NNN
é o número da migração (por exemplo,001
,002
, etc.).- Depois disso, você pode ter qualquer nome descritivo, por exemplo
init_schema
. - A extensão pode ser
.sql
ou.go
.
Exemplo de uma Migração SQL
Arquivo: 001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
Nosso Primeiro Exemplo
Alterando o Tipo de uma Coluna (String → Int)
Suponha que temos uma tabela users
com uma coluna age
do tipo VARCHAR(255)
. Agora queremos alterá-la para INTEGER
. Veja como a migração poderia ser (arquivo 005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
O que está acontecendo aqui:
-
Migração de Subida
- Nós alteramos a coluna
age
paraINTEGER
. A cláusulaUSING (age::INTEGER)
diz ao PostgreSQL como converter os dados existentes para o novo tipo. - Observe que essa migração falhará se houver algum dado em
age
que não seja numérico. Nesse caso, você precisará de uma estratégia mais complexa (veja abaixo).
- Nós alteramos a coluna
-
Down migration
- Se fizermos rollback, retornamos
age
paraVARCHAR(255)
. - Novamente usamos
USING (age::TEXT)
para converter deINTEGER
de volta para texto.
- Se fizermos rollback, retornamos
Os Segundos e Complexos Casos: Migrações em Múltiplas Etapas
Se a coluna age
puder conter dados bagunçados (não apenas números), é mais seguro fazer isso em várias etapas:
-
Adicione uma nova coluna (
age_int
) do tipoINTEGER
. - Copie os dados válidos para a nova coluna, lidando com ou removendo entradas inválidas.
- Exclua a coluna antiga.
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
Para permitir um rollback adequado, a seção Down
apenas reflete as ações em reverso.
Automatização é a Chave
Para economizar tempo, é realmente conveniente adicionar comandos de migração a um Makefile (ou qualquer outro sistema de construção). Abaixo está um exemplo de Makefile com os principais comandos do Goose para o PostgreSQL.
Vamos assumir:
-
O DSN para o banco de dados é
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - Os arquivos de migração estão em
db/migrations
.
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
Como usar?
1. Criar uma nova migração (arquivo SQL). Isso gera um arquivo db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Aplicar todas as migrações. O Goose criará uma tabela schema_migrations
no seu banco de dados (se ainda não existir) e aplicará quaisquer novas migrações em ordem ascendente.
make migrate-up
3. Reverter a última migração. Apenas desfaça a última.
make migrate-down
4. Reverter todas as migrações (use com cautela em produção). Reset completo.
make migrate-reset
5. Verificar o status da migração.
make migrate-status
Exemplo de saída:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
Resumo
Ao usar ferramentas de migração e um Makefile, podemos:
- Restringir o acesso direto ao banco de dados de produção, fazendo alterações apenas através de migrações.
- Acompanhar facilmente as versões do banco de dados e revertê-las se algo der errado.
- Manter um histórico único e consistente de alterações no banco de dados.
- Realizar migrações “suaves” que não quebrarão um ambiente de produção em execução em um mundo de microsserviços.
- Obter validação extra — cada alteração passará por um processo de PR e revisão de código (supondo que você tenha essas configurações em vigor).
Outra vantagem é que é fácil integrar todos esses comandos em seu pipeline de CI/CD. E lembre-se — segurança acima de tudo.
Por exemplo:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
Conclusão e Dicas
As principais ideias são tão simples:
- Mantenha suas migrações pequenas e frequentes. Elas são mais fáceis de revisar, testar e reverter se necessário.
- Use a mesma ferramenta em todos os ambientes para que dev, stage e prod estejam em sincronia.
- Integre migrações no CI/CD para que você não dependa de uma única pessoa executando-as manualmente.
Dessa forma, você terá um processo confiável e controlado para mudar a estrutura do seu banco de dados — um que não quebre a produção e permita que você responda rapidamente se algo der errado.
Boa sorte com suas migrações!
Obrigado pela leitura!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service