Como implementar políticas de retenção e purge automático de dados em produção
1. Fundamentos das Políticas de Retenção de Dados
1.1 Definição de políticas de retenção
Políticas de retenção de dados estabelecem regras claras sobre quanto tempo diferentes tipos de dados devem permanecer armazenados antes de serem removidos ou arquivados. Os principais objetivos incluem:
- Compliance regulatório: Atender exigências da LGPD (Brasil) e GDPR (Europa), que determinam prazos máximos para armazenamento de dados pessoais
- Redução de custos: Eliminar dados obsoletos reduz gastos com armazenamento em nuvem (S3, EBS) e bancos de dados
- Performance: Bancos com menos registros executam consultas mais rapidamente
1.2 Classificação de dados por criticidade
Para implementar políticas eficazes, classifique os dados em categorias:
text
Categoria | Exemplo | TTL sugerido
-------------------|----------------------------|---------------
Logs de debug | Aplicação, acesso | 7-30 dias
Métricas agregadas | CPU, memória, latência | 90 dias
Sessões de usuário | Tokens JWT, cookies | 24 horas
Dados transacionais| Pedidos, pagamentos | 5 anos (LGPD)
Dados sensíveis | CPF, dados bancários | Conforme lei
1.3 Retenção lógica vs física
- Soft delete: Marca o registro como excluído (ex:
deleted_at = NOW()) sem removê-lo fisicamente. Útil para recuperação rápida, mas não libera espaço. - Hard delete/purge: Remove fisicamente o registro do banco ou arquivo. Libera espaço, mas é irreversível.
Para produção, recomenda-se uma abordagem híbrida: soft delete inicial seguido de purge automático após período de quarentena.
2. Estratégias de Purge Automático em Banco de Dados
2.1 Implementação com pg_cron e partições no PostgreSQL
O PostgreSQL com extensão pg_cron permite agendar jobs SQL diretamente no banco:
-- Habilitar pg_cron (requer superusuário)
CREATE EXTENSION pg_cron;
-- Criar tabela particionada por data
CREATE TABLE logs_acesso (
id SERIAL,
usuario_id INT,
data_hora TIMESTAMPTZ NOT NULL,
acao TEXT,
PRIMARY KEY (id, data_hora)
) PARTITION BY RANGE (data_hora);
-- Criar partições mensais
CREATE TABLE logs_acesso_2025_01 PARTITION OF logs_acesso
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE logs_acesso_2025_02 PARTITION OF logs_acesso
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
-- Job semanal para remover partições com mais de 90 dias
SELECT cron.schedule(
'purge-logs-weekly',
'0 3 * * 0', -- Domingo às 3h
$$DROP TABLE IF EXISTS logs_acesso_2024_10$$
);
2.2 Queries batch eficientes
Para tabelas não particionadas, use DELETE com LIMIT para evitar locks prolongados:
-- Loop controlado em batches de 1000 registros
DO $$
DECLARE
deleted_rows INT;
BEGIN
LOOP
DELETE FROM logs_acesso
WHERE data_hora < NOW() - INTERVAL '90 days'
AND ctid IN (
SELECT ctid FROM logs_acesso
WHERE data_hora < NOW() - INTERVAL '90 days'
LIMIT 1000
);
GET DIAGNOSTICS deleted_rows = ROW_COUNT;
IF deleted_rows = 0 THEN
EXIT;
END IF;
COMMIT; -- Libera locks a cada batch
PERFORM pg_sleep(0.1); -- Pausa para reduzir IO
END LOOP;
END $$;
2.3 pg_partman para automação completa
A extensão pg_partman automatiza criação e remoção de partições:
-- Instalar pg_partman
CREATE EXTENSION pg_partman;
-- Configurar partição com retenção de 90 dias
SELECT partman.create_parent(
p_parent_table := 'public.logs_acesso',
p_control := 'data_hora',
p_type := 'native',
p_interval := '1 day',
p_premake := 30,
p_start_partition := '2025-01-01'
);
-- Agendar manutenção automática (remove partições antigas)
SELECT cron.schedule(
'partman-maintenance',
'0 4 * * *',
$$SELECT partman.run_maintenance()$$
);
3. Purge em Sistemas de Cache e Filas
3.1 Configuração de TTL no Redis
O Redis oferece expiração nativa de chaves:
# Exemplo de comandos Redis para TTL
SETEX user:session:123 86400 "dados_sessao" # Expira em 24h
EXPIRE cache:consulta:456 3600 # Expira em 1h
# Configuração de política de eviction no redis.conf
maxmemory 2gb
maxmemory-policy volatile-lru # Remove apenas chaves com TTL
3.2 Estratégias para filas Bull/Sidekiq
Para jobs expirados em filas, configure TTL e remova jobs órfãos:
// Bull (Node.js) - Remover jobs com mais de 7 dias
const Queue = require('bull');
const filaEmail = new Queue('email', 'redis://localhost:6379');
// Job de limpeza semanal
async function limparJobsAntigos() {
const jobs = await filaEmail.getJobs(['completed', 'failed']);
const limite = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const job of jobs) {
if (job.timestamp < limite) {
await job.remove();
}
}
}
3.3 Varredura com SCAN para chaves órfãs
Use SCAN para remover chaves sem TTL em lote:
# Script Lua no Redis para remover chaves órfãs
local cursor = '0'
local count = 0
repeat
local result = redis.call('SCAN', cursor, 'MATCH', 'temp:*', 'COUNT', 100)
cursor = result[1]
for _, key in ipairs(result[2]) do
local ttl = redis.call('TTL', key)
if ttl == -1 then -- Sem expiração
redis.call('EXPIRE', key, 3600) -- Define expiração
count = count + 1
end
end
until cursor == '0'
return count
4. Gerenciamento de Logs e Dados Não Estruturados
4.1 Rotação com logrotate
Configure logrotate para compressão e remoção automática:
# /etc/logrotate.d/minha-aplicacao
/var/log/minha-app/*.log {
daily
rotate 30 # Mantém 30 dias
compress
delaycompress
missingok
notifempty
create 0640 app app
postrotate
systemctl reload minha-app > /dev/null 2>&1 || true
endscript
}
4.2 ILM no Elasticsearch
Gerencie o ciclo de vida dos índices com fases automáticas:
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "30d"
}
}
},
"warm": {
"min_age": "30d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"cold": {
"min_age": "60d",
"actions": {
"freeze": {}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
4.3 Lifecycle policies no S3
Configure regras de expiração para buckets:
# Política de ciclo de vida no bucket S3
{
"Rules": [
{
"Id": "ExpireLogs",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Expiration": {
"Days": 90
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 60,
"StorageClass": "GLACIER"
}
]
}
]
}
5. Monitoramento e Validação do Processo de Purge
5.1 Métricas essenciais
Implemente métricas para acompanhar a eficácia:
-- Query para monitorar espaço recuperado
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as tamanho_atual,
(SELECT COUNT(*) FROM logs_acesso WHERE data_hora < NOW() - INTERVAL '90 days') as registros_para_remover
FROM pg_tables
WHERE tablename LIKE 'logs_%';
5.2 Alertas para falhas
Configure alertas no Prometheus/Grafana:
# Regra de alerta (Prometheus)
groups:
- name: purge_alerts
rules:
- alert: PurgeJobFailing
expr: rate(purge_job_errors_total[5m]) > 0
for: 10m
annotations:
summary: "Job de purge falhou na {{ $labels.instance }}"
5.3 Testes dry-run
Sempre simule antes de executar em produção:
-- Modo dry-run: apenas conta registros
SELECT COUNT(*) as registros_afetados
FROM logs_acesso
WHERE data_hora < NOW() - INTERVAL '90 days';
-- Simulação com BEGIN/ROLLBACK
BEGIN;
DELETE FROM logs_acesso
WHERE data_hora < NOW() - INTERVAL '90 days'
AND ctid IN (SELECT ctid FROM logs_acesso LIMIT 100);
SELECT COUNT(*) as removidos_simulacao FROM logs_acesso;
ROLLBACK;
6. Considerações de Performance e Segurança
6.1 Agendamento inteligente
Execute purges em janelas de baixa atividade:
# Cron para horário noturno (2h-5h)
0 2 * * * /usr/local/bin/purge_logs.sh # Início
30 4 * * * /usr/local/bin/verificar_purge.sh # Verificação
6.2 Batch size controlado
Evite sobrecarregar o banco:
-- DELETE com LIMIT para controle de carga
DO $$
DECLARE
batch_size INT := 500;
BEGIN
LOOP
WITH deletados AS (
DELETE FROM logs_acesso
WHERE ctid IN (
SELECT ctid FROM logs_acesso
WHERE data_hora < NOW() - INTERVAL '90 days'
LIMIT batch_size
)
RETURNING 1
)
SELECT COUNT(*) INTO batch_size FROM deletados;
EXIT WHEN batch_size = 0;
COMMIT;
PERFORM pg_sleep(0.05);
END LOOP;
END $$;
6.3 Integridade referencial
Verifique dependências antes do purge:
-- Verificar registros órfãos antes de remover
SELECT COUNT(*) FROM logs_acesso l
LEFT JOIN usuarios u ON l.usuario_id = u.id
WHERE u.id IS NULL AND l.data_hora < NOW() - INTERVAL '90 days';
-- Usar soft delete como segurança
UPDATE logs_acesso SET deleted_at = NOW()
WHERE data_hora < NOW() - INTERVAL '90 days';
7. Documentação e Governança da Política
7.1 Runbook de procedimentos
Documente cada etapa do processo:
# Runbook: Purge de Logs - Versão 2.1
# ======================================
#
# 1. Verificar espaço disponível
# df -h /var/lib/postgresql
#
# 2. Executar dry-run
# psql -d app -f /scripts/dry_run_purge.sql
#
# 3. Se registros > 1M, executar em batches
# nohup psql -d app -f /scripts/batch_purge.sql &
#
# 4. Verificar logs do job
# tail -f /var/log/purge/purge_$(date +%Y%m%d).log
#
# 5. Rollback (se necessário)
# psql -d app -c "SELECT restore_from_backup('2025-01-15')"
7.2 Versionamento de políticas
Mantenha changelog das alterações:
# CHANGELOG - Políticas de Retenção
#
# v2.1 - 2025-01-15
# - Reduzido TTL de logs de debug de 30 para 7 dias
# - Adicionado compressão gzip em logs > 100MB
#
# v2.0 - 2024-12-01
# - Implementado particionamento por dia
# - Adicionado pg_partman para automação
7.3 Auditoria e logs de compliance
Registre todas as operações de purge:
CREATE TABLE audit_purge_logs (
id SERIAL PRIMARY KEY,
data_execucao TIMESTAMPTZ DEFAULT NOW(),
tabela TEXT NOT NULL,
registros_removidos INT,
espaco_liberado_bytes BIGINT,
usuario_executor TEXT,
status TEXT CHECK (status IN ('sucesso', 'falha', 'rollback'))
);
-- Trigger para registrar purge
CREATE OR REPLACE FUNCTION log_purge()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_purge_logs (tabela, registros_removidos, usuario_executor, status)
VALUES (TG_TABLE_NAME, 1, current_user, 'sucesso');
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
Referências
- Documentação oficial pg_cron — Extensão do PostgreSQL para agendamento de jobs SQL, essencial para purge automático
- Guia de ILM no Elasticsearch — Documentação completa sobre gerenciamento do ciclo de vida de índices
- Políticas de expiração no Redis — Como configurar TTL e eviction policies para cache
- Lifecycle Policies no AWS S3 — Regras de transição e expiração para objetos em buckets
- logrotate manual — Ferramenta padrão Linux para rotação e compressão de logs
- PostgreSQL Partitioning Guide — Documentação oficial sobre particionamento de tabelas
- LGPD - Lei Geral de Proteção de Dados — Legislação brasileira que define prazos de retenção de dados pessoais