Intermediário8 min de leitura

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 ConsistentRead para 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:

Members (base table)
PKSKemaildisplayName
ACC#a1f9cPROFILEada@northwind.testAda L.
EmailIndex (GSI)
GSI1PKGSI1SK
ada@northwind.testACC#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.

EmailIndexTabela baseAppEmailIndexTabela baseAppasync propagationPutItem (new member)200 OKQuery by email0 items (stale)replicate changeQuery by email1 item (caught up)

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:

  1. 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 um GetItem fortemente 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.
  1. 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.

  1. 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.

  1. 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 ConsistentRead em 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.

Atualizado