Pular para o conteúdo principal

Procedimento Operacional — Arquitetura e Modelagem de Dados

Código: HORUS-DAT-PRO-001 Versão: 2.4 — 2026-10-05 Escopo: Regras e processos que a equipe deve seguir ao propor, revisar, aprovar e implementar mudanças no modelo de dados dos Sistemas do DTIC (PostgreSQL operacional e BigQuery analítico). Público: Arquitetos de dados, Tech Leads, desenvolvedores e gestores responsáveis por sistemas.


Resumo Executivo​

Este documento define como a equipe trabalha com dados nos Sistemas do DTIC. Ele cobre o processo de aprovação de mudanças de schema, os padrões de nomenclatura, as regras de versionamento, os critérios de classificação de dados e o fluxo de promoção entre ambientes (HOM → PROD).

O objetivo é garantir que o crescimento do modelo de dados seja previsível, rastreável e auditável — especialmente em períodos em que múltiplas mudanças acontecem em paralelo (como migrações e modernizações de sistemas).


1. Processo de Mudança de Schema​

Toda alteração no modelo de dados — criação de tabela, adição de coluna, criação de índice, alteração de constraint — segue o fluxo abaixo.

1.1 Fluxo de aprovação​

[Solicitante] → Proposta de mudança (Issue/PR)
↓
[Arquiteto de Dados] → Revisão técnica (nomenclatura, impacto, LGPD)
↓
[Gestor do Domínio] → Validação de negócio
↓
[Tech Lead] → Revisão de performance e operação
↓
Aprovação → Merge para HOM → Validação → Merge para PROD

1.2 O que exige aprovação formal (todos os 3 revisores)​

  • Criação de nova tabela
  • Módulo novo ou módulo que passa a usar entidade mestre (anexar a ficha arquitetural do Doc 06, Seção 8)
  • Criação de schema novo
  • Alteração de chave primária ou chave estrangeira
  • Remoção de coluna ou tabela
  • Adição de coluna com restrição NOT NULL em tabela com dados
  • Alteração de tipo de dado, tamanho ou precisão de coluna existente

Criação de schema novo e mudança em entidade mestre (mdm) exigem, além dos três revisores, homologação do Comitê de Dados (Doc 04, Seção 8.2).

1.3 O que exige apenas aprovação do Arquiteto de Dados​

  • Adição de coluna nullable em tabela existente
  • Criação de índice
  • Alteração de comentário ou descrição de coluna
  • Criação de view fora do domínio do próprio proponente

1.4 O que o desenvolvedor pode fazer sem aprovação prévia​

  • Criação de tabela temporária ou de staging em schema de desenvolvimento
  • Ajuste de dados em HOM sem impacto estrutural
  • Criação de view em schema próprio do domínio.

2. Padrões de Nomenclatura​

2.1 PostgreSQL​

ElementoPadrãoExemplo
Schema (quando o sistema organiza o banco em schemas)snake_case, singular{dominio}
Tabelasnake_case, pluralpessoas, municipios, processos
Colunasnake_case, com prefixo de tipo de dado quando a coluna se enquadra em um termo do catálogo (Seção 2.1.1)dt_nascimento, cd_ibge, nr_cpf
Chave primáriaid_{singular_da_tabela} (UUID) — tabela pessoas → id_pessoaid_pessoa, id_processo
Chave estrangeira (coluna)id_{entidade}; com papel, id_{entidade}_{papel}id_municipio, id_usuario_cadastro
Constraint FKfk_{tabela}_{coluna}fk_processos_id_municipio
Constraint CHECKck_{entidade}_{descricao} (entidade no singular)ck_participante_origem
Índiceidx_{entidade}_{coluna(s)} (entidade no singular)idx_pessoa_nr_cpf, idx_processo_dt_abertura
Índice únicouq_{entidade}_{coluna(s)} (entidade no singular)uq_pessoa_nr_cpf
Sequenceseq_{tabela}_{coluna}(evitar — usar UUID)
Viewvw_{descricao}vw_processos_ativos
Funçãofn_{verbo}_{descricao}fn_buscar_municipio_por_ibge
Triggertg_{momento}_{tabela}_{acao}tg_before_pessoas_update
Enum{nome}_enum, com prefixo do schema quando houvertipo_pessoa_enum

Nomes de objeto (tabela, constraint, índice) são limitados a 63 bytes no PostgreSQL. Abreviar de forma determinística e documentada — nunca depender de truncamento silencioso. Por brevidade, alguns exemplos deste documento usam o nome da entidade no singular; em objetos reais, o nome da tabela é sempre plural, a menos que não exista a versão em plural da palavra.

2.1.1 Prefixo de coluna​

Quando a coluna se enquadra em um dos termos da tabela abaixo, o nome começa por um prefixo de 2 letras seguido de _, que informa o que a coluna é. Coluna que não se enquadra em nenhum termo não leva prefixo. O prefixo indica o tipo do dado (ou o papel de chave), não a tabela de origem — por isso não se confunde com o prefixo de entidade do legado, que é proibido (ver abaixo). Ele corresponde ao termo de representação da ISO/IEC 11179-5 (data, número, nome, indicador etc.) e não ao tipo físico da coluna: fl_ costuma ser BOOLEAN, mas nr_cpf é texto.

PrefixoSignificaQuando usarExemplo
id_IdentificadorPK, FK e identificador de origem. Formato id_{entidade}; com papel: id_{entidade}_{papel}id_pessoa, id_municipio, id_usuario_cadastro, id_origem_externa
dt_DataData sem horadt_nascimento, dt_vencimento
dh_Data e horaTIMESTAMPTZdh_assinatura, dh_cadastro
nr_NúmeroNúmero de negócio emitido (processo, contrato) e numeral de documento oficial (CPF, CNPJ, RG); nunca PK/FKnr_processo, nr_cpf, nr_cnpj
vl_ValorValor monetário ou numéricovl_contrato, vl_renda_mensal
qt_QuantidadeContagemqt_membros_familia
nm_NomeNome próprio ou denominaçãonm_completo, nm_razao_social, nm_municipio
ds_DescriçãoTexto livreds_observacao
fl_FlagIndicador booleanofl_excluido, fl_urgente
tp_TipoClassificação por valor controlado, quando não modelada como tabela de domíniotp_documento, tp_origem_cadastro
sg_SiglaSiglasg_uf
cd_CódigoCódigo de referência externa ou de domíniocd_ibge, cd_situacao, cd_cnae_principal
js_JSONDocumento JSON/JSONBjs_antes, js_depois
gm_GeometriaTipo geoespacial (PostGIS)gm_limite

Regras

  1. Se a coluna se enquadra em um termo da tabela, o prefixo correspondente é obrigatório, inclusive nas colunas de auditoria e de soft delete (Seção 3), que sempre se enquadram.
  2. Coluna que não se enquadra em nenhum termo fica sem prefixo e segue snake_case descritivo, em português (Seção 2.1). O catálogo é fechado: um prefixo novo entra neste documento, com aprovação do Arquiteto de Dados, antes de ser usado.
  3. O prefixo é sempre de 2 letras; id_ segue a mesma regra e é o único prefixo de chave.
  4. Os prefixos de objeto da Seção 2.1 (idx_, uq_, fk_, fn_, tg_, seq_) identificam o tipo de objeto e não são usados em colunas.

Como escolher: é chave ou identificador de origem? id_. Senão: data? dt_ (com hora, dh_). Número emitido ou documento oficial? nr_. Valor? vl_. Quantidade? qt_. Nome? nm_. Texto livre? ds_. Verdadeiro/falso? fl_. Valor de lista controlada? tp_ (tipo) ou cd_ (código de domínio ou externo). Sigla? sg_. JSON? js_. Geometria? gm_. Se nenhuma resposta servir, a coluna fica sem prefixo.

Proibido:

  • Abreviações não óbvias (usar municipio, não mun)
  • Prefixos de entidade/tabela de origem herdados do legado (ex.: CidNome, PesCpf) — não confundir com os prefixos de tipo de dado da Seção 2.1.1, que são obrigatórios
  • Nomes em inglês para entidades de negócio (usar português)
  • Mistura de idiomas no mesmo identificador ("Portinglês" — ex.: dt_birth_date, status_processo, fl_completed, order_numero): se o termo de negócio é em português, todo o identificador segue em português: prefixo de tipo (Seção 2.1.1), radical e eventuais sufixos
  • Acentuação e caracteres especiais (á, ç, ã, etc.) em nomes de schema, tabela, coluna, constraint ou índice — usar apenas [a-z0-9_] (ex.: situacao, nunca situação); a acentuação correta do termo de negócio vive no glossário do Knowledge Catalog (Seção 7.4) e em comentários (COMMENT ON), não no identificador
  • Nomes genéricos: dados, info, tabela, registro
  • Colunas com o mesmo nome da tabela: pessoa.pessoa_id → usar pessoa.id_pessoa

2.2 BigQuery​

ElementoPadrãoExemplo
Dataset{dominio}_{camada}pedidos_bronze, clientes_gold
Tabela fatofato_{processo}, no pluralfato_pedidos, fato_atendimentos
Tabela dimensãodim_{entidade}, no pluraldim_municipios, dim_clientes
Tabela raw/staging{sistema}_{tabela}_rawcrm_clientes_raw
Colunasnake_case, com prefixo de tipo (Seção 2.1.1) quando aplicável; colunas de auditoria usam os mesmos nomes do PG (Seção 7.1)vl_contrato, cd_ibge, dh_cadastro
ParticionamentoPreferir DATE(dh_cadastro) ou DATE(dh_evento), conforme a coluna de data/hora disponível na tabela—
ClusteringColuna mais filtrada após a partiçãoid_municipio, id_programa

3. Colunas Obrigatórias em Toda Tabela​

Toda tabela de domínio e de dados mestres criada no PostgreSQL deve conter as colunas abaixo. Elas garantem rastreabilidade, auditoria e soft delete. Ficam fora desta regra as tabelas de log e de eventos, que são imutáveis (sem UPDATE nem DELETE para as roles de aplicação, ex.: auditoria.log_operacao) e dispensam as colunas de auditoria e de soft delete, e as tabelas de staging, que podem usar DELETE físico (Doc 04, Seção 3.1).

3.1 Identidade​

id_{tabela} UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY

UUIDs são obrigatórios como PKs. Nunca usar SERIAL ou BIGSERIAL como chave primária em tabelas de domínio. Sequências numéricas podem ser usadas como chaves de negócio secundárias quando necessário (ex.: número de processo).

A PK usa UUIDv7 (uuidv7(), nativo a partir do PostgreSQL 18): ordenável por tempo, reduz a fragmentação de índices. gen_random_uuid() (UUIDv4) não é usado como DEFAULT de PK.

Escopo: esta regra vale para as tabelas de domínio criadas pelo time; objetos criados por extensões do SGBD e pela ferramenta de migration estão fora do escopo. Em tabelas particionadas, a PK é composta por id_{tabela} e a coluna de partição (Seção 6.5).

3.2 Auditoria​

dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW()
id_usuario_cadastro UUID NOT NULL -- id_usuario dos Sistemas do DTIC
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW()
id_usuario_atualizacao UUID NOT NULL -- id_usuario dos Sistemas do DTIC

O campo dh_atualizacao deve ser mantido automaticamente via trigger (ver Seção 6).

3.3 Soft Delete​

fl_excluido BOOLEAN NOT NULL DEFAULT FALSE
dh_exclusao TIMESTAMPTZ
id_usuario_exclusao UUID

A flag e a data não podem divergir: toda tabela com soft delete leva CONSTRAINT ck_{entidade}_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL)).

O filtro fl_excluido = FALSE não é repetido à mão em cada consulta. A aplicação lê tabelas com soft delete por uma view que o aplica (vw_{descricao}, listando as colunas) ou pelo escopo padrão da ORM (ex.: paranoid do Sequelize, mapeado para dh_exclusao):

CREATE VIEW vw_processos_ativos AS
SELECT id_processo, nr_processo, dh_cadastro -- demais colunas de negócio
FROM processos
WHERE fl_excluido = FALSE;

Nunca usar DELETE físico em tabelas de domínio (a role da aplicação não recebe o privilégio DELETE nelas). O registro é marcado com fl_excluido = TRUE.

LGPD: soft delete não equivale a eliminação. Dado pessoal deve ter prazo de guarda definido e, ao fim da finalidade ou a pedido do titular (LGPD, art. 18), passar por procedimento auditado de eliminação ou anonimização das colunas de PII. Em tabelas com soft delete, a unicidade de código de negócio usa índice único parcial (WHERE fl_excluido = FALSE) — ver Seção 6.4.

Histórico de exclusões de pessoas (mdm.pessoas_exclusoes): tabela satélite (REG-CM-05, Doc 06) que registra cada exclusão e reativação de uma pessoa, para consulta administrativa e auditoria:

CREATE TABLE mdm.pessoas_exclusoes (
id_pessoa UUID NOT NULL REFERENCES mdm.pessoas (id_pessoa) ON DELETE RESTRICT,
dh_evento TIMESTAMPTZ NOT NULL,
tp_evento VARCHAR(20) NOT NULL CHECK (tp_evento IN ('EXCLUSAO','REATIVACAO')),
id_usuario_evento UUID NOT NULL,
PRIMARY KEY (id_pessoa, dh_evento) -- composta: permite múltiplos ciclos de exclusão/reativação da mesma pessoa
);
  • id_pessoa continua sendo a chave primária de mdm.pessoas; o soft delete (fl_excluido = TRUE) continua sendo o mecanismo operacional e nenhuma linha é removida fisicamente, preservando as FKs de processos, atendimentos, documentos etc.
  • A tabela é populada por trigger AFTER UPDATE quando fl_excluido muda de valor e é imutável: sem UPDATE/DELETE para as roles de aplicação, o que dispensa as colunas de auditoria e de soft delete.
  • Não guarda CPF nem outro dado pessoal: os dados são obtidos por join com mdm.pessoas, que nunca é apagada fisicamente. Assim não há cópia de dado mestre (Seção 11) nem de PII.
  • A unicidade de CPF continua garantida pelo índice único parcial de mdm.pessoas (Seção 6.4, WHERE fl_excluido = FALSE).
  • Escopo exclusivo de pessoas. Outras entidades com chave de negócio única (CNPJ, matrícula de imóvel etc.) usam apenas o índice único parcial (Seções 3.3 e 6.4).

3.4 Classificação LGPD (obrigatória em colunas sensíveis)​

Toda coluna que contenha dado pessoal deve ter um comentário de classificação aplicado via COMMENT ON COLUMN:

COMMENT ON COLUMN pessoas.nr_cpf IS 'LGPD:RESTRITO — CPF do titular. Mascarar em HOM. Não replicar para BQ sem pseudonimização.';
COMMENT ON COLUMN usuarios.ds_senha_hash IS 'LGPD:SIGILOSO — Hash de senha. Nunca replicar para BQ ou HOM.';
ClassificaçãoExemplosRegra
PÚBLICONome do empreendimento, endereço de núcleoPode replicar livremente
INTERNODados operacionais sem PIIReplicar apenas entre ambientes internos
RESTRITOCPF, RG, CNPJ (a coluna também guarda CNPJ de empresário individual, que identifica pessoa natural), renda, endereço residencial, data de nascimento, dados socioeconômicosMascarar em HOM; pseudonimizar no BQ
SIGILOSOSenha (mesmo hash), JWT, dados de saúdeNunca replicar; nunca expor em BQ

4. Versionamento de Schema — Migrations​

4.1 Mecanismo​

Não há ferramenta de migration definida. Toda mudança de schema é um script .sql versionado na pasta db/migration/ do repositório institucional dos Sistemas do DTIC e aplicado em ordem de versão, conforme as regras das Seções 4.2 a 4.4.

4.2 Nomenclatura de arquivos​

V{versao}__{descricao}.sql ← migration versionada (executa uma vez)
R__{objeto}.sql ← migration repetível (views, SPs, funções)

Exemplos:

V001__create_schema_dominio.sql
V002__create_table_pessoas.sql
V003__add_column_pessoas_tipo.sql
R__vw_pessoas_ativas.sql

Documentos de documentação seguem o padrão {NN}_{assunto}.{ext}:

  • NN com 2 dígitos, na ordem da sequência de leitura (01_, 02_, ...).
  • snake_case em minúsculas, sem acento, sem espaço e sem redundância como documento_de (ex.: 03_modelo_corporativo_dados.md).
  • A versão fica no cabeçalho do documento, nunca no nome do arquivo (nada de (1) ou _v2).
  • O PDF de um documento tem o mesmo nome do Markdown, só muda a extensão.
  • Todo documento tem um código HORUS-{ÁREA}-{SIGLA}-{NNN} no cabeçalho, que não muda entre versões: HORUS-ARQ-DF-001 (01 Data Foundation), HORUS-REQ-FUN-001 (02 Requisitos Funcionais), HORUS-DAT-MCD-001 (03 Modelo Corporativo de Dados), HORUS-DAT-GOV-001 (04 Governança PostgreSQL), HORUS-DAT-PRO-001 (05 Procedimento de Modelagem), HORUS-ARQ-CM-001 (06 Cadastro Mestre). Documento novo recebe o próximo NNN do seu ÁREA-SIGLA e o código entra nesta lista.

4.3 Regras de escrita de migrations​

  • Um migration = uma mudança lógica. Não agrupar CREATE TABLE + ALTER TABLE + INSERT em um único arquivo.
  • Migrations são imutáveis após merge para PROD. Nunca editar um arquivo V* já executado. Para corrigir, criar um novo V{n+1}.
  • Migrations devem ser reversíveis quando possível. Documentar no comentário do arquivo o rollback equivalente.
  • Migrations com dados iniciais usam prefixo V{n}__seed_{tabela}.sql e são separados dos DDL.
  • Nunca usar DROP TABLE ou DROP COLUMN sem aprovação formal (Seção 1.2).
  • Sem DDL manual em HOM/PROD fora da ferramenta de migration. DDL emergencial exige migration retroativa registrada.
  • Mudança sem parada: renomear ou remover coluna em uso segue o padrão expandir–migrar–contrair (adicionar a nova estrutura → migrar dados e uso → remover a antiga em migration posterior).
  • Bloqueios: em tabelas grandes, definir lock_timeout e statement_timeout no início da migration; criar índices com CREATE INDEX CONCURRENTLY (em migration própria, fora de transação) e constraints em tabelas com dados com NOT VALID seguido de VALIDATE CONSTRAINT.

4.4 Rollback​

PostgreSQL suporta DDL transacional — CREATE TABLE, ALTER TABLE, DROP COLUMN etc. podem rodar dentro de BEGIN ... ROLLBACK e serem desfeitos como qualquer outra transação, diferente da maioria dos SGBDs relacionais. A exceção são comandos que não podem rodar dentro de transação, como CREATE INDEX CONCURRENTLY e VACUUM (ver Seção 4.3) — para esses, não há rollback automático possível. Ainda assim, para cada migration de risco (remoção de coluna, alteração de tipo, ou qualquer comando fora de transação), deve existir um script de rollback documentado no PR, já que a ferramenta de migration não reverte automaticamente uma migration já aplicada em PROD (Seção 4.3).


5. Fluxo HOM → PROD​

5.1 Ambientes​

AmbienteProjeto GCPBanco PGPropósito
HOM{projeto}-homPostgreSQL HOMDesenvolvimento, testes de integração, homologação
PROD{projeto}-prodPostgreSQL PRODProdução — dados reais

5.2 Checklist de promoção HOM → PROD​

Antes de promover qualquer mudança de schema para PROD, verificar:

  • Migration executada com sucesso em HOM sem erros
  • Testes de regressão passando em HOM (smoke test mínimo)
  • Colunas com PII têm comentário LGPD aplicado
  • Índices de performance criados para colunas de filtro frequente
  • View ou query de validação executada confirmando integridade dos dados
  • PR aprovado por Arquiteto de Dados + Gestor do Domínio + Tech Lead
  • Script de rollback documentado no PR para mudanças destrutivas
  • Pipelines BQ ajustados se a mudança impacta colunas lidas por queries Bronze/Silver

5.3 Dados sensíveis em HOM​

  • HOM não deve conter CPF, RG, renda ou dados sigilosos reais.
  • Dados de PII em HOM devem ser sintéticos ou mascarados (substituição por dados fictícios plausíveis).
  • A replicação de dados de PROD para HOM deve passar por pipeline de mascaramento antes da carga.

6. Padrões de Implementação PostgreSQL​

6.1 Trigger de atualização automática de dh_atualizacao​

CREATE OR REPLACE FUNCTION fn_atualizar_dh_atualizacao()
RETURNS TRIGGER AS $$
BEGIN
NEW.dh_atualizacao = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Aplicar em cada tabela de domínio:
CREATE TRIGGER tg_before_pessoas_update
BEFORE UPDATE ON pessoas
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();

6.2 UUID padrão​

-- Usar em colunas de PK (UUIDv7, nativo a partir do PostgreSQL 18):
id_pessoa UUID NOT NULL DEFAULT uuidv7()

uuidv7() é nativo a partir do PostgreSQL 18 e não depende de extensão. Valores gerados fora do banco (aplicação, ETL) também devem ser UUIDv7, para manter a ordenação temporal dos índices.

6.3 Enum tipado vs. tabela de domínio​

CasoUsar
Valores fixos, raramente mudam, < 10 opçõesENUM do PostgreSQL
Valores que crescem com o tempo, gerenciados pela área de negócioTabela de domínio (tipos_{entidades}, no plural)

Convenção de valores: tanto os rótulos de ENUM quanto o cd_tipo de tabela de domínio usam UPPER_SNAKE_CASE em português, sem acento (ex.: RASCUNHO, EM_ANALISE, HOMOLOGADO, CANCELADO). Não misturar convenções de estado entre domínios (ex.: PENDING com FINALIZADO).

Exemplo de tabela de domínio:

CREATE TABLE tipos_pessoas (
id_tipo_pessoa UUID PRIMARY KEY DEFAULT uuidv7(),
cd_tipo VARCHAR(30) NOT NULL UNIQUE, -- ex.: 'BENEFICIARIO', 'SERVIDOR'
ds_tipo TEXT NOT NULL,
fl_ativo BOOLEAN NOT NULL DEFAULT TRUE
);

6.4 Constraints de integridade​

  • Toda FK deve ter ON DELETE RESTRICT por padrão. Usar CASCADE apenas com aprovação explícita e documentação do motivo.
  • Campos obrigatórios de negócio devem ser NOT NULL no banco — não confiar somente na validação da aplicação.
  • Campos de código de negócio (CPF, CNPJ, código IBGE) devem ter constraint UNIQUE. Em tabelas com soft delete, usar índice único parcial (CREATE UNIQUE INDEX uq_… ON … (coluna) WHERE fl_excluido = FALSE). Duplicidades pré-existentes devem ser saneadas, com decisão registrada, antes de criar a restrição.
  • Toda coluna que referencia outra entidade (id_{entidade}) deve ter constraint de FK. Exceção: id_usuario_cadastro, id_usuario_atualizacao, id_usuario_exclusao (referem o usuário da aplicação, autenticado por IdP externo) e os identificadores de origem id_origem_externa e id_legado. Referência a entidade mestre hospedada em outro banco é FK lógica, validada pela API de Cadastro Mestre do sistema dono, e mantém o índice (Doc 06, REG-CM-18). Coluna legada mantida apenas como histórico usa o sufixo _legado e tem prazo de remoção definido.
  • Toda coluna de FK deve ter índice — o PostgreSQL não o cria automaticamente na coluna referenciadora.
  • Todo instante de tempo usa TIMESTAMPTZ; timestamp sem fuso não é permitido. Regras de formato e faixa de valores devem ser expressas com CHECK.

6.5 Volumetria, particionamento e retenção​

  • Tabelas de eventos, logs e históricos, com crescimento contínuo no tempo, devem ter política de retenção definida na criação.
  • Quando o crescimento previsto justificar — ordem de dezenas de GB ou de dezenas de milhões de linhas, índices que deixam de caber em memória, ou expurgo periódico por data — usar particionamento declarativo (PARTITION BY RANGE sobre coluna de data), com partições criadas com antecedência e expurgo por DETACH/DROP de partição. Abaixo desse porte, índices adequados e manutenção regular são preferíveis: particionar tem custo operacional.
  • Em tabela particionada, a PK deve incluir a coluna de partição (limitação do PostgreSQL) e as FKs para ela são lógicas. Nesse caso, a PK é composta por id_{tabela} e pela coluna de partição.
  • O crescimento por tabela é acompanhado periodicamente; a decisão de particionar é do Arquiteto de Dados com o Tech Lead.

7. Modelagem BigQuery — Padrões Adicionais​

7.1 Camadas e responsabilidades​

CamadaResponsabilidadeQuem escreve
BronzeDado bruto da fonte, sem transformação. Schema espelha o PG operacional.Pipeline CDC / scheduled query
SilverLimpeza, tipagem, deduplicação, joins resolvidos. Sem lógica de negócio.Job de transformação (Dataform ou SQL schedulado)
GoldFatos e dimensões prontos para analytics. Lógica de negócio aplicada.Job de transformação com aprovação do Arquiteto

7.2 Proibições no BQ​

  • Nunca escrever dados de PROD diretamente no BQ de HOM sem mascaramento de PII.
  • Nunca criar tabelas com nomes genéricos (dados_municipio_v2, teste_final). Toda tabela em produção deve seguir a nomenclatura fato_* ou dim_* ou {sistema}_{tabela}_raw.
  • Nunca usar SELECT * em views ou queries Gold — listar colunas explicitamente.

7.3 Documentação de tabelas BQ​

Toda tabela Gold deve ter description definida no schema:

-- Exemplo via DDL BQ:
CREATE TABLE `{projeto}.{dominio}_gold.dim_municipios`
OPTIONS (description = 'Dimensão município canônica. Fonte: IBGE. Chave: cd_ibge. Dono: time de dados.')
AS ...

7.4 Classificação e Descoberta via Knowledge Catalog​

O Google Cloud Knowledge Catalog é a ferramenta institucional padrão de descoberta, classificação e metadados — usada tanto no BigQuery quanto no PostgreSQL/Cloud SQL (ver Doc 04, Seção 8.3).

  • Tag Templates de PII: toda coluna que armazene dados pessoais (classificação RESTRITO ou SIGILOSO — Seção 3.4) deve ser mapeada e tagueada via Knowledge Catalog.
  • Policy Tags: o acesso a colunas PII no BigQuery é restringido via Data Policies nativas do BigQuery, amarradas à taxonomia do Knowledge Catalog, garantindo mascaramento para perfis de menor privilégio (IAM).
  • Indicadores de qualidade e confiança: o Knowledge Catalog também classifica a confiabilidade do dado, sinalizando campos que requerem revisão ou estão com baixa confiança.
  • Glossário de termos corporativos: o dicionário de termos permitidos e sinônimos (ex.: definir se o sistema usa municipio ou cidade, bairro ou distrito) é mantido no Knowledge Catalog, que é a fonte de verdade; o Doc 03, Seção 7, apenas o espelha. Consultar o Knowledge Catalog antes de nomear uma entidade nova é parte do rito da Seção 11.

8. Responsabilidades​

PapelResponsabilidade
Arquiteto de DadosAprovação de mudanças estruturais; guardião do modelo de dados institucional; revisão de nomenclatura e LGPD; execução das migrations em PROD; operação do banco e dos pipelines de ETL, incluindo monitoramento de conexões e storage (Doc 04)
Tech LeadAprovação de performance e revisão de código das migrations
Gestor do DomínioValidação de regras de negócio; aprovação de mudanças no domínio sob sua responsabilidade
DesenvolvedorProposta e implementação de mudanças; escrita dos migrations; documentação no PR
Time de DadosManutenção dos pipelines BQ; promoção HOM → PROD no BQ; gestão das tabelas de dados mestres externos (IBGE, SEADE)

9. Checklist de Revisão de Modelagem​

Use este checklist ao revisar qualquer proposta de nova tabela ou mudança significativa. Sempre que possível, automatize estas verificações sobre o catálogo do banco (em CI e periodicamente): revisão apenas manual não escala.

Nomenclatura

  • Nome da tabela no plural, snake_case, em português
  • Colunas que se enquadram em um termo do catálogo usam o prefixo correspondente; as que não se enquadram ficam sem prefixo (dt_, dh_, nr_, vl_, qt_, ds_, fl_, tp_, sg_ — Seção 2.1.1)
  • Colunas sem prefixo de entidade/tabela herdado do legado (padrão de sistema anterior não migrado)
  • Sem mistura de idioma no mesmo identificador (Portinglês) e sem acentuação/caracteres especiais em schema, tabela, coluna, constraint ou índice (Seção 2.1)
  • FK nomeada como id_{entidade_referenciada}
  • Índices nomeados com padrão idx_ ou uq_
  • Valores de ENUM/cd_tipo de tabela de domínio em UPPER_SNAKE_CASE, português, sem acento (Seção 6.3)

Estrutura

  • PK é UUID com DEFAULT uuidv7()
  • Colunas de auditoria presentes: dh_cadastro, id_usuario_cadastro, dh_atualizacao, id_usuario_atualizacao
  • Soft delete presente (tabela de domínio ou mestre): fl_excluido, dh_exclusao, id_usuario_exclusao, com CHECK de coerência entre fl_excluido e dh_exclusao
  • Trigger de dh_atualizacao criado

Tabela mestre (schema mdm) — regras completas no Doc 06

  • Tabela criada no schema mdm (nunca em public ou em schema de domínio) e sem duplicar entidade mestre existente (REG-CM-04)
  • Linhagem presente: tp_origem_cadastro, dh_sincronizacao_fonte, id_origem_externa (REG-CM-14)
  • Situação cadastral em cd_situacao com CHECK de domínio; sem status, fl_ativo ou is_active (REG-CM-07 e 10)
  • Papéis contextuais em tabela associativa do módulo, não como coluna da tabela mestre (REG-CM-11)
  • Trigger auditoria.fn_registrar_operacao aplicada com a PK (REG-CM-16)
  • Ficha arquitetural de módulo anexada à proposta (Doc 06, Seção 8)

LGPD

  • Colunas com PII identificadas e classificadas com COMMENT ON COLUMN
  • Colunas com PII classificadas com o prefixo LGPD:RESTRITO ou LGPD:SIGILOSO no comentário, pois é ele que define o mascaramento no log de auditoria (Doc 04, Seção 7.2)
  • Campos sigilosos (senha, JWT) ausentes da tabela (não devem existir no BQ e nem no modelo analítico)

Integridade

  • FKs com ON DELETE RESTRICT (ou justificativa para CASCADE)
  • Campos obrigatórios de negócio marcados NOT NULL
  • Campos de código único com constraint UNIQUE (índice único parcial WHERE fl_excluido = FALSE em tabelas com soft delete — Seção 6.4)
  • FK entre tabelas aponta sempre para a PK UUID (id_{entidade}), nunca para chave de negócio (nr_cpf, nr_cnpj, cd_ibge, nr_processo)
  • Tabela migrada de legado: coluna id_legado com acesso restrito e plano de expurgo (Doc 04, Seção 6.4)
  • Toda coluna id_{entidade} que referencia outra tabela tem FK e índice (exceto id_usuario_*, id_origem_externa, id_legado e FK lógica entre bancos, Doc 06, REG-CM-18)
  • Instantes de tempo em TIMESTAMPTZ
  • Tabela de crescimento contínuo tem política de retenção e avaliação de particionamento (Seção 6.5)

Documentação

  • Migration em arquivo separado com nome descritivo
  • Script de rollback documentado (para mudanças destrutivas)
  • Entidade adicionada ao modelo corporativo de dados institucional, quando aplicável ao sistema, se for nova

A automação do checklist tem exemplo no Apêndice A.


10. Diretrizes de ETL/ELT​

Esta seção orienta o time na escolha e operação dos motores de ETL/ELT usados para ingestão e transformação de dados públicos, dentro do mesmo padrão institucional dos Sistemas do DTIC (100% GCP, sem infraestrutura própria a manter).

10.1 Critério de escolha da ferramenta​

Regra geral: nenhuma ferramenta de ETL/ELT, orquestração ou streaming fora do GCP é permitida. Toda a esteira de dados é construída exclusivamente com serviços gerenciados GCP, listados abaixo — não há avaliação caso a caso de ferramenta de terceiros ou auto-hospedada fora desta lista.

NecessidadeFerramenta GCP a usarObservação
Transformação em SQL (Silver/Gold)BigQuery Scheduled Queries ou Dataform (GCP)Sem orquestrador dedicado quando a lógica for majoritariamente SQL e não depender de outras etapas — mesmo padrão da Seção 7.1
Ingestão/transformação em código (Python)Cloud Run Jobs (contêiner)Job agendado ou disparado por evento; sem servidor de longa duração dedicado ao ETL
Rotinas reativas curtas (reconciliações, verificações entre cargas)Cloud FunctionsDisparada por evento ou agendamento, sem servidor dedicado; falha gera alerta (Seção 10.3)
Orquestração de pipeline com múltiplas etapas dependentesCloud Composer (Apache Airflow gerenciado)Único orquestrador permitido; ver regra de uso ponta a ponta abaixo
Processamento em streaming (quando necessário)Dataflow (Apache Beam), alimentado por Pub/SubUsar apenas quando o requisito for latência baixa/contínua; caso contrário, preferir processamento em lote (Seção 10.3)
Mensageria/eventosPub/SubÚnico serviço de mensageria permitido
  • Uso do Airflow (Cloud Composer) é permitido para orquestrar as transformações Silver/Gold, desde que o DAG cubra o processo de ponta a ponta — da origem do dado (extração) até o destino final (tabela Gold consumível) — dentro do mesmo pipeline. Não é permitido usar o Airflow para orquestrar apenas uma etapa intermediária e deixar as demais soltas em agendamentos paralelos e desconexos: se há dependência entre extração, Bronze, Silver e Gold, o DAG único do Airflow é responsável pela cadeia inteira.
  • Quando a transformação Silver/Gold não tiver dependência de etapas anteriores dentro do mesmo pipeline (ex.: uma consulta agendada isolada, sem orquestração de extração), não é necessário Airflow — usar BigQuery Scheduled Query ou Dataform diretamente, conforme a tabela acima.
  • Antes de adicionar um novo DAG/job, avaliar se a necessidade já é coberta por uma peça já existente na lista acima — reduzir o número de ferramentas distintas reduz custo de manutenção e superfície de atualização.

10.2 Atualização e versionamento dos pipelines​

  • Scripts e definições de pipeline (SQL, jobs, arquivos de configuração de agendamento) são versionados no mesmo repositório do código, revisados pelo mesmo rito de PR aplicado às migrations (Seção 4).
  • Atualização de versão de imagem/dependência dos jobs de ETL segue o mesmo processo de verificação de vulnerabilidade (SAST/Trivy) exigido para qualquer outro serviço em contêiner.
  • Mudanças que alterem o schema de destino (novas colunas, novas tabelas) seguem o mesmo fluxo de aprovação de mudança de schema (Seção 1) — o pipeline de ETL não cria estrutura de tabela por conta própria fora desse rito.

10.3 Janela e frequência de processamento​

  • Cargas incrementais são preferíveis a reprocessamento completo: usar marca d'água (watermark) de data/hora (dh_atualizacao de origem) ou CDC para carregar apenas o que mudou, reduzindo tempo de execução e custo de processamento.
  • Toda falha de execução do pipeline deve gerar alerta nos padrões de observabilidade institucionais (Cloud Logging/Monitoring) — silenciar falha de ETL não é aceitável.

10.4 Recomendação de Melhores Práticas de DataOps​

  • Consultas de transformação (BigQuery) devem filtrar por partição e evitar SELECT *, controlando custo de processamento por consulta.
  • Preferir agendamento nativo (consulta agendada, job de contêiner agendado) a manter um serviço em execução contínua apenas esperando o próximo disparo.
  • Não é permitida a adoção de ferramenta de ETL/ELT, orquestração, streaming ou mensageria fora da lista da Seção 10.1 (fora do GCP). Qualquer necessidade não coberta pela lista é levada ao Comitê de Dados para avaliação de qual serviço GCP gerenciado deve supri-la — não para aprovação de ferramenta externa (Doc 04, Seção 8).

11. Reuso de Dados Mestres Já Existentes​

Antes de propor uma nova tabela, toda equipe deve verificar se a entidade já existe no modelo corporativo de dados institucional dos Sistemas do DTIC, quando aplicável ao sistema (ver Modelo Corporativo de Dados). Isso vale especialmente para dados de referência e cadastros mestres (ex.: dados territoriais, cadastrais ou outros dados compartilhados entre domínios), independentemente de a equipe proponente conhecer previamente todo o modelo já existente.

  • É responsabilidade de quem propõe a mudança consultar o Modelo Corporativo de Dados e, em caso de dúvida sobre a existência de uma entidade equivalente, confirmar com o Arquiteto de Dados antes de seguir com a modelagem — não presumir que o dado ainda não existe apenas por falta de acesso ou conhecimento prévio do modelo.
  • A ausência confirmada de uma entidade equivalente é o único cenário em que a criação de nova tabela é justificável — e, mesmo assim, segue o rito normal de aprovação (Seção 1).

Regra de fonte única da verdade: os Sistemas do DTIC operam com uma única fonte da verdade por entidade de dado mestre. É proibida a duplicação de dados já existentes no modelo corporativo em qualquer sistema ou domínio — tabelas próprias que repliquem um dado mestre já modelado não são permitidas, mesmo como cópia local, cache persistente ou tabela de apoio. O consumo do dado mestre de outro banco pela API de Cadastro Mestre (Doc 06, REG-CM-17) respeita essa regra, pois não há cópia local.



Apêndice A — Exemplo de automação do checklist​

Exemplo ilustrativo, não um artefato em uso. Uma consulta sobre o catálogo do PostgreSQL devolve uma linha por violação das regras verificáveis por máquina, e um workflow de CI falha o PR enquanto houver linha. Salvar a consulta como db/checks/verificar_modelagem.sql.

Cobertura: PK UUID com uuidv7(), colunas obrigatórias de auditoria e soft delete, prefixo obrigatório por tipo (booleano, data, data e hora, JSON), prefixo de objeto usado em coluna, identificadores sem acento ou maiúscula, FK com índice e com ON DELETE RESTRICT, TIMESTAMPTZ, trigger de dh_atualizacao, comentário LGPD: em colunas de PII conhecidas, UNIQUE clássico em tabela com soft delete e CHECK de coerência do soft delete. Tabelas de log e de staging que não seguem as colunas obrigatórias (ex.: auditoria.log_operacao) devem ficar fora do escopo, ajustando a lista de schemas e tabelas excluídas no início da consulta. Seguem para revisão manual: plural, idioma, qualidade semântica do nome, identificação de PII não listada e adequação de índices.

WITH t AS (
SELECT c.oid, n.nspname, c.relname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'p')
AND NOT c.relispartition
AND n.nspname !~ '^pg_'
AND n.nspname NOT IN ('information_schema', 'auditoria')
AND c.relname <> 'schema_migrations'
AND c.relname !~ '_(exclusoes|fusoes)$' -- tabelas de histórico imutáveis
),
col AS (
SELECT t.oid, t.nspname, t.relname, a.attnum, a.attname, a.atttypid
FROM t
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum > 0 AND NOT a.attisdropped
)
-- 1. PK deve ser UUID com DEFAULT uuidv7()
SELECT 'PK_UUIDV7' AS regra, format('%I.%I', t.nspname, t.relname) AS objeto
FROM t
LEFT JOIN pg_index i ON i.indrelid = t.oid AND i.indisprimary
LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY (i.indkey)
LEFT JOIN pg_attrdef d ON d.adrelid = t.oid AND d.adnum = a.attnum
WHERE i.indexrelid IS NULL
OR a.atttypid <> 'uuid'::regtype
OR COALESCE(pg_get_expr(d.adbin, d.adrelid), '') NOT LIKE '%uuidv7()%'
UNION ALL
-- 2. Colunas obrigatórias de auditoria e soft delete
SELECT 'COLUNA_OBRIGATORIA: ' || r.nm, format('%I.%I', t.nspname, t.relname)
FROM t
CROSS JOIN unnest(ARRAY['dh_cadastro', 'id_usuario_cadastro', 'dh_atualizacao',
'id_usuario_atualizacao', 'fl_excluido', 'dh_exclusao',
'id_usuario_exclusao']) AS r(nm)
WHERE NOT EXISTS (SELECT 1 FROM col WHERE col.oid = t.oid AND col.attname = r.nm)
UNION ALL
-- 3. Prefixo obrigatório pelo tipo e prefixo de objeto usado em coluna
SELECT 'PREFIXO_COLUNA', format('%I.%I.%I', nspname, relname, attname)
FROM col
WHERE (atttypid = 'boolean'::regtype AND attname !~ '^fl_')
OR (atttypid = 'date'::regtype AND attname !~ '^dt_')
OR (atttypid = 'timestamptz'::regtype AND attname !~ '^dh_')
OR (atttypid = 'jsonb'::regtype AND attname !~ '^js_')
OR attname ~ '^(idx|uq|fk|fn|tg|seq)_'
UNION ALL
-- 4. Identificadores só com [a-z0-9_] (sem acento nem maiúscula)
SELECT 'IDENTIFICADOR_INVALIDO', format('%I.%I', nspname, relname)
FROM t
WHERE relname !~ '^[a-z0-9_]+$' OR nspname !~ '^[a-z0-9_]+$'
UNION ALL
SELECT 'IDENTIFICADOR_INVALIDO', format('%I.%I.%I', nspname, relname, attname)
FROM col
WHERE attname !~ '^[a-z0-9_]+$'
UNION ALL
-- 5. FK sem índice na coluna referenciadora
SELECT 'FK_SEM_INDICE', format('%I.%I (%s)', t.nspname, t.relname, con.conname)
FROM t
JOIN pg_constraint con ON con.conrelid = t.oid AND con.contype = 'f'
WHERE NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = t.oid
AND (i.indkey::int2[])[0:cardinality(con.conkey) - 1] = con.conkey)
UNION ALL
-- 6. FK deve ser ON DELETE RESTRICT
SELECT 'FK_NAO_RESTRICT', format('%I.%I (%s)', t.nspname, t.relname, con.conname)
FROM t
JOIN pg_constraint con ON con.conrelid = t.oid AND con.contype = 'f'
WHERE con.confdeltype <> 'r'
UNION ALL
-- 7. Instante de tempo deve ser TIMESTAMPTZ
SELECT 'TIMESTAMP_SEM_FUSO', format('%I.%I.%I', nspname, relname, attname)
FROM col
WHERE atttypid = 'timestamp'::regtype
UNION ALL
-- 8. Tabela sem trigger de dh_atualizacao
SELECT 'TRIGGER_DH_ATUALIZACAO', format('%I.%I', t.nspname, t.relname)
FROM t
WHERE NOT EXISTS (
SELECT 1 FROM pg_trigger tg
JOIN pg_proc p ON p.oid = tg.tgfoid
WHERE tg.tgrelid = t.oid AND NOT tg.tgisinternal
AND p.proname = 'fn_atualizar_dh_atualizacao')
UNION ALL
-- 9. Coluna de PII conhecida sem comentário LGPD
SELECT 'LGPD_SEM_COMENTARIO', format('%I.%I.%I', nspname, relname, attname)
FROM col
WHERE attname IN ('nr_cpf', 'nr_cnpj', 'nr_rg', 'nm_completo', 'nm_social', 'nm_mae', 'dt_nascimento')
AND COALESCE(col_description(oid, attnum), '') NOT LIKE 'LGPD:%'
UNION ALL
-- 10. UNIQUE clássico em tabela com soft delete (usar índice único parcial)
SELECT 'UNIQUE_COM_SOFT_DELETE', format('%I.%I (%s)', t.nspname, t.relname, con.conname)
FROM t
JOIN pg_constraint con ON con.conrelid = t.oid AND con.contype = 'u'
WHERE EXISTS (SELECT 1 FROM col WHERE col.oid = t.oid AND col.attname = 'fl_excluido')
UNION ALL
-- 11. Soft delete sem CHECK de coerência entre fl_excluido e dh_exclusao
SELECT 'CHECK_SOFT_DELETE', format('%I.%I', t.nspname, t.relname)
FROM t
WHERE EXISTS (SELECT 1 FROM col WHERE col.oid = t.oid AND col.attname = 'fl_excluido')
AND NOT EXISTS (
SELECT 1 FROM pg_constraint con
WHERE con.conrelid = t.oid AND con.contype = 'c'
AND pg_get_constraintdef(con.oid) LIKE '%fl_excluido%'
AND pg_get_constraintdef(con.oid) LIKE '%dh_exclusao%')
ORDER BY 1, 2;

Workflow de exemplo (GitHub Actions), aplicando as migrations em um PostgreSQL descartável e rodando a consulta:

name: verificar-modelagem
on:
pull_request:
paths: ['db/migration/**', 'db/checks/**']
jobs:
checks:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
env: { POSTGRES_PASSWORD: ci }
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-retries 10
env:
PGHOST: localhost
PGUSER: postgres
PGPASSWORD: ci
steps:
- uses: actions/checkout@v4
- name: Aplicar migrations
run: |
for f in $(ls db/migration/V*.sql | sort -V); do
psql -v ON_ERROR_STOP=1 -f "$f"
done
- name: Verificar regras de modelagem
run: |
psql -At -F ' | ' -f db/checks/verificar_modelagem.sql > violacoes.txt
cat violacoes.txt
test ! -s violacoes.txt