Por que um DynamoDB GSI é eventualmente consistente
Você escreve um item, imediatamente consulta um Índice Secundário Global para ele e obtém
nada de volta — mesmo que a gravação tenha sido bem-sucedida e uma tabela base GetItem
retorna o item bem.
Nada está quebrado. Você atingiu a propriedade mais surpreendente de GSIs: cada leitura de um GSI é eventualmente consistente. Há uma breve janela após uma gravação onde o índice ainda não alcançou.
Os DynamoDB GSIs são eventualmente consistentes?
Sim — cada leitura de um Índice Secundário Global é eventualmente consistente, sem
maneira de cancelar. Sua gravação é confirmada primeiro na tabela base e depois se propaga
de forma assíncrona ao índice, então umemitido logo após uma gravação pode
retornar linhas obsoletas ou ausentes. DynamoDB não oferece sinalizador ConsistentRead para GSI.
- Um GSI é uma tabela separada e replicada de forma assíncrona — seus commits de gravação primeiro para a tabela base e depois se propaga para o índice.
- Não existe sinalizador
ConsistentReadpara um GSI. Ao contrário da tabela base, você não pode forçar uma leitura forte para fechar a lacuna. - Leia suas próprias gravações na tabela base, não no GSI. Você já segura ologo após uma gravação.
- Imponha a exclusividade com uma gravação condicional, não com uma consulta GSI. O a lacuna de propagação transforma-se em "isso foi tirado?" faça check-in em uma corrida.
O sintoma: uma inscrição que "não consegue se encontrar"
Pegue uma tabela Membros para um serviço de contas de usuário. A tabela base é codificada por um
ID interno, mas os usuários fazem login por e-mail, então há uma pesquisa por e-mail GSI:
| PK | SK | displayName | |
|---|---|---|---|
| ACC#a1f9c | PROFILE | ada@northwind.test | Ada L. |
| GSI1PK | GSI1SK |
|---|---|
| ada@northwind.test | ACC#a1f9c |
O fluxo de inscrição faz duas coisas consecutivas: PutItem o novo membro, então
Query EmailIndex WHERE GSI1PK = "ada@northwind.test" para verificar mais ninguém
reivindicou esse endereço e para carregar o perfil.
Execute essas duas chamadas com alguns milissegundos de diferença e Query pode retornar zero
itens. Faça novamente um segundo depois e a linha estará lá. A escrita não falhou
- o índice ainda não havia sido atualizado.
Por que isso acontece: GSIs são replicados de forma assíncrona
Uma GSI é uma tabela separada e gerenciada internamente com suas próprias partições e seus próprio esquema de chave. Não é mantido dentro da mesma transação que o seu gravação na tabela base.
Quando você PutItem, DynamoDB se compromete de forma duradoura com a tabela base, reconhece seu
escreve e então propaga de forma assíncrona a alteração para cada GSI. O AWS
Documentação do GSI
afirma claramente: GSIs suporta apenas leituras eventualmente consistentes.
O atraso de propagação entre a gravação da tabela base e a atualização do índice é geralmente um fração de segundo - mas não é garantido e não é limitado sob carga. Projetar como se fosse limitado é a armadilha.
Esse atraso é a compensação original do design do Dynamo. O 2007 Artigo do Amazon Dynamo escolheu disponibilidade e tolerância à partição em vez de consistência forte.
GSIs herdam essa linhagem. O acoplamento frouxo é o que permite que o índice seja dimensionado e permaneça gravável independentemente da tabela base.
A lacuna entre 200 OK e "replicar alteração" é a janela onde seu índice
ler é obsoleto. Não há nenhum sinalizador de leitura consistente que o feche.
Ao contrário da tabela base - onde você passa ConsistentRead = true para forçar um
GetItem/Query — um GSI rejeita categoricamente essa opção.
Um LSI pode ser lido fortemente porque compartilha as partições da tabela base; veja GSI vs LSI para saber por que essa distinção existe.
Custo de leitura em um GSI
As consultas GSI cobram o índice sob demanda RCU em us-east-1 como qualquer outra leitura —
0,5 RCU por 4 KB bloco eventualmente consistente — e você não pode pagar o dobro por
consistência forte porque ConsistentRead é rejeitado. O atraso de propagação é
grátis; o índice lido não é. Compare as taxas de leitura da tabela base com as taxas de leitura GSI no
calculadora de preços.
Uma armadilha mais sutil: valores antigos obsoletos, não apenas faltando valores novos
O caso da linha faltante é o óbvio. O bug mais silencioso é ler um texto obsoleto valor anterior.
Digamos que Ada mude seu e-mail de ada@northwind.test para ada.l@northwind.test. O
a tabela base é atualizada atomicamente, mas por um momento o GSI ainda pode retornar o
antiga entrada de índice.
Uma pesquisa no novo valor falha, enquanto o valor abandonado ainda é resolvido.
Pior: se você consultar o GSI e responder com base no que leu, poderá agir de acordo com um valor que não existe mais. Trate qualquer leitura de GSI como um instantâneo que pode ficar atrasado na realidade.
Projete em torno disso - não lute contra isso
A janela de propagação é real, então a correção é arquitetônica, não um botão de nova tentativa que você alternar. Quatro padrões, aproximadamente em ordem de preferência:
- Leia suas próprias gravações na tabela base. Logo após uma gravação você já
mantenha a chave primária (
ACC#a1f9c), então faça umGetItemfortemente consistente em a tabela base em vez de consultar o GSI.
O GSI é para o padrão de acesso outro — "Tenho um e-mail, encontre a conta"
- não para confirmar a escrita que você acabou de fazer.
- Imponha a exclusividade com um item de proteção, não com o GSI. Nunca confie em uma consulta GSI para provar que um e-mail não foi reivindicado - a lacuna de propagação faz com que seja uma corrida dois inscrições simultâneas podem perder.
Em vez disso, escreva um item de exclusividade dedicado digitado no próprio e-mail
(PK = "EMAIL#ada@northwind.test") dentro de um TransactWriteItems com um
ConditionExpression de attribute_not_exists(PK).
Condições de tabela base fortemente consistentes, aplicadas atomicamente, são o que realmente impor exclusividade.
TransactWriteItems:
- Put member item (PK = ACC#a1f9c, SK = PROFILE)
- Put uniqueness item (PK = EMAIL#ada@northwind.test)
ConditionExpression: attribute_not_exists(PK)Se uma segunda inscrição concorrer ao mesmo endereço, sua condição falhará e o inteiroé rejeitado - sem GSI, sem atraso de propagação, sem reivindicação dupla.
Crie e visualize a condição attribute_not_exists com o
DynamoDB Expression Builder
antes de conectá-lo ao código.
- Tolere o atraso na UX. Quando a leitura GSI é genuinamente a ferramenta certa (login por e-mail para um usuário existente), a janela é inferior a um segundo e inofensiva - uma conta estabelecida propagada há muito tempo.
Reserve o caminho da tabela base fortemente consistente para o momento de leitura após gravação apenas.
- Consulte novamente, não presuma. Se um fluxo de trabalho precisar observar um item totalmente novo o GSI, trate um resultado vazio como "ainda não visível", não "não existe" e consultar novamente após um breve intervalo.
Mas prefira os padrões 1 e 2, que eliminam totalmente as suposições.
Veja você mesmo a lacuna de propagação
A maneira mais rápida de construir intuição é observar isso acontecer. Em DynoTable você coloca um item na tabela base e consulte imediatamente o GSI em uma segunda guia.
Em uma tabela carregada, você ocasionalmente capturará o índice que segue os dados base e, em seguida, observe-o convergir na próxima atualização.
Ver o atraso com seus próprios dados faz com que "leia suas próprias gravações da base tabela" é muito melhor do que qualquer diagrama.
Armadilhas e próximos passos
- Não bloqueie a lógica em uma leitura após gravação GSI. Verificações de exclusividade ", fiz minha gravação confirmações "land" e loops de leitura-modificação-gravação pertencem fortemente tabela base consistente.
- Não procure
ConsistentReadem um GSI — não é permitido e causará erro. - Não modele um padrão de acesso como GSI quando a chave base já responde. Sirva uma leitura da chave primária e você pulará totalmente a janela de propagação.
Escolher o formato de chave certo é o jogo inteiro
design de tabela única; saber quando um Query vence um
Scan mantém você fora do índice em primeiro lugar
(Query vs Scan).
Construa e teste sua exclusividade ConditionExpression no
DynamoDB Expression Builder. Então
tente DynoTable para observar as gravações da tabela base se propagarem para um GSI em tempo real
tempo e projete suas chaves para que a janela de consistência eventual nunca o incomode.