GSI: quando uma tabela precisa ser consultada por mais de um caminho
Quando começamos a estudar bancos NoSQL, principalmente DynamoDB, uma coisa fica bem clara:
você não modela apenas os dados, você modela os acessos aos dados.
Em bancos relacionais, muitas vezes pensamos assim:
SELECT * FROM orders WHERE status = 'PENDING';
SELECT * FROM orders WHERE customer_id = 123;
SELECT * FROM orders WHERE created_at >= '2026-01-01';
Se precisar consultar por outro campo, criamos um índice, fazemos um join, filtramos, ordenamos e seguimos o jogo.
Mas em bancos como DynamoDB, a conversa muda um pouco.
Você geralmente acessa os dados pela chave primária da tabela. E se amanhã você precisar consultar a mesma tabela por outro campo?
É aí que entra o GSI, ou Global Secondary Index.
O que é um GSI?
GSI significa Global Secondary Index
De forma simples:
Um GSI permite consultar uma tabela usando uma chave diferente da chave primária original.
Segundo a própria documentação da AWS, um índice secundário permite consultar dados usando uma chave alternativa, além da chave primária da tabela. No caso do GSI, essa chave pode ter uma partition key e, opcionalmente, uma sort key diferentes da tabela principal.
Vamos imaginar uma tabela de pedidos:
Table: Orders
PK: orderId
Attributes:
- orderId
- customerId
- status
- createdAt
- total
Com essa estrutura, buscar um pedido pelo orderId é simples:
orderId = "order_123"
Mas e se eu quiser buscar todos os pedidos de um cliente?
customerId = "customer_456"
A tabela principal não foi modelada para isso.
Então uma opção seria criar um GSI:
GSI: OrdersByCustomer
PK: customerId
SK: createdAt
Agora eu consigo consultar os pedidos por cliente e ainda ordenar pela data de criação.
Uma forma simples de visualizar
Pense na tabela principal como uma gaveta organizada por número do pedido:
Orders Table
PK: orderId
order_001 -> customer_1 -> PENDING
order_002 -> customer_2 -> PAID
order_003 -> customer_1 -> PAID
Ela é ótima quando você sabe o orderId.
Mas se você quiser buscar por customerId, teria que varrer tudo.
Com um GSI, é como criar uma segunda forma de organização:
GSI: OrdersByCustomer
PK: customerId
SK: createdAt
customer_1 -> order_001
customer_1 -> order_003
customer_2 -> order_002
O dado continua na tabela principal, mas agora existe um índice preparado para outro padrão de consulta.
GSI não é cópia completa da tabela
Um ponto importante: o GSI não precisa carregar todos os atributos da tabela.
Você escolhe quais atributos serão projetados no índice.
Por exemplo:
GSI: OrdersByStatus
PK: status
SK: createdAt
Projected attributes:
- orderId
- customerId
- total
Isso significa que, ao consultar esse índice, você pode receber apenas os campos projetados.
Se você projetar atributos demais, o índice fica maior e mais caro.
Se projetar atributos de menos, talvez você precise fazer uma segunda consulta na tabela principal para buscar o restante dos dados.
Esse é um trade-off bem importante.
Exemplo prático
Imagine um sistema de pedidos.
A tabela principal foi criada assim:
PK: orderId
Esse acesso resolve bem o caso:
Buscar pedido por ID
Mas o produto começa a crescer e surgem novos requisitos:
- Buscar pedidos por cliente
- Buscar pedidos por status
- Buscar pedidos por data
- Listar pedidos pendentes mais recentes
Se você tentar resolver tudo apenas com a chave primária original, provavelmente vai cair em scans.
E scan em tabela grande é aquele tipo de coisa que começa inocente e depois vira uma dor de cabeça.
Uma modelagem possível seria:
Table: Orders
PK: orderId
GSI 1: OrdersByCustomer
PK: customerId
SK: createdAt
GSI 2: OrdersByStatus
PK: status
SK: createdAt
Agora você tem diferentes formas de consultar a mesma entidade.
Por que o nome é "Global"?
O termo global existe porque o índice pode consultar dados da tabela inteira, atravessando todas as partições.
Isso é diferente de um LSI, Local Secondary Index, que fica limitado à mesma partition key da tabela principal.
No GSI, a chave do índice pode ser completamente diferente da chave da tabela.
Exemplo:
Tabela principal:
PK: orderId
GSI:
PK: customerId
SK: createdAt
Ou seja, o índice não está preso ao mesmo partition key da tabela original.
O lado bom do GSI
GSI é muito útil quando você tem múltiplos padrões de acesso.
Ele ajuda em casos como:
- Buscar usuários por email
- Buscar pedidos por cliente
- Buscar transações por status
- Buscar produtos por categoria
- Buscar logs por tenant
- Buscar mensagens por conversa
- Buscar eventos por data
Exemplo:
Users Table
PK: userId
Mas no login você precisa buscar por email:
GSI: UsersByEmail
PK: email
Sem esse GSI, você teria que fazer scan para encontrar o usuário pelo email, o que seria péssimo conforme a tabela cresce.
O lado perigoso do GSI
Apesar de parecer uma solução mágica, GSI tem custo.
Cada vez que você escreve na tabela principal, o DynamoDB também precisa atualizar os GSIs afetados.
Então, se você tem uma tabela com muitos writes e muitos GSIs, cada escrita pode ficar mais cara.
Exemplo:
Table write:
- grava na tabela principal
- atualiza GSI por customerId
- atualiza GSI por status
- atualiza GSI por category
Ou seja, mais índices significam mais flexibilidade de leitura, mas também mais custo e complexidade na escrita.
A própria AWS explica que consultas em um GSI consomem capacidade do índice, não da tabela principal, e que alterações na tabela também podem consumir capacidade do índice quando ele precisa ser atualizado.
GSI tem consistência eventual
Esse é um ponto muito importante.
Consultas em Global Secondary Index no DynamoDB suportam apenas eventual consistency.
Na prática, isso significa que você pode gravar um item na tabela principal e, por alguns instantes, ele ainda não aparecer no GSI.
Exemplo:
1. Crio um novo pedido com status = "PENDING"
2. A escrita na tabela principal retorna sucesso
3. Consulto o GSI OrdersByStatus imediatamente
4. Pode ser que o pedido ainda não apareça naquele exato momento
5. Depois de um pequeno intervalo, o índice converge
Isso não significa que o banco está errado.
Significa que o índice é atualizado de forma assíncrona.
Por isso, GSI é ótimo para muitos casos, mas talvez não seja ideal quando você precisa de leitura fortemente consistente logo após uma escrita.
Quando usar GSI?
Eu usaria GSI quando existe um padrão de acesso claro que não é atendido pela chave primária da tabela.
Alguns exemplos:
Buscar usuário por email
Buscar pedidos por cliente
Listar pagamentos por status
Consultar produtos por categoria
Buscar eventos por organização
Listar notificações por usuário
O ponto principal é:
crie um GSI para responder uma pergunta específica do seu sistema.
Não crie GSI só porque "talvez um dia eu precise filtrar por esse campo".
Quando evitar GSI?
Eu evitaria GSI quando:
- O acesso é raro
- O volume de escrita é muito alto
- O índice teria baixa cardinalidade
- O campo indexado muda toda hora
- O padrão de consulta ainda não está claro
- Um scan ocasional em uma tabela pequena já resolve
- A aplicação precisa de consistência forte naquele acesso
Um exemplo perigoso seria criar um GSI com partition key status:
PK: status
Se os status forem apenas:
PENDING
PAID
CANCELED
Você pode acabar concentrando muitos itens na mesma chave, principalmente se a maioria dos pedidos estiver em PENDING.
Isso pode gerar hot partition.
Nesse caso, talvez você precise pensar em uma chave mais bem distribuída, como:
PK: status#date
SK: createdAt
Ou outra estratégia baseada no padrão real de acesso.
GSI resolve problema de modelagem, não falta de modelagem
Uma armadilha comum é pensar:
"Vou criar vários GSIs e depois vejo como consultar."
Isso é perigoso.
Em DynamoDB, o ideal é começar pelos access patterns.
Antes de criar a tabela, pergunte:
Como minha aplicação vai buscar esses dados?
Quais telas precisam desses dados?
Quais filtros são obrigatórios?
Qual ordenação eu preciso?
Qual volume de leitura e escrita espero?
Preciso de consistência forte?
Depois disso, você decide a chave da tabela e os GSIs.
GSI não deve ser tratado como gambiarra para consulta mal pensada.
Ele deve ser parte da modelagem.
Exemplo mais próximo do mundo real
Imagine um SaaS multi-tenant.
Você tem uma tabela de produtos:
Products
PK: productId
Attributes:
- productId
- companyId
- categoryId
- name
- status
- stock
- createdAt
Buscar produto por ID é fácil.
Mas em uma tela de dashboard, talvez você precise listar os produtos de uma empresa:
GSI: ProductsByCompany
PK: companyId
SK: createdAt
Em outra tela, talvez precise listar produtos por categoria dentro da empresa:
GSI: ProductsByCompanyAndCategory
PK: companyId#categoryId
SK: name
E talvez no dashboard você precise listar produtos com estoque baixo.
Nesse caso, criar um GSI pode ou não ser a melhor opção. Depende do volume, da frequência de consulta e da forma como o alerta será calculado.
O importante é entender que cada GSI nasce para responder uma pergunta:
Quais produtos pertencem a essa empresa?
Quais produtos dessa categoria pertencem a essa empresa?
Quais produtos estão ativos?
Quais produtos precisam aparecer no dashboard?
Resumo mental
Para mim, o jeito mais simples de pensar em GSI é:
A tabela principal responde um caminho de busca.
O GSI cria outro caminho de busca.
Mas esse novo caminho tem custo.
Ele ocupa espaço, consome capacidade, precisa ser atualizado e trabalha com consistência eventual.
Então, GSI é uma ferramenta poderosa, mas precisa ser usada com intenção.
Conclusão
Global Secondary Index é um dos conceitos mais importantes para modelar bem tabelas no DynamoDB.
Ele permite consultar os mesmos dados por diferentes chaves, sem depender de scans caros e ineficientes.
Mas ele também traz trade-offs:
- mais custo
- mais complexidade
- consistência eventual
- risco de hot partition
- necessidade de pensar melhor nos access patterns
No fim, GSI não é só um "índice secundário".
Ele é uma forma de materializar um novo padrão de acesso dentro do seu banco.
E em bancos como DynamoDB, entender os padrões de acesso antes de modelar os dados faz toda a diferença.
Passei o dia todo procrastinando e também ocupado com outros afazeres, mas não queria deixar esse estudo morrer só no meu caderno. Então estou postando agora uma parte do que estudei hoje sobre GSI (Global Secondary Index). Talvez não esteja perfeito, mas faz parte do processo: estudar, organizar as ideias e compartilhar.