{"id":8429,"date":"2019-01-21T15:02:59","date_gmt":"2019-01-21T17:02:59","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=8429"},"modified":"2019-10-28T11:55:46","modified_gmt":"2019-10-28T14:55:46","slug":"indices-para-jsonb-no-postgres","status":"publish","type":"post","link":"http:\/\/blog.plataformatec.com.br\/2019\/01\/indices-para-jsonb-no-postgres\/","title":{"rendered":"\u00cdndices para JSONB no Postgres"},"content":{"rendered":"

No Postgres existe a op\u00e7\u00e3o de salvar dados como JSON<\/a> – o que pode ser muito \u00fatil especialmente quando temos muitas incertezas quanto aos requisitos de neg\u00f3cio que ajudariam na modelagem das tabelas.<\/p>\n

A flexibilidade de poder armazenar os dados sem se preocupar com a estrutura das tabelas parece interessante, por\u00e9m qual o impacto disso na performance?<\/p>\n

A verdade \u00e9 o que os dados do tipo JSON s\u00e3o armazenados da maneira como foram inseridos – isto \u00e9, em texto – o que deixa as consultas um pouco mais lentas j\u00e1 que os dados precisam ser parseados<\/em> novamente. Por\u00e9m, existe uma outra varia\u00e7\u00e3o: o JSONB. Esse formato processa o texto no momento da inser\u00e7\u00e3o e o armazena em formato bin\u00e1rio. Isso faz com que a inser\u00e7\u00e3o demore um pouco mais, em troca de consultas mais r\u00e1pidas. Ah, e o JSONB tamb\u00e9m suporta \u00edndices!<\/p>\n

Nesse artigo voc\u00ea vai encontrar uma compara\u00e7\u00e3o dos tipos de \u00edndices dispon\u00edveis para JSONB no Postgres, um benchmark<\/em> e como us\u00e1-los em aplica\u00e7\u00f5es Rails.<\/p>\n

Setup<\/h2>\n

Para testar a performance de \u00edndices, \u00e9 importante ter um banco populado com uma quantidade razo\u00e1vel de registros. Abaixo voc\u00ea encontrar\u00e1 as etapas necess\u00e1rias para criar o banco de dados utilizado nesse benchmark<\/em>.
\nPara acompanhar as etapas \u00e0 seguir, precisaremos do
Postgres devidamente instalado e rodando<\/a>.<\/p>\n

Iremos importar cerca de 600 mil registros na estrutura abaixo:<\/p>\n

{\n\n        \"customer_id\": \"ATVPDKIKX0DER\",\n        \"product\": {\n            \"category\": \"Arts & Photography\",\n            \"group\": \"Book\",\n            \"id\": \"1854103040\",\n            \"sales_rank\": 72019,\n            \"similar_ids\": [\n                \"1854102664\",\n                \"0893815381\",\n                \"0893816493\",\n                \"3037664959\",\n                \"089381296X\"\n            ],\n            \"subcategory\": \"Art\",\n            \"title\": \"The Age of Innocence\"\n        },\n        \"review\": {\n            \"date\": \"1995-08-10\",\n            \"helpful_votes\": 5,\n            \"rating\": 5,\n            \"votes\": 12\n        }\n   }\n<\/code><\/pre>\n

Para importar esses dados, fa\u00e7a o download do arquivo<\/a> e siga as instru\u00e7\u00f5es abaixo:<\/p>\n

1. Criar banco de dados<\/h3>\n
\u276f psql\n<\/code><\/pre>\n
CREATE DATABASE store;\n<\/code><\/pre>\n

2. Criar tabela<\/h3>\n

Vamos criar a tabela reviews<\/code> com um \u00fanico campo (content<\/code>) para armazenar o conte\u00fado de cada registro:<\/p>\n

\u276f psql store\n<\/code><\/pre>\n
CREATE TABLE reviews(content jsonb);\n<\/code><\/pre>\n

3. Importar os dados<\/h3>\n

Por fim, vamos importar os dados do JSON que acabamos de baixar utilizando o comando copy<\/code> dentro do console do Postgres:<\/p>\n

copy reviews (content) FROM 'customer_reviews_nested_1998.json'\n<\/code><\/pre>\n

B-tree<\/h2>\n

O \u00edndice do formato B-tree<\/strong> funciona somente com uma chave do documento e para um operador espec\u00edfico: isto \u00e9, o operador usado na defini\u00e7\u00e3o do \u00edndice dever\u00e1 ser o mesmo utilizado na consulta:<\/p>\n

CREATE INDEX ON reviews((content #>> '{product,category}'));\n<\/code><\/pre>\n

Para que o \u00edndice seja utilizado, devemos fazer a consulta com o mesmo operador:<\/p>\n

SELECT COUNT(*) FROM reviews WHERE content #>> '{product,category}' = 'Arts & Photography';\n<\/code><\/pre>\n

Se utilizarmos outros operadores o \u00edndice n\u00e3o ser\u00e1 aplicado, e consequentemente a busca ficar\u00e1 mais lenta:<\/p>\n

SELECT COUNT(*) FROM reviews WHERE content->'product'->>'category' = 'Arts & Photography';\n<\/code><\/pre>\n

O \u00edndice para a consulta acima seria o seguinte:<\/p>\n

CREATE INDEX ON reviews((content->'product'->>'category'));\n<\/code><\/pre>\n

Se for necess\u00e1rio responder perguntas como \u201cquais as reviews<\/em> tem rating<\/em> 4<\/code> ou mais\u201d ou \u201cquais reviews<\/em> n\u00e3o s\u00e3o de livros\u201d – operadores ><\/code>, =<\/code>, <<\/code>, >=<\/code>, <=<\/code> e !=<\/code> – esse \u00e9 o \u00edndice mais indicado.<\/p>\n

Por\u00e9m fica uma observa\u00e7\u00e3o aqui: nos testes feitos nesse benchmark<\/em>, os \u00edndices para esses operadores acabaram n\u00e3o<\/strong> valendo a pena, o tempo de execu\u00e7\u00e3o foi o mesmo. Mas pode ser que esse n\u00e3o seja sempre o caso, por isso \u00e9 importante fazer benchmarks<\/em> no seu ambiente para ter uma escolha mais assertiva.<\/p>\n

Pr\u00f3s:<\/h3>\n