Construindo Banco de Dados para Testes de Performance
Quando trabalhamos com aplicações em produção, um dos maiores desafios é realizar testes de performance sem comprometer dados sensíveis dos usuários. Neste tutorial prático, você aprenderá a construir um banco de dados dedicado para testes de performance, aplicando técnicas de scrubbing (limpeza) de dados usando PostgreSQL e funções personalizadas.
Por Que Criar um Banco de Dados de Performance Separado?
A principal vantagem é poder testar mudanças em larga escala - queries, índices, otimizações - com dados realistas, mas sem expor informações confidenciais. Isso permite que toda a equipe tenha acesso ao banco para testes, algo impossível quando trabalhamos diretamente com dados de produção.
Uma alternativa seria gerar dados completamente falsos, mas isso apresenta problemas: as propriedades estatísticas e a distribuição dos dados afetam diretamente a performance das queries. O PostgreSQL usa essas estatísticas para escolher planos de execução, então queremos manter essas características intactas.
Gerando Volume de Dados Realista
Para simular um ambiente de produção, precisamos de volume. Vamos começar populando milhões de registros na tabela de usuários usando um script de bulk load:
# Execute o script de carga em massa
cd db
time sh scripts/bulk_load.sh
# Verifique o tamanho da tabela
psql $DATABASE_URL
SELECT PG_SIZE_PRETTY(
PG_TOTAL_RELATION_SIZE('rideshare.users')
);
Este script criará 10 milhões de registros, consumindo pelo menos 4 GB de espaço. Certifique-se de ter espaço disponível antes de executar!
Identificando Dados Sensíveis
Dados sensíveis são aqueles que identificam indivíduos únicos: nomes, emails, telefones, CPFs, etc. Na tabela users, os campos first_name e email são claramente sensíveis.
Para rastrear essas colunas, utilizamos comentários no PostgreSQL:
COMMENT ON COLUMN users.email
IS 'sensitive_data=true';
-- Verifique com describe expandido
\d+ users
Criando uma Função de Scrubbing para Emails
Vamos criar uma função que substitui a parte única do email (antes do @) mantendo o domínio intacto. Isso preserva características importantes dos dados enquanto remove informações identificáveis.
Função Básica
Começamos com uma estrutura simples:
CREATE OR REPLACE FUNCTION SCRUB_EMAIL(email_address varchar(255))
RETURNS VARCHAR(255) AS $$
SELECT email_address;
$$ LANGUAGE SQL;
-- Teste a função
SELECT SCRUB_EMAIL(email)
FROM users
LIMIT 5;
Implementação Completa
Agora vamos implementar a lógica completa. Usaremos funções built-in do PostgreSQL como SPLIT_PART() para separar o email, MD5() para gerar texto aleatório, e CONCAT() para montar o resultado:
-- Substitui email_address com texto aleatório do mesmo tamanho
-- da porção única do email (antes do @)
-- Tamanho mínimo de 5 caracteres para evitar colisões MD5
CREATE OR REPLACE FUNCTION SCRUB_EMAIL(
email_address VARCHAR(255)
) RETURNS VARCHAR(255) AS $$
SELECT
CONCAT(
SUBSTR(
MD5(RANDOM()::TEXT),
1,
GREATEST(
LENGTH(
SPLIT_PART(email_address, '@', 1)
) + 1, 5
)
),
'@',
SPLIT_PART(email_address, '@', 2)
);
$$ LANGUAGE SQL;
Vamos testar com diferentes tamanhos de email:
-- Use SETSEED para resultados determinísticos
SELECT SETSEED(0.5);
SELECT SCRUB_EMAIL('bob@gmail.com');
-- Resultado: 2bffb@gmail.com
SELECT SETSEED(0.5);
SELECT SCRUB_EMAIL('bob-and-jane@gmail.com');
-- Resultado: 2bffb502c463@gmail.com
Estratégias de Scrubbing: Direct Updates vs Clone and Replace
Existem duas abordagens principais para aplicar o scrubbing em larga escala:
1. Direct Updates (Atualizações Diretas)
Adequada para tabelas com menos de 1 milhão de registros. Simples de implementar, mas requer manutenção pós-atualização:
-- Atualização direta em todos os registros
UPDATE users
SET email = SCRUB_EMAIL(email);
-- Manutenção necessária após updates
VACUUM (ANALYZE, VERBOSE) users;
REINDEX INDEX index_users_on_email;
2. Clone and Replace (Clonar e Substituir)
Para tabelas grandes (mais de 1 milhão de registros), esta estratégia é muito mais rápida. Criamos uma cópia da tabela, populamos com dados limpos, e então fazemos a troca:
-- Passo 1: Criar tabela clone (sem índices inicialmente)
CREATE TABLE users_copy (
LIKE users INCLUDING ALL EXCLUDING INDEXES
);
-- Passo 2: Inserir dados com scrubbing on-the-fly
INSERT INTO users_copy(
id, first_name, last_name,
email, type, created_at, updated_at
)
(
SELECT
id, first_name, last_name,
SCRUB_EMAIL(email), -- função de scrubbing
type, created_at, updated_at
FROM users
);
-- Passo 3: Criar índices na tabela clone
CREATE UNIQUE INDEX index_users_on_email
ON users_copy USING btree (email);
-- Passo 4: Trocar as tabelas (em transação)
BEGIN;
DROP TABLE users CASCADE;
ALTER TABLE users_copy RENAME TO users;
COMMIT;
Processamento em Lote com Procedures
Para tabelas muito grandes, processar tudo de uma vez pode ser problemático. A solução é usar procedures PL/pgSQL que processam os dados em lotes:
CREATE OR REPLACE PROCEDURE SCRUB_BATCHES()
LANGUAGE PLPGSQL
AS $$
DECLARE
id_atual INT := (SELECT MIN(id) FROM users);
id_maximo INT := (SELECT MAX(id) FROM users);
tamanho_lote INT := 1000;
linhas_atualizadas INT;
BEGIN
WHILE id_atual <= id_maximo LOOP
-- UPDATE por intervalo de id
UPDATE users
SET email = SCRUB_EMAIL(email)
WHERE id >= id_atual
AND id < id_atual + tamanho_lote;
GET DIAGNOSTICS linhas_atualizadas = ROW_COUNT;
COMMIT;
RAISE NOTICE 'id_atual: % - Linhas atualizadas: %',
id_atual, linhas_atualizadas;
id_atual := id_atual + tamanho_lote;
END LOOP;
END;
$$;
-- Executar a procedure
CALL SCRUB_BATCHES();
Esta procedure processa 1000 registros por vez, fazendo commit após cada lote e imprimindo o progresso.
Gerenciando Constraints e Sequences
Ao usar a estratégia Clone and Replace, você precisará lidar com constraints de chave estrangeira e sequences. Aqui está como listar e recriar esses objetos:
-- Listar todas as constraints de chave estrangeira
SELECT
conrelid::regclass AS nome_tabela,
conname AS chave_estrangeira,
PG_GET_CONSTRAINTDEF(oid)
FROM pg_constraint
WHERE contype = 'f'
AND connamespace = 'rideshare'::regnamespace;
-- Transferir ownership da sequence para tabela clone
ALTER SEQUENCE users_id_seq
OWNED BY users_copy.id;
-- Listar definições de índices como DDL
SELECT PG_GET_INDEXDEF(indexrelid) || ';' AS indice
FROM pg_index
WHERE indrelid = 'users'::regclass;
Considerações Finais
Construir um banco de dados de performance com scrubbing automatizado é um investimento que compensa ao longo do tempo. Os principais benefícios incluem:
Segurança: Dados sensíveis são removidos, permitindo acesso mais amplo à equipe.
Realismo: As propriedades estatísticas dos dados são preservadas, tornando os testes de performance mais confiáveis.
Escalabilidade: Com as estratégias corretas (processamento em lote, clone and replace), o processo funciona mesmo para bancos com centenas de milhões de registros.
Automação: Todo o processo pode ser automatizado e executado regularmente para manter o banco de performance sincronizado com produção.
Lembre-se de sempre testar seu processo de scrubbing em um ambiente separado primeiro, e documente bem quais colunas contêm dados sensíveis em seu schema. Use as ferramentas de versionamento do PostgreSQL (como a gem fx para funções) para manter tudo rastreável e auditável.
Com essas técnicas, você terá um ambiente de testes robusto que equilibra segurança, realismo e performance!
Nenhum comentário:
Postar um comentário