Avançado8 min de leitura

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:

WorkspaceEvents (tabela base)
EventPKEventSKactorIdverbtargetRef
WS#orbit-9TS#2026-06-23T14:02:11ZUSR#kpROLE_GRANTEDUSR#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:

ByActor (GSI) — seu próprio espaço de partições
GSI1PKGSI1SKEventPKEventSKverb
USR#kpTS#2026-06-23T14:02:11ZWS#orbit-9TS#2026-06-23T14:02:11ZROLE_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 GSIDe onde vemOpcional?
Partition + sort key da GSIOs atributos que você nomeou como chaves da GSINão
Chave(s) da tabela baseCopiadas de cada item baseNão
Atributos projetadosSua escolha de ProjectionSim

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:

PutItemevento WS#orbit-9Comitar napartição base200 OKao chamadorCaminho async:extrair chaves da GSIRotear para a partiçãoUSR#kp de ByActorEscrever atributosprojetados

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 Query na 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 Query retornar 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.

Atualizado