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 alterações em um banco de dados devem ser feitas com cuidado e de acordo com um processo específico. Idealmente, esses passos estariam integrados ao 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 do motivo pelo qual elas são necessárias, vamos esclarecer isso. Se você já conhece o básico, sinta-se à vontade para pular.
Principal Desafio
Quando fazemos alterações “planejadas” e “suaves” no banco de dados, precisamos manter a disponibilidade do serviço e atender aos 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” (por exemplo, simplesmente executar ALTER TABLE
sem preparação), a tabela pode ser bloqueada por um período significativo — e seus usuários ficariam sem serviço.
Para evitar dores de cabeça, siga duas regras:
- Aplicar migrações de uma forma que não bloqueie a tabela (ou pelo menos minimize os bloqueios).
- Se você precisa alterar o tipo de uma coluna, muitas vezes é mais fácil criar uma nova coluna com o tipo correto primeiro e depois excluir a antiga posteriormente.
Outro Problema: Controle de Versão e Rollbacks
Às vezes é necessário reverter uma migração.
Fazer isso manualmente — acessar o banco de dados de produção e mexer com os 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 reverta-as, 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 iremos analisar aqui).
- E assim por diante.
Goose: O Que É e Por Que É Útil
Goose é uma ferramenta Go leve que ajuda a gerenciar migrações automaticamente. Ela oferece:
- Simplicidade. Mínimas dependências e uma estrutura de arquivos transparente para as migrações.
- Versatilidade. Suporta vários 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 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;
Nossa Primeira 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 mudá-la para INTEGER
. Aqui está como a migração pode parecer (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 up
- Alteramos a coluna
age
paraINTEGER
. A cláusulaUSING (age::INTEGER)
informa ao PostgreSQL como converter os dados existentes para o novo tipo. - Note que esta migração falhará se houver qualquer dado em
age
que não seja numérico. Nesse caso, você precisará de uma estratégia mais complexa (veja abaixo).
- Alteramos a coluna
-
Migração reversa
- Se fizermos rollback, retornamos
age
paraVARCHAR(255)
. - Novamente utilizamos
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:
- Adicionar uma nova coluna (
age_int
) do tipoINTEGER
. - Copiar os dados válidos para a nova coluna, lidando com ou removendo entradas inválidas.
- Eliminar 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 espelha as ações de forma reversa.
A Automação é Fundamental
Para economizar tempo, é muito conveniente adicionar comandos de migração a um Makefile (ou qualquer outro sistema de compilação). Abaixo está um exemplo de Makefile com os principais comandos do Goose para o PostgreSQL.
Vamos supor:
- 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. Crie 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. Aplique todas as migrações. Goose irá criar uma tabela schema_migrations
no seu banco de dados (se ainda não existir) e aplicar quaisquer novas migrações em ordem crescente.
make migrate-up
3. Reverta a última migração. Apenas desfaça a última.
make migrate-down
4. Reverta todas as migrações (use com cautela em produção). Reset completo.
make migrate-reset
5. Verifique 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 por meio de migrações.
- Rastrear 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 ideias principais 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 sincronizados.
- Integre as migrações no CI/CD para que você não dependa de uma única pessoa executando manualmente.
Dessa forma, você terá um processo confiável e controlado para alterar 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 por ler!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service