Database refactoring: renomear, dividir e reorganizar com segurança
1. Fundamentos do Database Refactoring
Database refactoring é o processo de alterar incrementalmente o esquema de um banco de dados em produção, preservando seu comportamento funcional e sem causar interrupções. Diferente do refactoring de código, que permite múltiplas versões simultâneas via branches, o banco de dados é um recurso compartilhado e centralizado. Qualquer alteração mal planejada pode quebrar consultas, stored procedures, relatórios ou até mesmo causar perda de dados.
Os riscos mais comuns incluem:
- Quebra de queries: aplicações que esperam colunas ou nomes de tabelas antigos
- Perda de dados: migrações com DELETE ou DROP sem backup prévio
- Downtime não planejado: locks prolongados durante ALTER TABLE em tabelas grandes
A abordagem segura exige planejamento, scripts de reversão e validação de dependências.
2. Renomeação Segura de Objetos
Renomear tabelas ou colunas exige cautela. A estratégia recomendada é "renomear e manter sinônimos" usando views.
Exemplo: Renomear coluna name para full_name
Passo 1: Criar view de compatibilidade antes de renomear
CREATE VIEW users_v AS
SELECT id, name AS full_name, email, created_at
FROM users;
Passo 2: Renomear a coluna original
ALTER TABLE users RENAME COLUMN name TO full_name;
Passo 3: Atualizar a view para espelhar a nova estrutura
CREATE OR REPLACE VIEW users_v AS
SELECT id, full_name, email, created_at
FROM users;
Plano de rollback:
-- Reverter renomeação
ALTER TABLE users RENAME COLUMN full_name TO name;
DROP VIEW IF EXISTS users_v;
Validação de dependências: use consultas ao information_schema para localizar stored procedures, triggers ou relatórios que referenciam a coluna antiga:
SELECT routine_name, routine_definition
FROM information_schema.routines
WHERE routine_definition ILIKE '%name%';
3. Divisão de Tabelas
Divisão Vertical
Separa colunas grandes (BLOBs, textos longos) das colunas de acesso frequente.
Exemplo: Dividir tabela products em products_core e products_details
-- Criar tabela com colunas de uso frequente
CREATE TABLE products_core AS
SELECT id, name, price, category_id
FROM products;
-- Criar tabela com colunas grandes
CREATE TABLE products_details AS
SELECT id, description, image_data
FROM products;
-- View unificadora
CREATE VIEW products_v AS
SELECT c.id, c.name, c.price, c.category_id, d.description, d.image_data
FROM products_core c
JOIN products_details d ON c.id = d.id;
Divisão Horizontal (Particionamento)
Para tabelas grandes, use particionamento por data.
Exemplo: Particionar tabela orders por ano
-- Criar tabela particionada
CREATE TABLE orders (
id INT,
customer_id INT,
order_date DATE,
total DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(order_date));
-- Criar partições
CREATE TABLE orders_2023 PARTITION OF orders
FOR VALUES FROM (2023) TO (2024);
CREATE TABLE orders_2024 PARTITION OF orders
FOR VALUES FROM (2024) TO (2025);
-- Migrar dados
INSERT INTO orders SELECT * FROM legacy_orders;
View unificadora para migração gradual:
CREATE VIEW all_orders AS
SELECT * FROM orders_active
UNION ALL
SELECT * FROM orders_archive;
4. Reorganização de Esquema
Merge de Tabelas Redundantes
Consolidar duas tabelas que armazenam dados similares.
-- Validar integridade antes do merge
SELECT id FROM customers_premium
EXCEPT
SELECT id FROM customers_standard;
-- Merge com transação
BEGIN;
INSERT INTO customers (id, name, email, type)
SELECT id, name, email, 'premium' FROM customers_premium;
INSERT INTO customers (id, name, email, type)
SELECT id, name, email, 'standard' FROM customers_standard;
DROP TABLE customers_premium;
DROP TABLE customers_standard;
COMMIT;
Split de Tabelas Superlotadas
Separar dados históricos em tabela de arquivo.
BEGIN;
-- Criar tabela filha
CREATE TABLE orders_archive (LIKE orders);
-- Mover dados antigos
INSERT INTO orders_archive
SELECT * FROM orders WHERE order_date < '2023-01-01';
-- Remover da tabela principal
DELETE FROM orders WHERE order_date < '2023-01-01';
COMMIT;
Normalização Controlada
Separar endereços em tabela própria.
-- Criar tabela addresses
CREATE TABLE addresses (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
street VARCHAR(200),
city VARCHAR(100),
zip_code VARCHAR(20)
);
-- Migrar dados
INSERT INTO addresses (user_id, street, city, zip_code)
SELECT id, street, city, zip_code FROM users;
-- Remover colunas da tabela original
ALTER TABLE users DROP COLUMN street,
DROP COLUMN city,
DROP COLUMN zip_code;
5. Estratégias de Migração com Zero Downtime
O padrão "Expand-Migrate-Contract" (EMC) é a abordagem mais segura.
Fase 1: Expand
Adicione a nova estrutura sem remover a antiga.
-- Adicionar nova coluna
ALTER TABLE users ADD COLUMN full_name VARCHAR(200);
-- Manter coluna antiga
-- name continua existindo
Fase 2: Migrate
Sincronize dados usando triggers ou CDC.
-- Trigger para manter sincronia
CREATE OR REPLACE FUNCTION sync_full_name()
RETURNS TRIGGER AS $$
BEGIN
NEW.full_name := NEW.name;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_sync_full_name
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION sync_full_name();
Fase 3: Contract
Remova a estrutura antiga após validação.
-- Após confirmar que nenhuma aplicação usa a coluna antiga
ALTER TABLE users DROP COLUMN name;
DROP TRIGGER trg_sync_full_name;
6. Ferramentas e Automação
Ferramentas open-source para versionamento de esquemas:
- Sqitch: baseado em tags, permite rollback granular
- Flyway: migrations SQL versionadas com checksum
- Liquibase: suporte a XML, YAML e JSON para definição de mudanças
Exemplo de script de validação pós-refactoring:
-- Verificar consistência de dados
SELECT COUNT(*) FROM orders
WHERE NOT EXISTS (SELECT 1 FROM customers WHERE customers.id = orders.customer_id);
-- Verificar performance
EXPLAIN ANALYZE SELECT * FROM orders WHERE order_date > '2024-01-01';
Integração com CI/CD:
# .gitlab-ci.yml
stages:
- test-migration
- deploy
test-migration:
stage: test-migration
script:
- sqitch deploy db:pg://test-db
- pgbench -c 10 -t 1000 -f queries.sql
7. Casos Práticos e Exemplos de Código
Exemplo 1: Renomear coluna com view de compatibilidade
Já demonstrado na seção 2.
Exemplo 2: Dividir tabela orders com particionamento
-- Criar tabela particionada
CREATE TABLE orders (
id INT,
customer_id INT,
order_date DATE,
total DECIMAL(10,2)
) PARTITION BY RANGE (order_date);
-- Partições mensais
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Migrar dados
INSERT INTO orders SELECT * FROM legacy_orders;
Exemplo 3: Reorganizar tabela users separando endereço
Já demonstrado na seção 4 (Normalização Controlada).
8. Monitoramento e Rollback em Produção
Métricas de sucesso
- Tempo de query: comparar antes e depois do refactoring
- Taxa de erro: monitorar logs de aplicação
- Locks no banco: usar
pg_stat_activitypara detectar bloqueios
SELECT pid, state, wait_event, query
FROM pg_stat_activity
WHERE wait_event IS NOT NULL;
Plano de contingência
Script de rollback rápido:
-- rollback_rename_column.sql
BEGIN;
ALTER TABLE users RENAME COLUMN full_name TO name;
DROP VIEW IF EXISTS users_v;
COMMIT;
Lições aprendidas
Documente cada refactoring com:
- Data e hora da execução
- Scripts aplicados (forward e rollback)
- Resultados dos testes de performance
- Problemas encontrados e soluções
Referências
- Database Refactoring: Evolutionary Database Design — Artigo seminal de Martin Fowler sobre design evolutivo de bancos de dados e técnicas de refactoring
- PostgreSQL Documentation: ALTER TABLE — Documentação oficial sobre renomeação e alteração de tabelas no PostgreSQL
- Flyway: Version Control for Databases — Guia completo da ferramenta Flyway para versionamento de migrations SQL
- Sqitch: Sensible Database Change Management — Documentação oficial do Sqitch, ferramenta de gerenciamento de mudanças baseada em tags
- Liquibase: Database Refactoring Best Practices — Tutorial rápido da Liquibase com exemplos de refactoring de esquemas
- Zero-Downtime Database Migrations — Artigo técnico do Shopify sobre migrações com zero downtime usando o padrão Expand-Migrate-Contract