Capítulo 34 — Banco de Dados e Persistência
Este capítulo detalha a estratégia de persistência e armazenamento de dados da Plataforma de Relacionamento Digital com o Cidadão, descrevendo como dados transacionais, documentais, analíticos e de auditoria são armazena…
34.1 Objetivo do Capítulo
Este capítulo detalha a estratégia de persistência e armazenamento de dados da Plataforma de Relacionamento Digital com o Cidadão, descrevendo como dados transacionais, documentais, analíticos e de auditoria são armazenados, segregados, consultados, replicados e preservados ao longo do ciclo de vida da plataforma.
A arquitetura de dados separa responsabilidades por tipo e natureza do dado: dados transacionais operacionais em bancos dos serviços de domínio, dados documentais no Document Service e armazenamento de objetos, dados de cache em Redis, dados de busca em índices especializados, dados vetoriais para IA em vector store, dados analíticos em Data Lake, e dados de auditoria em Audit Store imutável.
Nenhum componente único atende indistintamente a todas essas necessidades. A tecnologia é selecionada conforme a natureza do dado, o padrão de acesso, os requisitos de latência, consistência, isolamento e conformidade regulatória.
34.2 Papel da Persistência na Plataforma
A persistência é a camada que converte operações transientes em estado permanente. Uma falha nesta camada afeta toda a plataforma: perda de dados invalida a solicitação do cidadão, inconsistência entre bancos degrada a confiabilidade, vazamento de dados entre tenants viola conformidade, e indisponibilidade do armazenamento derruba os serviços.
A arquitetura de persistência é, portanto, uma das decisões arquiteturais mais críticas. Ela é documentada em detalhe, testada rigorosamente e monitorada continuamente.
34.3 Princípios da Arquitetura de Dados
A persistência da plataforma é governada por dez princípios fundamentais:
1. Ownership por Domínio — cada serviço de domínio é o único responsável por gravar em seu armazenamento. Nenhum outro serviço acessa diretamente as tabelas do domínio alheio. Colaboração ocorre por API síncrona ou por eventos assíncronos.
2. Isolamento Multi-Tenant Estrutural — dados de um tenant nunca são acessíveis por outro por erro de aplicação ou falha de controle de acesso. O isolamento é estrutural na tecnologia escolhida, não apenas por filtro de aplicação.
3. Menor Privilégio em Credenciais — cada serviço usa credenciais de banco específicas com permissões mínimas. Não existe credencial administrativa compartilhada entre serviços.
4. Consistência Definida pelo Caso de Uso — transações locais para operações dentro de um domínio; consistência eventual entre domínios via eventos com idempotência.
5. Cache como Otimização — Redis é usado para aceleração de consultas e coordenação, nunca como fonte implícita de verdade de dados de negócio.
6. Separação OLTP / OLAP — dados transacionais operacionais e dados analíticos têm armazenamentos e responsabilidades distintas. Um serviço nunca consulta o Data Lake para decisão de negócio em tempo real.
7. Minimização de Dados Pessoais — PII é armazenado apenas onde funcionalmente necessário, com finalidade declarada e política de retenção. Dados pessoais em cache, logs ou índices de busca são minimizados.
8. Rastreabilidade de Alterações — alterações sensíveis são registradas em trilha de auditoria imutável com identificação de quem alterou, quando, qual valor anterior e qual novo valor.
9. Retenção Governada — cada tipo de dado tem política de retenção e exclusão. Exclusão de dados pessoais é executada conforme LGPD: anonimização quando possível, exclusão permanente quando necessário.
10. Índices e Projeções Reconstruíveis — índices de busca, vector stores e caches são projeções reconstruíveis a partir dos dados de origem. Em caso de inconsistência, a origem prevalece e a projeção é refeita.
34.4 Tecnologia de Banco Operacional
34.4.1 PostgreSQL como SGBD Relacional
O banco relacional primário da plataforma é PostgreSQL na versão 14 ou superior. A escolha é justificada por: maturidade, confiabilidade em ambiente crítico, suporte a JSON/JSONB para dados semi-estruturados, recurso de particionamento, Row-Level Security, triggers para auditoria, LISTEN/NOTIFY para comunicação intra-banco, e comunidade robusta.
Cada serviço de domínio possui banco dedicado ou schema isolado no mesmo cluster, conforme a estratégia definida em ADR-111.
34.4.2 Propriedade por Serviço
Request Service → banco request-service (ou schema request)
Workflow Service → banco workflow-service (ou schema workflow)
Citizen Service → banco citizen-service (ou schema citizen)
Document Service → banco document-service (ou schema document)
Scheduling Service → banco scheduling-service (ou schema scheduling)
Nenhum serviço acessa tabelas de outro serviço diretamente. Uma queries de Request Service que precisa de dados de Workflow Service consulta a Workflow API — não acessa o banco do workflow.
34.4.3 Credenciais e Permissões Mínimas
Cada serviço usa credenciais PostgreSQL específicas:
-- Usuário request-service-app
CREATE USER "request-service-app" WITH ENCRYPTED PASSWORD '...';
GRANT USAGE ON SCHEMA request TO "request-service-app";
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA request TO "request-service-app";
-- Sem permissão em schemas de outros serviços
Credenciais administrativas são rotacionadas regularmente e nunca compartilhadas entre equipes ou serviços.
34.4.4 Isolamento Multi-Tenant no Banco
A estratégia de isolamento multi-tenant no nível de banco é uma das decisões arquiteturais mais críticas. Três modelos são avaliados conforme o requisito:
Modelo 1: Banco Dedicado por Tenant
Cada tenant principal ou grande volume possui seu próprio banco PostgreSQL. Vantagens: isolamento máximo, backup e restauração por tenant, performance sem competição de recursos, conformidade contratual facilitada. Desvantagens: provisionamento, custo de infraestrutura, múltiplas migrações de schema, gestão operacional em escala.
Modelo 2: Schema por Tenant
Todos os tenants compartilham o mesmo cluster PostgreSQL, mas cada tenant tem seus próprios schemas. Vantagens: conformidade com GDPR (dados separados logicamente), gestão centralizada de backup, economia de recursos. Desvantagens: grande número de schemas (centenas), migrations muito lentas, conexões e memory overhead.
Modelo 3: Shared Schema com Tenant Discriminator
Todas as tabelas multi-tenant incluem coluna tenant_id como discriminator. Vantagens: simplicidade operacional extrema, economia de infraestrutura, escalabilidade horizontal ilimitada. Desvantagens: exige disciplina rigorosa no código (Tenant Context sempre aplicado), risco de data leakage por query sem filtro de tenant, impossível isolar backup por tenant.
Modelo Híbrida (Recomendado)
A maioria dos tenants em shared schema com tenant_id discriminator; tenants com requisitos especiais (grande volume, conformidade contratual, isolamento mandatório) em banco ou schema dedicado. O Tenant Context determina o roteamento automaticamente.
A decisão final é registrada como ADR-112 com justificativa técnica, financeira e de conformidade.
34.4.5 Padrões Obrigatórios em Shared Schema
Quando o modelo shared schema é adotado, os seguintes padrões são obrigatórios:
Tenant Discriminator em toda Tabela Multi-Tenant
CREATE TABLE requests (
id UUID PRIMARY KEY,
tenant_id VARCHAR NOT NULL,
protocol_code VARCHAR NOT NULL,
subject_id VARCHAR NOT NULL,
status VARCHAR NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tenant_id, subject_id) REFERENCES citizens(tenant_id, id)
);
Toda tabela multi-tenant inclui tenant_id como coluna NOT NULL. O tenant_id efetivo vem sempre do Tenant Context resolvido a partir da identidade autenticada — nunca de parâmetro enviado pelo cliente.
Constraints Multi-Tenant
-- Incorreto: dois tenants poderiam compartilhar o mesmo código
UNIQUE (protocol_code);
-- Correto: protocolo é único por tenant
UNIQUE (tenant_id, protocol_code);
-- Chave estrangeira deve incluir tenant para garantir referência dentro do tenant
FOREIGN KEY (tenant_id, citizen_id) REFERENCES citizens(tenant_id, id);
Sem constraint multi-tenant, dois tenants podem violar integridade referencial ou unicidade um do outro.
Índices Orientados a Queries Reais
-- Query comum: "listar solicitações do tenant de um cidadão, ordenadas por data"
CREATE INDEX idx_requests_tenant_citizen_created
ON requests (tenant_id, subject_id, created_at DESC);
-- Query de admin: "encontrar todas as solicitações de um tenant com status específico"
CREATE INDEX idx_requests_tenant_status
ON requests (tenant_id, status);
Índices incluem tenant_id como primeira coluna para garantir que a query use índice. Sem isso, queries podem escaneiar tabela inteira mesmo com filtro de tenant.
Row-Level Security (Camada Adicional)
Quando o banco PostgreSQL suporta, Row-Level Security é avaliado como camada adicional de proteção:
CREATE POLICY tenant_isolation ON requests
USING (tenant_id = current_setting('app.current_tenant')::varchar);
ALTER TABLE requests ENABLE ROW LEVEL SECURITY;
RLS garante que mesmo uma query sem cláusula WHERE de tenant não retorna dados de outro tenant. Registrado como ADR-114 (adoção e custo de performance).
34.5 Transações e Consistência
34.5.1 Transações Locais ao Serviço
A plataforma não utiliza transações distribuídas (2PC — Two-Phase Commit). Transações permanecem locais ao serviço que gerencia o banco. Consistência entre domínios é obtida por eventos com Transactional Outbox e idempotência nos consumidores.
Fluxo Transacional Típico:
BEGIN TRANSACTION (banco do Request Service)
1. UPDATE requests SET status = 'SUBMITTED', version = 2
WHERE id = '...' AND tenant_id = '...' AND version = 1
2. INSERT INTO outbox_event (
aggregate_id, aggregate_type, event_type, payload, tenant_id, created_at
) VALUES (
'...', 'REQUEST', 'REQUEST_SUBMITTED', '{...}', '...', NOW()
)
COMMIT
Ambas operações são atômicas. Se falhar INSERT na outbox, toda a transação reverte. O RabbitMQ nunca recebe evento de uma transação que falhou.
Consumidor Recebe o Evento:
Workflow Service recebe REQUEST_SUBMITTED
├─ Verifica idempotência: existe workflow com idempotency_key?
├─ Sim → retorna resposta cached sem lado-a-lado
└─ Não:
BEGIN TRANSACTION (banco do Workflow Service)
1. INSERT INTO workflows (request_id, ..., idempotency_key)
2. INSERT INTO outbox_event (...)
COMMIT
34.6 Controle de Concorrência
34.6.1 Optimistic Locking
A maioria das operações usa optimistic locking via coluna version:
UPDATE requests
SET status = 'APPROVED', version = 19, updated_at = NOW()
WHERE id = '...' AND tenant_id = '...' AND version = 18;
Se nenhuma linha for afetada (version != 18), a aplicação recebe zero rows affected e retorna 409 Conflict ao cliente com instrução de recarregar e tentar novamente.
Optimistic locking não bloqueia leituras, permitindo altíssima concorrência para casos sem conflito real.
34.6.2 Pessimistic Locking
Pessimistic locking é utilizado para seções críticas curtas com recurso escasso (ex.: última vaga em agendamento). Exemplo:
BEGIN TRANSACTION;
SELECT * FROM scheduling_slots
WHERE tenant_id = '...' AND date = '...'
FOR UPDATE SKIP LOCKED;
-- Uma única conexão obtém o lock
UPDATE scheduling_slots SET booked_count = booked_count + 1
WHERE id = '...' AND booked_count < total_count;
COMMIT;
Pessimistic locking é evitado em operações longas, pois bloqueia outros processos por período estendido.
34.6.3 Distributed Locks via Redis
Para coordenação entre múltiplas instâncias de um serviço (ex.: apenas uma instância executa reconciliação periódica por tenant), distributed locks via Redis são utilizados:
const lockKey = `lock:reconciliation:${tenantId}`;
const lockValue = uuid(); // owner token único
const lockTTL = 300; // 5 minutos
// Adquirir lock atomicamente com timeout
const acquired = await redis.set(
lockKey,
lockValue,
'EX', lockTTL,
'NX' // Apenas se não existe
);
if (acquired) {
try {
await reconcile(tenantId);
} finally {
// Liberar somente se o token ainda pertence a nós
await redis.eval(
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end',
1, lockKey, lockValue
);
}
}
34.7 Migrações de Schema
34.7.1 Versionamento Automatizado
Migrações de schema são automatizadas com Flyway ou Liquibase (decisão padronizada em ADR-130). Cada migração é um arquivo versionado no repositório de código do serviço:
src/main/resources/db/migration/
V001__create_requests_table.sql
V002__add_tenant_id_column.sql
V003__create_idx_requests_tenant_status.sql
Migrações são executadas automaticamente no startup do serviço. Rollback é evitado; migrações são apenas forward.
34.7.2 Estratégia Expand-Migrate-Contract para Zero-Downtime
Para alterações de schema que exigem zero-downtime (nunca derrubar o serviço), a estratégia Expand-Migrate-Contract é aplicada:
1. EXPAND (Release N) Adicionar nova coluna nullable ou nova tabela sem afetar código existente:
ALTER TABLE requests ADD COLUMN new_status VARCHAR;
2. DEPLOY Versão N+1 e N do serviço rodam simultaneamente. Código N continua usando coluna antiga; código N+1 começa a popular coluna nova durante operações de escrita.
3. MIGRATE Backfill de dados existentes (em batches se grande volume):
UPDATE requests SET new_status = status_mapping[status]
WHERE new_status IS NULL;
Backfills longos nunca são bloqueantes no startup. São jobs assíncronos com checkpoint, rate control e observabilidade.
4. SWITCH Código da aplicação migra para usar apenas coluna nova. Coluna antiga não é mais escrita.
5. CONTRACT (Release N+2) Remover coluna antiga em release posterior, quando todas instâncias da versão N foram descomissionadas.
34.8 Temporalidade e Timestamps
A plataforma distingue semanticamente quatro tipos de timestamp:
| Campo | Significado | Exemplo |
|---|---|---|
created_at | Quando o registro foi persistido no banco | Requisição INSERT em 2026-07-16 10:30:00 UTC |
updated_at | Quando o registro foi modificado por último | Última UPDATE em 2026-07-16 14:45:00 UTC |
occurred_at | Quando o fato de negócio efetivamente ocorreu | Solicitação aberta pelo cidadão em 2026-07-16 10:25:00 UTC |
effective_from / effective_to | Vigência de uma regra, configuração ou versão | Política de retenção vigente de 2026-01-01 a 2026-12-31 |
Timezone Obrigatório:
Todos os timestamps são armazenados em UTC (UTC+0). A interface apresenta o timestamp no fuso horário do usuário ou do tenant. Conversão ocorre na camada de apresentação, nunca no banco.
-- Correto: armazena em UTC
INSERT INTO requests (created_at) VALUES (CURRENT_TIMESTAMP AT TIME ZONE 'UTC');
-- Busca:
SELECT created_at AT TIME ZONE 'America/Sao_Paulo' FROM requests;
34.9 Trilha de Auditoria
34.9.1 Componentes da Trilha
Alterações sensíveis são registradas em trilha imutável com: data/hora UTC, tenant_id, subject_id (usuário que executou), tipo de ação, recurso afetado, valor anterior, valor novo e resultado.
O Audit Store é segregado do banco operacional. Em outras palavras, mesmo que o banco de domínio seja comprometido, a trilha de auditoria permanece íntegra.
34.9.2 Implementação com Triggers
A trilha é registrada automaticamente via triggers de banco:
-- Tabela de auditoria no schema separado
CREATE TABLE audit.audit_log (
id BIGSERIAL PRIMARY KEY,
tenant_id VARCHAR NOT NULL,
table_name VARCHAR NOT NULL,
row_id VARCHAR NOT NULL,
action VARCHAR NOT NULL, -- INSERT, UPDATE, DELETE
subject_id VARCHAR NOT NULL,
occurred_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
old_values JSONB,
new_values JSONB,
correlation_id VARCHAR
);
-- Trigger que registra UPDATE na tabela requests
CREATE OR REPLACE FUNCTION audit.requests_update()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit.audit_log (
tenant_id, table_name, row_id, action, subject_id,
old_values, new_values
) VALUES (
NEW.tenant_id, 'requests', NEW.id, 'UPDATE',
current_setting('app.current_subject_id', true),
to_jsonb(OLD), to_jsonb(NEW)
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_audit_requests_update
AFTER UPDATE ON requests
FOR EACH ROW
EXECUTE FUNCTION audit.requests_update();
34.9.3 Dados Pessoais na Trilha
Dados pessoais na trilha são minimizados. A trilha registra referências (IDs e tipos de alteração), não cópias completas de dados sensíveis. Se necessário referenciar nome do cidadão, apenas o ID é incluído.
34.10 Retenção e Exclusão de Dados
34.10.1 Política de Retenção por Domínio
Cada tipo de dado tem política de retenção definida conforme requisito de negócio e LGPD:
| Tipo de Dado | Retenção | Justificativa |
|---|---|---|
| Solicitações finalizadas | 5 anos após conclusão | Prazo de auditoria administrativa |
| Logs de auditoria | 5 anos | Conformidade e investigação |
| Dados de cidadão | Enquanto vínculo ativo + 6 meses | LGPD art. 16 — eliminação após finalidade |
| Sessões ativas | TTL da sessão (15 min) | Segurança |
| Logs de aplicação | 90 dias | Operacional |
| Documentos em GED | 5 anos após upload (configurável por tenant) | Documentação de processos |
| Telemetria operacional | 90 dias | Diagnóstico |
34.10.2 Exclusão Lógica vs. Física
A plataforma adota exclusão lógica por padrão (deleted_at timestamp):
UPDATE documents
SET deleted_at = NOW()
WHERE id = '...' AND tenant_id = '...';
A exclusão lógica preserva a possibilidade de auditoria e recuperação, e é compatível com LGPD quando combinada com restrição de leitura ao contexto administrativo (apenas administrador do tenant vê soft-deleted).
34.10.3 Anonimização Conforme LGPD
Para cumprir direito ao esquecimento (LGPD art. 18, VI), a plataforma anonimiza dados pessoais identificáveis mantendo o registro técnico:
UPDATE citizens
SET
name = 'CIDADÃO ANONIMIZADO',
email = CONCAT('anonimo-', id, '@example.com'),
phone = NULL,
birth_date = NULL,
anonymized_at = NOW()
WHERE id = '...' AND tenant_id = '...';
O ID técnico (UUID) é preservado para permitir rastreabilidade de ações em auditoria sem expor dados pessoais.
34.10.4 Hard Delete para Conformidade Rigorosa
Em situações extremas (cumprimento de ordem judicial, vazamento confirmado de dados), hard delete é executado em job controlado com trilha de auditoria da própria exclusão:
DELETE FROM documents WHERE id = '...' AND tenant_id = '...';
DELETE FROM audit_hard_delete_log (...);
34.11 Conexões e Performance
34.11.1 Pool de Conexões
Cada serviço usa pool de conexões com limite dimensionado conforme carga:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 5000
idle-timeout: 600000
max-lifetime: 1800000
Pool maior aumenta concorrência, mas cada conexão consome memória do servidor PostgreSQL (tipicamente 5-10 MB por conexão). O sizing correto depende do workload medido.
34.11.2 Query Optimization
Queries são otimizadas continuamente com EXPLAIN ANALYZE, índices apropriados, e monitoramento de queries lentas (slow query log):
-- Ativar log de queries lentas (> 1 segundo)
ALTER SYSTEM SET log_min_duration_statement = 1000;
SELECT pg_reload_conf();
34.11.3 Connection Limit Monitoring
Limite de conexões ao PostgreSQL é monitorado e alerta quando exceder threshold. Um número excessivo de conexões indica vazamento de conexões na aplicação (connections abertas e não fechadas).
34.12 Backup e Recuperação
34.12.1 Estratégia de Backup
A estratégia de backup combina três técnicas:
Backup Físico (pg_basebackup):
Executado diariamente em horário de menor carga. Backup completo da instância PostgreSQL. Retenção: 30 dias. Armazenado em storage criptografado segregado do ambiente de produção.
Backup Lógico (pg_dump):
Executado a cada 6 horas. Exporta schema + dados em formato texto. Retenção: 7 dias. Usado para restore seletivo (apenas um banco ou schema) e migração entre versões.
Point-in-Time Recovery (PITR):
WAL archiving continuamente habilitado. Permite restaurar o banco em qualquer timestamp dentro da janela de retenção. Retenção padrão: 7 dias. Janela configurável conforme criticidade do serviço.
34.12.2 Restore de Banco
Restore é testado periodicamente (trimestralmente) em ambiente de staging:
# Restore completo
pg_restore -d cidadao_staging /backup/cidadao_2026-07-16.dump
# Restore até timestamp específico
pg_basebackup + WAL replay com --target-time '2026-07-16 14:30:00'
34.12.3 Restore por Tenant
Quando o modelo é shared schema com tenant discriminator, restore por tenant é impossível via backup de banco. Solução:
- Export por tenant: pg_dump com filtro WHERE tenant_id = X; restore apenas de dados do tenant;
- Restore completo + masking: restaura banco completo em ambiente isolado; administradores do tenant podem visualizar apenas dados do próprio tenant.
A estratégia de recuperação por tenant é registrada como ADR-124.
34.13 Alta Disponibilidade e Replicação
34.13.1 Replicação Streaming
PostgreSQL é configurado com replicação streaming para réplica primária + 2 réplicas assíncronas (ou síncronas conforme criticidade):
Aplicação
│
├─ INSERT/UPDATE/DELETE → Primary
│
Primary
│
├─ WAL streaming → Replica 1 (sync) — failover
├─ WAL streaming → Replica 2 (async)
└─ WAL streaming → Replica 3 (async)
Síncrono garante que a transação não é confirmada até que pelo menos uma réplica tenha recebido o WAL — garante zero perda de dados em failover. Assíncrono tem latência menor mas risco de perda de dados em failover.
34.13.2 Failover Automático
Em produção, Patroni + etcd ou pg_auto_failover gerenciam failover automático. Quando a primária torna-se indisponível, uma réplica é promovida em segundos. Aplicações detectam e reconectam-se à nova primária.
34.13.3 Read Replicas para Queries Analíticas
Read replicas segregam carga analítica (dashboards, relatórios) da transacional. Aplicações de analytics apontam para read replica; produção aponta para primária. Latência de replicação (tipicamente <1 segundo) é tolerável para analytics.
34.14 Observabilidade do Banco
34.14.1 Métricas PostgreSQL
Métricas coletadas e expostas via Prometheus:
- conexões ativas vs. máximo permitido;
- queries por segundo;
- latência média de query;
- replicação lag;
- cache hit ratio;
- index hit rate;
- locks ativos.
34.14.2 Alertas Configurados
Alertas críticos:
- conexão ao banco falhou por mais de 30 segundos;
- replicação lag > 60 segundos;
- número de conexões ativas > 80% do máximo;
- queries com latência > 5 segundos em alta frequência;
- disk usage > 85% do tamanho provisionado.
34.14.3 Tracing de Queries
Queries são correlacionadas com traces distribuídos via OpenTelemetry. Cada query tem span com nome descritivo, identificador de tenant, e tempo de execução. Queries lentas (>1 segundo) são automaticamente destacadas.
34.15 Banco Vetorial e Embeddings
34.15.1 Vector Store para RAG
O vector store armazena embeddings de documentos e conteúdo autorizado para suporte ao RAG (Retrieval-Augmented Generation) dos assistentes de IA:
Documento autorizado
│
├─ Sanitização e chunking (blocos de até 500 tokens)
│
├─ Geração de embedding via LLM Gateway
│
├─ Armazenamento no vector store do tenant
│
└─ Publicação de evento ai.knowledge-indexed.v1
34.15.2 Isolamento por Tenant
Cada tenant tem sua própria coleção ou namespace vetorial. Uma query de busca semântica do tenant A nunca retorna conteúdo do tenant B — segregação é estrutural, não apenas por filtro de aplicação.
34.15.3 Reconstruibilidade
O vector store é reconstruível a partir dos documentos autorizados armazenados no Document Service / GED. Em caso de inconsistência, a origem (Document Service) prevalece e a projeção vetorial é refeita via job de reindexação.
34.16 Rastreabilidade com Anexo III
| Item ANX-III | Atendimento |
|---|---|
| 6.2 | Controle de acesso com isolamento multi-tenant no nível de banco (tenant_id em constraints e índices) |
| 6.3 | Auditoria de alterações via triggers e trilha imutável em schema segregado |
| 1.1 | Integração de dados entre módulos via eventos com idempotência e Transactional Outbox |
34.17 Benefícios da Arquitetura de Dados
- isolamento multi-tenant garantido estruturalmente — tenant_id em constraints, não apenas em filtros de aplicação;
- consistência eventual entre domínios — sem deadlocks de transações distribuídas; eventos com Transactional Outbox;
- auditoria completa — trilha imutável segregada com timestamps UTC e subject_id;
- escalabilidade horizontal — bancos segregados por serviço; replicação e particionamento por tenant;
- recuperabilidade — backup físico, lógico e PITR; restore testado trimestralmente;
- performance otimizada — índices multi-tenant, cache com invalidação ativa, read replicas para analytics;
- conformidade LGPD — retenção governada, anonimização e exclusão controlada, minimização de PII;
- observabilidade completa — métricas, alertas e tracing de queries com identificador de tenant.
34.18 Riscos e Mitigações
| Risco | Consequência | Mitigação |
|---|---|---|
| Tensor sem tenant_id em filtro | Vazamento de dados entre tenants | Constraint de unicidade inclui tenant_id; índices incluem tenant_id; re-validação em aplicação |
| N+1 queries | Latência, sobrecarga do banco | Queries otimizadas com JOINs; eager loading; cache de dados referência |
| Cache inconsistente | Dado obsoleto entregue como atual | Invalidação ativa por evento; TTL explícito; fallback à origem |
| Backup não testado | Impossibilidade de recover em incidente | Restore testado trimestralmente em staging; documentação de RTO/RPO |
| Conexão não devolvida ao pool | Esaurimento de conexões; bloqueio de aplicação | Pool configurado; monitoring de conexões ativas; timeout de conexão |
| Replicação lag não monitorado | Dados inconsistentes lidos de réplica | Lag monitorado e alerta > 60 segundos; queries críticas leem de primária |
| Auditoria correlacionada ao banco operacional | Comprometimento da auditoria | Schema segregado; triggers em transação separada; retenção longa |
| Exclusão de dados sem conformidade LGPD | Violação de privacidade | Anonimização padrão; exclusão física em casos extremos com trilha de auditoria da exclusão |
34.19 Decisões Arquiteturais
| ADR | Tema |
|---|---|
| ADR-111 | Isolamento multi-tenant — banco por serviço, schema por tenant ou shared schema com discriminator |
| ADR-112 | Estratégia de segregação multi-tenant no nível de banco (constraints, índices, RLS) |
| ADR-113 | Tecnologia de banco para cada domínio (PostgreSQL, especializado) |
| ADR-114 | Row-Level Security (RLS) — adoção e limitações |
| ADR-115 | Transações — local vs. distribuída; Transactional Outbox pattern |
| ADR-116 | Replicação PostgreSQL — síncrona vs. assíncrona |
| ADR-117 | Backup — frequência, retenção, PITR; restore automático vs. manual |
| ADR-118 | Cache Redis — TTL, invalidação, política de falha |
| ADR-119 | Vector store para RAG — tecnologia e segregação por tenant |
| ADR-120 | Migrations de schema — Flyway/Liquibase; expand-migrate-contract |
| ADR-121 | Temporalidade — campos created_at, updated_at, occurred_at em UTC |
| ADR-122 | Auditoria — triggers vs. aspectos; trilha segregada vs. banco operacional |
| ADR-123 | Retenção e exclusão — soft delete vs. hard delete; anonimização LGPD |
| ADR-124 | Restore por tenant — viabilidade e estratégia em shared schema |
34.20 Considerações Finais
A arquitetura de dados é a fundação da plataforma. A segregação correcta de multi-tenant no nível de banco — com tenant_id em constraints, índices e RLS quando aplicável — não é um detalhe operacional. É a diferença entre um sistema seguro e um sistema vulnerável a vazamento de dados.
A escolha entre banco por tenant, schema por tenant ou shared schema é trade-off entre isolamento máximo (banco/schema), operação em escala (shared schema) e custo. Qualquer que seja a escolha, Tenant Context derivado de identidade validada deve permear toda a arquitetura.
O padrão de Transactional Outbox garante que eventos são gravados na mesma transação dos dados operacionais, eliminando a possibilidade de perda de evento ou gravação de dado sem publicação. A consistência eventual entre domínios é alcançada sem deadlocks de transações distribuídas.
A auditoria segregada do banco operacional preserva integridade mesmo em cenários de comprometimento. Dados pessoais na auditoria são minimizados. Conformidade com LGPD é estruturada em retenção, anonimização e exclusão.
O backup em múltiplas formas (físico, lógico, PITR) garante recuperabilidade em cenários diversos. Restore testado trimestralmente valida que a estratégia realmente funciona — não apenas em teoria.
O Capítulo 35 detalha Cache Distribuído.
34.21 Controle de Versão
| Campo | Valor |
|---|---|
| Documento | Documento Mestre — Plataforma de Relacionamento Digital com o Cidadão |
| Capítulo | 34 — Banco de Dados e Persistência |
| Versão | 1.0 |
| Situação | Concluído |
| Última atualização | 16/07/2026 |
34.22 Rastreabilidade PRODEMGE
- [ANX-III] Bloco 1 — itens 1.1, 1.2 (dados compartilhados entre serviços via eventos; cada serviço com banco próprio)
- [ANX-III] Bloco 6 — itens 6.2, 6.3 (controle de acesso multi-tenant; auditoria de alterações)
- [ANX-IV] Itens 3.1.6, 3.1.7, 3.1.8 — escalabilidade por replicação e particionamento; isolamento estrutural; consistência via Transactional Outbox
- [ANX-V] Item 2.1 — migrations versionadas com Flyway; estratégia expand-migrate-contract para zero-downtime
- [ANX-V] Item 2.3 — retenção governada, anonimização LGPD, direito ao esquecimento, exclusão controlada
- [ANX-V] Item 2.4 — backup físico + lógico + PITR; restore testado trimestralmente; RTO/RPO definidos por serviço
- [PNR] — Banco de dados PostgreSQL relacional como tecnologia principal; cache Redis; vetor store segregado por tenant; data lake por tenant
- [EDITAL] — Edital CP001/2026: isolamento multi-tenant estrutural; LGPD compliant; backup, recuperação e replicação
34.23 Padrões de Conformidade Multi-Tenant no Banco
-- Padrão 1: Toda tabela multi-tenant inclui tenant_id NOT NULL
CREATE TABLE <entity> (
id UUID PRIMARY KEY,
tenant_id VARCHAR NOT NULL,
...
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
-- Padrão 2: Toda constraint de unicidade é (tenant_id, chave natural)
ALTER TABLE <entity> ADD CONSTRAINT uniq_<entity>_natural_key
UNIQUE (tenant_id, natural_key_column);
-- Padrão 3: Toda foreign key cross-table inclui tenant_id
ALTER TABLE <entity> ADD CONSTRAINT fk_<entity>_<other>_<fk>
FOREIGN KEY (tenant_id, <fk_column>) REFERENCES <other_table>(tenant_id, id);
-- Padrão 4: Todo índice multi-tenant começa com tenant_id
CREATE INDEX idx_<entity>_<access_pattern>
ON <entity> (tenant_id, <colunas_do_padrão_de_acesso>);
-- Padrão 5: Trigger de auditoria registra tenant_id automaticamente
CREATE TRIGGER trg_audit_<entity>_<action>
AFTER <action> ON <entity>
FOR EACH ROW
EXECUTE FUNCTION audit.<entity>_<action>();
Estes cinco padrões garantem que o isolamento multi-tenant é estrutural no banco de dados. Uma violação acidental é detectada via constraint ou trigger antes de retornar dados ao cliente.
34.24 Operação de Banco — Runbook
34.24.1 Monitoramento Diário
| Métrica | Threshold | Ação |
|---|---|---|
| Conexões ativas | < 80% do máximo | Alerta em > 80%; intervenção em > 90% |
| Replicação lag | < 60s | Alerta em > 60s; escalação em > 5min |
| Slow queries | < 10 queries/hora > 5s | Revisão diária das queries mais lentas |
| Disk usage | < 85% provisionado | Expansão de storage se > 80% |
| Backup diário | Executado com sucesso | Alerta se falhar; backup manual de emergência |
34.24.2 Rotação de Credenciais
Credenciais de banco são rotacionadas trimestralmente. Procedimento:
- Gerar nova credencial forte;
- Aplicar em ambiente de staging; testar por 1 semana;
- Aplicar em produção em janela de baixa carga;
- Atualizar secrets no cofre (Vault/SOPS);
- Reiniciar serviços para aplicar nova credencial;
- Revogar credencial antiga após 24h de comprovação de sucesso.
34.24.3 Atualizações de Versão PostgreSQL
Atualizações de versão (ex.: PostgreSQL 15 → 16) seguem procedimento controlado:
- Atualizar versão em staging; executar suite de testes de regressão;
- Atualizar em produção com janela de manutenção anunciada;
- Migrar réplicas uma por uma (rolling upgrade);
- Validar integridade de dados (constraints, contagens, testes E2E);
- Monitorar métricas por 1 semana pós-upgrade.
Fim do Capítulo 34 — Banco de Dados e Persistência
Capítulo 33 — Aplicativo Mobile: React Native
Este capítulo detalha a implementação técnica do Aplicativo Mobile do Cidadão — o canal digital nativo para iOS e Android que complementa o Portal Web com experiência adaptada a dispositivos móveis.
Capítulo 35 — Cache Distribuído
Este capítulo detalha a estratégia de cache distribuído da Plataforma de Relacionamento Digital com o Cidadão, descrevendo como diferentes tipos de dados são acelerados por cache distribuído, como invalidação é coordenad…