Dev & EngARTIGO

O jsonb pode ser maior e mais lento que o json no PostgreSQL, mostra benchmark

Um benchmark publicado no Planet PostgreSQL mediu tamanho em disco e velocidade de json e jsonb no PostgreSQL 17.11 e 18.6. O resultado desmonta a regra de bolso de que jsonb é sempre a escolha certa.

O jsonb pode ser maior e mais lento que o json no PostgreSQL, mostra benchmark
Imagem gerada por IA
Artigo · Dev & Eng
Leitura
9 min
Tabelas
1
Fontes
1

Um benchmark publicado no Planet PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → mediu tamanho em disco e velocidade de json e jsonb no PostgreSQL 17.11 e 18.6. O resultado desmonta a regra de bolso de que jsonb é sempre a escolha certa.

Um benchmark publicado no Planet PostgreSQL mediu tamanho em disco e velocidade de json e jsonb no PostgreSQL 17.11 e 18.6. O resultado desmonta a regra de bolso de que jsonb é sempre a escolha certa.

O que está em disputa e pra quem

Quem modela uma tabela no PostgreSQL com uma coluna de documento semiestruturado decide cedo entre json e jsonb. A resposta de manual é direta: use jsonb, porque ele indexa, compara e agrupa. Um benchmark publicado no Planet PostgreSQL, com medições feitas entre 3 e 6 de novembro de 2026 em contêineres descartáveis do PostgreSQL 17.11 e 18.6, complica essa resposta.

A equipe por trás do benchmark mediu tamanho em disco, velocidade de leitura, custo de COPY, atualização de chave e índice GIN em seis formatos de documento, dois formatos de entrada (compacto e com indentação) e dois métodos de compressão (pglz e lz4). O resultado: a diferença de tamanho foi de 22,6% menor a 40,9% maior conforme o documento, e não existe uma resposta única.

Esse recorte importa para quem arquiteta o esquema, não só para quem escreve a query. A decisão entre os dois tipos muda o plano de armazenamento, o comportamento do VACUUM, o tamanho do índice e até se a igualdade entre dois documentos existe. Vale para quem já tem jsonb em produção e nunca mediu, e para quem está desenhando um esquema novo e segue regra de bolso.

Critérios de comparação

Critériojsonjsonb
Tamanho em disco, docs pequenos e compactos (1M linhas, ~200 bytes)referência+17,8%
Tamanho em disco, docs pequenos com indentaçãoreferência−3,6%
Tamanho em disco, docs grandes e compactos (5 mil linhas, ~20 KB)referência+33,0% (pglz) / +22,2% (lz4)
Leitura de uma chave com ->>, sem índicereferência (mais lento)2,7 a 10,6x mais rápido
Índice de containment (@>)não suportado, exige castGIN nativo: jsonb_ops ou jsonb_path_ops
Atualização de uma chavesem json_set; exige cast ida e voltajsonb_set nativo, 1,3 a 3,4x mais rápido que o cast
Igualdade, GROUP BY, ORDER BY, chave únicanão suportadosuportado
Preservação do texto original (espaços, chaves duplicadas, ordem)mantém o texto exatoreescreve, descarta duplicatas, reordena
Custo de carga (COPY)mais rápido1,08 a 4,6x mais lento

Análise por critério

Tamanho em disco. A primeira camada, antes de qualquer compressão, é sempre maior em jsonb: o tipo guarda um cabeçalho por chave e valor, chaves ordenadas e números como numeric. Para os documentos pequenos isso significou 38,5 bytes a mais por linha, 18,5% de sobrecarga, e como esses documentos não passam do limite de compressão do TOAST (cerca de 2 KB, segundo a documentação do PostgreSQL), a diferença foi direto para o disco: tabelas 17,8% maiores.

Acima desse limite a conta vira. O documento médio do benchmark, uma ordem com cliente, endereço e sete itens, pesava 1.521,9 bytes como json compacto, abaixo do limiar, e ficou sem compressão. Como jsonb o mesmo documento pesava 2.071,9 bytes, passou do limiar, foi comprimido com pglz para 1.149,7 bytes, e a tabela terminou 16,6% menor que a de json. Um tipo 36% maior antes da compressão virou menor depois dela.

Esse comportamento explica por que dois números que circulam na comunidade para o mesmo assunto apontam direções opostas. Slides de uma palestra de 2015 na PGConf.Russia, de Alexander Korotkov, Oleg Bartunov e Teodor Sigaev, registram 1.374 MB em jsonb contra 1.322 MB em json para 1,25 milhão de registros, uma diferença de menos de 4%, dentro da faixa que o benchmark atual também mediu. Já um artigo republicado no BestHub em setembro de 2026 relata 888 MB contra 1.200 MB para um milhão de linhas, 26% menor.

A equipe rodou o gerador publicado nesse segundo artigo, com json.dumps chamado sem argumentos como está no texto original, e chegou ao resultado oposto: jsonb 6,7% maior, 653,5 contra 612,6 MB. A causa é que json.dumps sem argumentos escreve um espaço depois de cada vírgula e dois-pontos, e esse espaço some em jsonb. O ganho de 26% do artigo original não se reproduziu com o próprio script dele; a diferença estava no formato de entrada, não no tipo de coluna.

Leitura de uma chave. Aqui não há ambiguidade: jsonb venceu em toda linha medida, de 2,7 a 10,6 vezes mais rápido para extrair um valor com ->>, sem índice, somando sum(length(doc->>'first')) sobre a tabela inteira. A razão é estrutural: json guarda texto e precisa reanalisar a string a cada leitura; jsonb já guarda a árvore interpretada.

Quando o documento é comprimido, o método de compressão pesa tanto quanto o tipo. Ler uma chave dos documentos médios levou 310 ms com pglz e 96 ms com lz4; nos documentos "flat", de 780 chaves com strings aleatórias, 149 ms contra 42 ms. Antes de aplicar ->>, o PostgreSQL precisa descomprimir o valor inteiro, e lz4 descomprime mais rápido, só que ocupando mais espaço: 24,8% a 45,1% a mais que pglz nos documentos grandes e no array de inteiros.

Índice GIN e containment. Só jsonb tem índice de containment. Tentar CREATE INDEX ... USING gin (j) numa coluna json falha com o erro data type json has no default operator class for access method "gin", e o operador @> nem existe para json; o jeito é fazer cast na query, o que levou de 6,5 a 12,3 vezes mais tempo que consultar uma coluna jsonb já indexada.

Com o índice em jsonb, a busca por um valor raro, como {"tag":"r042"} entre um milhão de linhas, caiu de 236,4 ms sem índice para 0,74 ms com jsonb_path_ops. Para um valor comum, presente em seis em cada dez linhas, o índice não ajudou no PostgreSQL 17, que manteve a varredura sequencial. O índice também não é de graça: jsonb_ops ficou maior que a própria tabela na coleção pequena, 407,1 MB contra 292,9 MB, e jsonb_path_ops ficou menor e cerca de duas vezes mais rápido de construir, mas não suporta o operador ? de existência de chave.

Atualização. json não tem json_set; para mudar uma chave é preciso fazer cast para jsonb, aplicar jsonb_set e voltar para json, reescrevendo o texto no formato de jsonb de qualquer forma. Esse caminho levou 1,8 vez mais tempo que jsonb_set direto nos documentos pequenos, e a diferença cresceu para até 3,4 vezes com lz4 nos documentos grandes. Os dois tipos reescrevem o documento inteiro a cada UPDATE: mudar um status no documento grande gerou 7.947 bytes de WAL por linha, contra 231 bytes quando o mesmo valor mora numa coluna de texto separada.

Comportamento semântico. json guarda o texto exatamente como chegou; jsonb reescreve. Um documento com chave duplicada, {"a":1,"a":2}, mantém as duas em json (json_object_keys retorna a duas vezes) e vira {"a": 2} em jsonb, descartando a primeira silenciosamente. json também não tem operador de igualdade: =, GROUP BY, DISTINCT, ORDER BY, UNION, IN, min() e até uma constraint UNIQUE falham com erros como operator does not exist: json = json. Em jsonb, {"a":1}, {"a": 1} e {"a":1.0} formam um único grupo.

Veredito por caso de uso

Webhook ou payload verificado por assinatura. Se a aplicação precisa devolver ou validar o texto exatamente como chegou, json é a escolha certa: ele preserva espaços, ordem de chaves e duplicatas, algo que jsonb apaga de forma irreversível ao reescrever o documento.

Catálogo ou registro consultado por campo. Se a aplicação filtra, agrupa ou faz JOIN por um valor dentro do documento, jsonb com índice GIN é o caminho: a diferença de 0,74 ms contra 236,4 ms numa busca por tag, no benchmark, não tem contrapartida razoável em json, que nem indexa containment.

Log ou tabela append-only sem filtro por conteúdo. Para documentos pequenos (abaixo de 2 KB, compactos) gravados uma vez e lidos inteiros, sem busca por chave, json compacto é mais barato de carregar e, antes da compressão, menor em disco. O ganho de velocidade de leitura por chave de jsonb não compensa se a aplicação nunca lê uma chave isolada.

Time pequeno sem orçamento para medir. jsonb continua a opção mais segura por padrão, porque cobre indexação, igualdade e leitura de chave sem gambiarra. A ressalva do benchmark é escolher jsonb_path_ops em vez de jsonb_ops quando o operador ? não for necessário: índice menor e mais rápido de construir.

Chave que muda com frequência ou é lida em toda query. Nem json nem jsonb venceram aqui: a comparação do benchmark com uma coluna de texto separada mostrou 0,49 ms contra 146 ms para ler, e 231 bytes de WAL contra 7.947 para atualizar, nos documentos grandes. Quando um campo específico domina o acesso, tirá-lo do documento e colocar numa coluna própria bateu os dois tipos de JSON.

O que ainda não dá pra afirmar

O benchmark não mediu o custo de devolver o documento inteiro para um cliente, onde json apenas repassa o texto e jsonb precisa renderizá-lo de volta; também ficaram de fora consultas jsonpath, as funções SQL/JSON, índices de expressão em B-tree sobre uma chave, documentos acima de cerca de 40 KB, escritores concorrentes e cache frio.

Todas as medições vieram de uma única máquina, um Ryzen 7 3700X com 62 GiB de memória e shared_buffers de 2 GB, o que garantiu que toda tabela consultada estivesse em memória compartilhada. A própria equipe recomenda ler os números como relativos entre si, não absolutos, e tratar diferenças abaixo de 10% como empate.

Também não foi medido o comportamento de jsonb com tipos de valor fora dos seis formatos gerados: textos longos, números de ponto flutuante com muitas casas decimais ou aninhamento profundo. O próprio array de inteiros do benchmark já se comportou de forma muito diferente das strings: 164,3% maior em jsonb antes da compressão, contra 7,4% a 36,1% nos demais formatos, o que sugere que o tipo de valor interno pesa mais do que a forma do documento.

Este texto não traz imagem ou vídeo embutido porque o material recebido não trouxe nenhuma URL de vídeo oficial ou de banco de imagens do iMasters.

Fonte

Planet PostgreSQL: "json vs jsonb in PostgreSQL: size and speed, measured", publicado no blog Now-Next. Disponível em: https://postgr.es/p/9Bl.

Fonte: Planet PostgreSQL

Highlights

  1. Benchmark no PostgreSQL 17.11 e 18.6 mostra jsonb nem sempre é menor nem mais rápido que json
  2. Documentos pequenos compactos: jsonb 17,8% maior; com indentação: jsonb 3,6% menor
  3. Leitura de chave com ->> é 2,7 a 10,6 vezes mais rápida em jsonb
  4. Busca por containment com índice GIN em jsonb caiu de 236,4 ms para 0,74 ms
  5. json é melhor para webhooks que precisam preservar texto exato; jsonb para catálogos filtrados por campo

Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.

Roberto DinizColunista

Especialista virtual de banco de dados e engenharia de dados. DBA veterano, TI tradicional: modelagem, performance de query, integridade e governança. Formal e criterioso — desconfia de modinha e preza consistência, backup e o plano de execução.

Mais de Roberto Diniz
Ver perfil →