Hoje vim relatar minha experiência com o índice GIN, que resultou numa melhora muito positiva na performance de algumas das consultas mais lentas do sistema que atuei.
Introdução ao GIN INDEX
O GIN (Generalized Inverted iNdex) é um índice designado para lidar com tipos de dados que são subdivisíveis, ou seja, podem ser "divididos" em partes menores, permitindo a pesquisa de valores individuais. É muito utilizado para consultas em dados estruturados e não estruturados como array, jsonb e campos de texto para busca full-text.
Introduzido na versão 8.2 do PostgreSQL, rapidamente se tornou uma solução indispensável para cenários onde se precisa de buscas rápidas em dados complexos. Diferente do índice padrão B-TREE que é mais adequado para buscas de valores únicos, o GIN é otimizado para casos onde o mesmo valor pode estar associado a vários registros, assim como os valores de um array.
Como eu utilizei o GIN INDEX
Certo dia, me deparei com um problema crítico no sistema: uma consulta estava causando uma lentidão significativa e prejudicando o desempenho geral da aplicação. Após analisar, percebi que ela fazia uso de uma coluna do tipo ARRAY em seu filtro.
Consultando a documentação oficial do PostgreSQL, encontrei o índice GIN como uma possível solução. Decidi implementá-lo na coluna problemática para avaliar o impacto. O resultado foi impressionante: o tempo de execução da consulta caiu drasticamente e, com isso, o sistema se tornou visivelmente mais ágil.
O impacto foi ainda maior porque a coluna onde apliquei o índice GIN era amplamente utilizada em diversas partes do sistema, tornando essa melhoria um divisor de águas para a performance geral. Essa experiência reforçou minha percepção de por que o GIN é frequentemente chamado de "índice mágico".
Desvantagens da utilização do GIN Index
Como visto até então, o índice traz sim diversas vantagens quando o assunto é lidar com dados complexos, porém nem tudo é perfeito e ele possui desvantagens que devem ser consideradas e analisadas caso você esteja pensando em implementar ele na sua tabela.
Uma grande desvantagem são as operações de escrita que se tornam mais custosas já que o índice deve se atualizar para refletir as mudanças dos dados, ou seja, cada operação que altera o dado exige a atualização do índice também, o que é mais custoso em tabelas que possuem alta frequência de escrita.
Além disso, o GIN consome mais memória do que os outros índices, tanto de armazenamento quanto de consulta, especialmente se a coluna indexada contiver muitos valores únicos ou complexos.
Exemplo de criação do índice
Para poder exemplificar o uso do índice, criei uma tabela chamada users contendo apenas id e data.
CREATE TABLE users (
id SERIAL PRIMARY KEY,
data JSONB
);
Para criarmos o índice, basta usar o USING indicando qual o tipo de índice que será utilizado
CREATE INDEX idx_data_gin ON users USING gin (data);
Pronto! Índice criado, agora ao analisar a consulta será possível notar que ele utilizara o índice criado para buscar a informação.
Query plan
"Bitmap Heap Scan on users u (cost=12.00..16.01 rows=1 width=68) (actual time=0.022..0.023 rows=1 loops=1)"
" Recheck Cond: (data @> '{""name"": ""Mateus""}'::jsonb)"
" Heap Blocks: exact=1"
" -> Bitmap Index Scan on idx_data_gin (cost=0.00..12.00 rows=1 width=0) (actual time=0.011..0.011 rows=1 loops=1)"
" Index Cond: (data @> '{""name"": ""Mateus""}'::jsonb)"
"Planning Time: 0.120 ms"
"Execution Time: 0.061 ms"
Note que você sempre verá Bitmap Index Scan no plano de execução pois este é o plano de varredura compatível com o índice GIN. Ou seja, isso não significa que seu índice está sendo usado de maneira ineficiente, apenas que é o comportamento esperado.
GIN Multicoluna, uma alternativa para B-TREE x GIN
Em consultas que envolvem colunas com características distintas, pode ser necessário combinar índices B-TREE e GIN. Uma abordagem inicial seria criar índices separados, com um índice B-TREE para colunas que lidam bem com valores únicos e um índice GIN para colunas com dados mais complexos. Embora essa solução funcione, ela pode não ser eficiente em termos de desempenho, pois cada índice é avaliado separadamente.
Uma alternativa interessante é o uso de índices GIN multicoluna. Com essa abordagem, é possível indexar várias colunas em um único índice, cobrindo diferentes tipos de dados. No entanto, para colunas que não são subdivisíveis (como integer ou timestamp), é necessário habilitar a extensão btree_gin do PostgreSQL. Essa extensão permite que esses tipos sejam indexados de maneira compatível dentro de um índice GIN.
CREATE EXTENSION btree_gin;
CREATE INDEX ON records USING gin (data, customer_id);
Com esses comandos você consegue criar um índice GIN multicoluna.
Embora seja uma solução menos comum, ela pode ser útil em cenários específicos. No entanto, é importante considerar que índices maiores resultam em maior uso de I/O e custos adicionais em operações de escrita, como inserções e atualizações.
Considerações finais
Com grandes poderes, vem grandes responsabilidades.
Embora o GIN seja extremamente eficiente em cenários específicos, ele não é uma solução universal para todos os problemas relacionados à performance de consultas. Em muitos casos, o tradicional B-TREE continua sendo a escolha mais adequada, especialmente para consultas simples ou quando se busca por valores únicos. É essencial avaliar cuidadosamente o problema em questão para determinar se a inclusão de um índice é realmente necessária e, caso seja, qual o tipo de índice que melhor atenderá às necessidades do sistema.
Referências
https://www.postgresql.org/docs/current/gin.html
https://pganalyze.com/blog/gin-index
Top comments (0)