Como uma GSI do DynamoDB É Armazenada Internamente
Um Global Secondary Index não é um ponteiro de volta para a sua tabela. É uma tabela separada e gerenciada internamente — suas próprias partições, seu próprio key schema, sua própria capacidade — que o DynamoDB mantém em sincronia copiando escritas para ela de forma assíncrona.
Vindo do SQL, um índice é uma B-tree aparafusada à mesma tabela física, atualizada dentro da mesma transação. Uma GSI quebra ambas essas suposições, e quase toda surpresa de GSI rastreia de volta a esse único fato.
Como uma GSI do DynamoDB é armazenada?
Uma GSI do DynamoDB é armazenada como uma tabela separada e gerenciada internamente — com suas próprias partições, seu próprio key schema e sua própria capacidade — e não como um ponteiro para a tabela base. O DynamoDB copia cada escrita para o índice de forma assíncrona, armazenando apenas as chaves da GSI, as chaves da tabela base e quaisquer atributos projetados.
- Uma GSI é a própria tabela. Ela tem um espaço de partições totalmente independente com chave pela partition key da GSI, não pela da tabela base.
- As escritas replicam assincronamente. Sua escrita comita primeiro na tabela base, depois o DynamoDB a distribui para cada GSI por um caminho de background.
- Só os atributos projetados são armazenados. O índice guarda as chaves da GSI, as chaves da base, mais os atributos que você projetou — nada mais.
- A chave da GSI não precisa ser única. Múltiplos itens base podem compartilhar uma partition/sort key da GSI; a primary key da base é o critério de desempate que os mantém distintos.
Comece com um item base
Pegue um audit log de SaaS. Toda ação privilegiada em um workspace vira um
evento imutável. A tabela base, WorkspaceEvents, tem chaves de modo que todos os
eventos de um workspace vivam em uma coleção de itens, ordenados por tempo:
| EventPK | EventSK | actorId | verb | targetRef |
|---|---|---|---|---|
| WS#orbit-9 | TS#2026-06-23T14:02:11Z | USR#kp | ROLE_GRANTED | USR#mara |
EventPK = "WS#orbit-9" particiona por workspace; EventSK é um timestamp ISO para
que um Query retorne os eventos de um workspace em ordem cronológica. Isso serve
perfeitamente "me mostre a timeline deste workspace".
Não serve mais nada. Você não consegue perguntar "o que USR#kp fez em todos os
workspaces?" — actorId não é uma chave, então a única forma de responder isso na
tabela base é um Scan completo. Esse é o padrão de acesso
que uma GSI existe para adicionar.
Adicione uma GSI e veja uma segunda tabela aparecer
Defina uma GSI, ByActor, que reparticiona os mesmos eventos por quem os executou:
ByActor (GSI)
GSI1PK = actorId ("USR#kp")
GSI1SK = EventSK ("TS#2026-06-23T14:02:11Z")
O DynamoDB agora mantém uma segunda estrutura física. O mesmo evento lógico é
armazenado duas vezes — uma na partição WS#orbit-9 da tabela base, e de novo
na partição USR#kp da GSI:
| GSI1PK | GSI1SK | EventPK | EventSK | verb |
|---|---|---|---|---|
| USR#kp | TS#2026-06-23T14:02:11Z | WS#orbit-9 | TS#2026-06-23T14:02:11Z | ROLE_GRANTED |
Note o que veio junto: as chaves da tabela base (EventPK, EventSK) são
armazenadas em cada item da GSI automaticamente. É assim que um hit na GSI consegue
te apontar de volta para o item completo — e por que um índice
KEYS_ONLY ainda custa armazenamento.
O que de fato vive na GSI
O índice não copia o item inteiro. Cada entrada da GSI guarda exatamente três coisas, e você controla só a terceira:
| Armazenado na GSI | De onde vem | Opcional? |
|---|---|---|
| Partition + sort key da GSI | Os atributos que você nomeou como chaves da GSI | Não |
| Chave(s) da tabela base | Copiadas de cada item base | Não |
| Atributos projetados | Sua escolha de Projection | Sim |
Projection é KEYS_ONLY, INCLUDE (uma lista nomeada) ou ALL. Um Query na
GSI só pode retornar atributos que estão no índice.
Peça um que não foi projetado e o DynamoDB não o busca de forma transparente — você não recebe nada de volta para aquele campo. (docs de GSI da AWS)
Essa é a armadilha relacional invertida: o SQL faria join de volta no heap para a coluna faltante. Uma GSI nunca faz. A projeção é o contrato inteiro.
Como uma escrita chega ao índice
A replicação é a parte que mais quebra a intuição de SQL. Uma escrita base e sua atualização de índice não são uma operação atômica.
Quando você faz PutItem, o DynamoDB comita de forma durável na tabela base,
confirma sua escrita e então propaga a mudança por um caminho de background que
atualiza cada GSI. A confirmação não espera pelo índice.
Aqui está a ordem dos eventos para nossa escrita de auditoria, de cima para baixo:
O chamador recebe seu 200 OK no passo três, antes dos passos quatro a seis
terminarem — então um Query em ByActor na lacuna pode perder um evento novinho.
Essa assincronia é por design, não um defeito: é a linhagem do paper Amazon Dynamo de 2007, que escolheu disponibilidade sobre consistência síncrona. As consequências completas vivem em por que uma GSI é eventualmente consistente.
A chave da GSI não é uma chave única
No SQL, um índice secundário não-único é o padrão e um único é uma constraint na qual você opta. Uma GSI é o oposto: ela não tem garantia de unicidade, jamais.
Dois eventos de auditoria do mesmo actor em timestamps que colidem compartilhariam
a mesma GSI1PK e GSI1SK. O DynamoDB armazena ambos — ele os desambígua
internamente pela primary key da tabela base, que é sempre carregada junto.
Então um Query na GSI para um actor em um instante pode legitimamente retornar
vários itens. Se você assumiu uma-linha-por-chave do jeito que um índice único do
SQL te daria, esse é o footgun.
Quando você consulta o índice, o
DynamoDB Expression Builder escreve a
KeyConditionExpression com nomes e valores escapados corretamente — ex.:
correspondendo um actor desde um corte:
KeyConditionExpression: "#a = :actor AND #ts > :since"
ExpressionAttributeNames: { "#a": "actorId", "#ts": "EventSK" }
ExpressionAttributeValues: {
":actor": { "S": "USR#kp" },
":since": { "S": "TS#2026-06-01T00:00:00Z" }
}A capacidade vive com o índice, não com a tabela
Como a GSI é a própria tabela, ela tem sua própria capacidade de leitura e
escrita, cobrada e limitada separadamente da tabela base. Uma leitura em ByActor
consome as unidades de leitura da GSI, nunca as da tabela.
O acoplamento reverso é o que morde: toda escrita na tabela base também escreve no índice, e se a GSI não consegue absorver isso, ela faz back-pressure na escrita base. Esse mecanismo ganha seu próprio guia — quando uma GSI faz throttling de escritas na tabela base.
É também por isso que a partition key de uma GSI importa tanto quanto a da tabela base. Uma chave de GSI de baixa cardinalidade aglomera escritas em uma partição de índice mesmo quando as escritas base estão perfeitamente espalhadas — uma hot partition que você criou ao re-chavear.
Armadilhas e próximos passos
- Não espere atributos não-projetados de volta. Um
Queryna GSI retorna só o que o índice armazena. Se você precisa do item completo, projete-o ou busque-o da tabela base pelas chaves carregadas junto. - Não trate uma chave de GSI como única. Planeje para um
Queryretornar mais de um item por chave; a primary key da base é a única identidade real. - Não leia uma GSI logo após a escrita que a alimentou. O caminho async significa que o índice pode não mostrar sua escrita ainda — leia a tabela base quando você precisar de read-your-own-writes.
- Dimensione a capacidade da GSI deliberadamente. Ela é independente nas leituras e uma dependência oculta nas escritas.
Todo o jogo é escolher formatos de chave que sirvam seus padrões — single-table design sobrecarrega uma GSI por muitos deles; GSI vs LSI cobre quando um índice local encaixa em vez disso.
Monte e visualize sua KeyConditionExpression de GSI no
DynamoDB Expression Builder, depois
experimente o DynoTable para inspecionar os atributos projetados de um
índice e observar escritas replicarem para a GSI nas suas próprias tabelas.