SQL

PostgreSQL JSONB: consulte dados aninhados e escolha o índice GIN certo

PG Monitoring Team July 11, 2026 9 min de leitura

JSONB é útil para atributos que realmente variam entre linhas: payloads de webhook, metadados de produto, configurações de integração ou propriedades de eventos. Não é uma desculpa para esconder um modelo relacional em uma coluna. A diferença aparece quando a tabela cresce e cada predicado JSON vira um sequential scan.

Dados de exemplo

CREATE TABLE events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now(),
  payload jsonb NOT NULL
);

INSERT INTO events (payload) VALUES
('{"type":"payment","amount":89.90,"customer":{"country":"BR"},"tags":["vip","mobile"]}'),
('{"type":"signup","customer":{"country":"US"},"tags":["web"]}');

Leia valores simples e aninhados

-- Valor JSON
SELECT payload -> 'customer' AS customer
FROM events;

-- Valor texto, adequado para comparação
SELECT id, payload ->> 'type' AS event_type
FROM events
WHERE payload ->> 'type' = 'payment';

-- Valor texto aninhado
SELECT id
FROM events
WHERE payload #>> '{customer,country}' = 'BR';

Use -> quando a próxima operação precisa de JSONB e ->> quando você precisa de texto. Misturar os dois é causa frequente de erros confusos de operador.

Containment: o melhor encaixe para um índice GIN geral

O operador de contenção @> pergunta se um documento contém um fragmento JSON. Ele combina naturalmente com um índice GIN de propósito geral.

CREATE INDEX events_payload_gin_idx
ON events USING gin (payload);

SELECT id, created_at
FROM events
WHERE payload @> '{"type":"payment","customer":{"country":"BR"}}';

Use EXPLAIN (ANALYZE, BUFFERS) depois de criar o índice. O PostgreSQL pode corretamente escolher sequential scan numa tabela pequena ou para um predicado pouco seletivo.

Índices de expressão para uma chave muito consultada

Quando uma chave JSON é filtrada o tempo todo, um expression index B-tree pequeno geralmente é melhor que um GIN geral grande.

CREATE INDEX events_type_idx
ON events ((payload ->> 'type'));

SELECT COUNT(*)
FROM events
WHERE payload ->> 'type' = 'payment';

Para uma chave aninhada, indexe exatamente a expressão usada pela query:

CREATE INDEX events_country_idx
ON events ((payload #>> '{customer,country}'));

SELECT id
FROM events
WHERE payload #>> '{customer,country}' = 'BR';

Arrays e existência

-- O documento possui uma chave no nível superior?
SELECT id FROM events WHERE payload ? 'amount';

-- tags contém "vip"?
SELECT id FROM events WHERE payload @> '{"tags":["vip"]}';

Um índice GIN padrão suporta esses operadores. Não suponha que ele acelera toda expressão JSONPath; valide o plano para o operador exato que você usa.

Quando JSONB é o modelo errado

  • Um valor é obrigatório em toda linha e participa de joins.
  • Você precisa de foreign keys, unicidade ou restrições de faixa nele.
  • Você o ordena ou agrega na maior parte das requisições.
  • A chave é central ao domínio do produto, não metadado incidental.

Nesses casos, modele o valor como coluna tipada. Mantenha atributos flexíveis e esparsos em JSONB e mova campos estáveis e muito acessados para colunas conforme a aplicação amadurece.

Custo de escrita: índices GIN aceleram leituras, mas adicionam trabalho a cada insert e update. Indexe somente caminhos JSONB que sua carga realmente consulta e monitore tamanho de índice e latência de escrita após o rollout.

O PG Monitoring ajuda mostrando quais predicados JSONB consomem tempo, se um índice mudou o plano e se a manutenção de índice está virando parte do gargalo de escrita.

Related Articles

Encontre esse problema antes que ele afete a produção

Monitore queries, capacidade, autovacuum, índices e replicação continuamente com contexto operacional.

Fale conosco