Modelos de consistência do DAX e do DynamoDB - Amazon DynamoDB

Modelos de consistência do DAX e do DynamoDB

O Amazon DynamoDB Accelerator (DAX) é um serviço de armazenamento em cache por gravação simultânea (write-through) projetado para simplificar o processo de adição de um cache a tabelas do DynamoDB. Como o DAX opera separadamente do DynamoDB, é importante compreender os modelos de consistência do DAX e do DynamoDB para garantir que as aplicações se comportem conforme esperado.

Em muitos casos de uso, a maneira como a aplicação usa o DAX afeta a consistência dos dados dentro do cluster do DAX e a consistência dos dados entre o DAX e o DynamoDB.

Consistência entre nós de cluster do DAX

Para obter alta disponibilidade para sua aplicação, recomendamos provisionar o cluster do DAX com pelo menos três nós. Além disso, coloque esses nós em várias zonas de disponibilidade dentro de uma região.

Quando o cluster do DAX estiver em execução, ele replicará os dados entre todos os nós do cluster (supondo que você tenha provisionado mais de um nó). Considere uma aplicação que realiza uma operação UpdateItem bem-sucedida usando o DAX. Essa ação faz com que o cache de itens no nó primário seja modificado com o novo valor. Esse valor é, então, replicado em todos os outros nós no cluster. Essa replicação é eventualmente consistente e costuma demorar menos de um segundo para ser concluída.

Nesse cenário, é possível que dois clientes leiam a mesma chave do mesmo cluster do DAX, mas recebam valores diferentes, dependendo do nó que cada cliente acessou. Os nós estarão todos consistentes quando a atualização tiver sido totalmente replicada por todos os nós do cluster. (Esse comportamento é semelhante à natureza final consistente do DynamoDB.)

Se você estiver criando uma aplicação que usa o DAX, essa aplicação deverá ser projetada para tolerância a dados finais consistentes.

Comportamento do cache de itens do DAX

Cada cluster do DAX tem dois caches distintos: um cache de itens e um cache de consultas. Para ter mais informações, consulte DAX: como ele funciona.

Esta seção aborda as implicações de consistência da leitura e da gravação no cache de itens do DAX.

Consistência de leituras

Com o DynamoDB, por padrão, a operação GetItem executa uma leitura final consistente. Suponha que você use UpdateItem com o cliente do DynamoDB. Se você tentar ler o mesmo item imediatamente, poderá ver os dados no estado em que estavam antes da atualização. Isso acontece devido ao atraso da propagação em todos os locais de armazenamento do DynamoDB. A consistência normalmente é atingida em segundos. Portanto, se você repetir a leitura, provavelmente verá o item atualizado.

Ao usar GetItem com o cliente do DAX, a operação (neste caso, uma leitura final consistente) ocorre conforme mostrado abaixo.

Diagrama de fluxo de trabalho que mostra as etapas numeradas para atualização de um item.
  1. O cliente do DAX emite uma solicitação GetItem. O DAX tenta ler o item solicitado do cache de itens. Se o item estiver no cache (acerto no cache), o DAX o retornará à aplicação.

  2. Se o item não estiver disponível (erro de cache), o DAX realizará uma operação final consistente GetItem no DynamoDB.

  3. O DynamoDB retornará o item solicitado, e o DAX o armazenará no cache de itens.

  4. O DAX retornará o item para a aplicação.

  5. (Não mostrado) Se o cluster do DAX contiver mais de um nó, o item será replicado em todos os outros nós do cluster.

O item permanece no cache de itens do DAX, sujeito à configuração de vida útil (TTL) e ao algoritmo de Least Recently Used (LRU – Menos usado recentemente) do cache. Para ter mais informações, consulte DAX: como ele funciona.

No entanto, durante esse período, o DAX não lê novamente o item no DynamoDB. Se outra pessoa atualizar o item usando um cliente do DynamoDB, ignorando totalmente o DAX, uma solicitação GetItem usando o cliente do DAX produzirá resultados diferentes da mesma solicitação GetItem usando o cliente do DynamoDB. Nesse cenário, o DAX e o DynamoDB manterão valores inconsistentes para a mesma chave até que o TTL do item do DAX expire.

Se uma aplicação modificar dados em uma tabela subjacente do DynamoDB, ignorando o DAX, a aplicação precisará antecipar e tolerar as inconsistências de dados que possam surgir.

nota

Além do GetItem, o cliente do DAX também oferece suporte a solicitações BatchGetItem. BatchGetItem é essencialmente um wrapper ao redor de uma ou mais solicitações GetItem e, dessa forma, o DAX trata cada uma delas como uma operação GetItem individual.

Consistência de gravações

O DAX é um cache write-through, o que simplifica o processo de manter o cache de itens do DAX consistente com as tabelas do DynamoDB subjacentes.

O cliente do DAX oferece suporte às mesmas operações da API de gravação que o DynamoDB (PutItem, UpdateItem, DeleteItem, BatchWriteItem e TransactWriteItems). Quando você usa essas operações com o cliente do DAX, os itens são modificados tanto no DAX quanto no DynamoDB. O DAX atualizará os itens em seu cache de itens, independentemente do valor de TTL desses itens.

Por exemplo, suponha que você emita uma solicitação GetItem do cliente do DAX para ler um item da tabela ProductCatalog. (A chave de partição é Id e não há uma chave de classificação). Você recupera o item cujo Id é 101. O valorQuantityOnHand para esse item é 42. O DAX armazena o item em seu cache de itens com um TTL específico. Para este exemplo, suponha que o TTL é de 10 minutos. Três minutos depois, outra aplicação usa o cliente do DAX para atualizar o mesmo item de forma que o valor de QuantityOnHand agora é 41. Supondo que o item não seja atualizado novamente, qualquer leitura subsequente do mesmo item durante os próximos dez minutos retornará o valor armazenado em cache a QuantityOnHand (41).

Como o DAX processa gravações

O DAX foi projetado para aplicações que exigem leituras de alta performance. Por ser um cache write-through, o DAX passa suas gravações para o DynamoDB de forma síncrona e, em seguida, replica assíncrona e automaticamente as atualizações resultantes para o cache de itens em todos os nós do cluster. Você não precisa gerenciar a lógica de invalidação do cache, pois o DAX lida com ela automaticamente.

O DAX oferece suporte às seguintes operações de gravação: PutItem, UpdateItem, DeleteItem, BatchWriteItem e TransactWriteItems.

Quando você envia uma solicitação PutItem, UpdateItem, DeleteItem ou BatchWriteItem ao DAX:

  • O DAX envia a solicitação para o DynamoDB.

  • O DynamoDB responde ao DAX, confirmando que a gravação foi bem-sucedida.

  • O DAX grava o item em seu cache de itens.

  • O DAX retorna uma resposta de êxito ao solicitante.

Quando você envia uma solicitação TransactWriteItems ao DAX:

  • O DAX envia a solicitação para o DynamoDB.

  • O DynamoDB responde ao DAX, confirmando que a transação foi concluída.

  • O DAX retorna uma resposta de êxito ao solicitante.

  • Em segundo plano, o DAX faz uma solicitação TransactGetItems para cada item na solicitação TransactWriteItems para armazenar o item no cache de itens. TransactGetItems é usada para garantir o isolamento serializável.

Se houver falha em uma gravação no DynamoDB por qualquer motivo, inclusive controle de utilização, o item não será armazenado em cache no DAX. A exceção da falha é retornada ao solicitante. Isso garante que os dados sejam gravados no cache do DAX somente se forem gravados primeiro com êxito no DynamoDB.

nota

Toda gravação no DAX altera o estado do cache de itens. No entanto, as gravações no cache de itens não afeta o cache de consultas. (O cache de itens e o cache de consultas do DAX têm finalidades diferentes e operam de forma independente um do outro.)

Comportamento do cache de consultas DAX

O DAX armazena em cache os resultados das solicitações Query e Scan em seu cache de consultas. No entanto, esses resultados não afetam de forma alguma o cache de itens. Quando sua aplicação emite uma solicitação Query ou Scan com o DAX, o conjunto de resultados é salvo no cache de consultas, e não no cache de itens. Você não pode "aquecer" o cache de itens executando uma operação Scan porque o cache de itens e o cache de consultas são entidades separadas.

Consistência de consulta-atualização-consulta

As atualizações no cache de itens, ou na tabela subjacente do DynamoDB, não invalidam nem modificam os resultados armazenados no cache de consultas.

Para ilustrar, considere o seguinte cenário. Um aplicativo está trabalhando com a tabela DocumentRevisions, que tem DocId como chave de partição e RevisionNumber como chave de classificação.

  1. Um cliente emite uma solicitação Query para DocId 101 para todos os itens cujo RevisionNumber é maior ou igual a 5. O DAX armazena o conjunto de resultados no cache de consultas e retorna esse conjunto ao usuário.

  2. O cliente emite uma solicitação de PutItem de DocId 101 com um valor de RevisionNumber igual a 20.

  3. O cliente emite a mesma Query descrita na etapa 1 (DocId 101 e RevisionNumber >= 5).

Neste cenário, o conjunto de resultados armazenado em cache para a Query emitida na etapa 3 será idêntico ao conjunto de resultados que foi armazenado em cache na etapa 1. Isso acontece porque o DAX não invalida os conjuntos de resultados de Query ou Scan com base em atualizações em itens individuais. A operação PutItem da etapa 2 é refletida no cache de consultas do DAX apenas quando o TTL de Query expira.

Seu aplicativo deve considerar o valor do TTL do cache de consultas e por quanto tempo pode tolerar resultados inconsistentes entre o cache de consultas e o cache de itens.

Leituras altamente consistentes e transacionais

Para realizar uma solicitação de GetItem, BatchGetItem, Query ou Scan fortemente consistente, defina o parâmetro ConsistentRead como true. O DAX transfere solicitações de leitura fortemente consistente ao DynamoDB. Ao receber uma resposta do DynamoDB, o DAX retorna os resultados ao cliente, mas não armazena os resultados em cache. O DAX não pode servir leituras fortemente consistentes por conta própria, pois ele não está firmemente acoplado ao DynamoDB. Por esse motivo, todas as leituras subsequentes do DAX precisariam ser leituras finais consistentes. E todas as leituras fortemente consistentes subsequentes precisariam ser passadas para o DynamoDB.

O DAX trata solicitações TransactGetItems da mesma forma como trata leituras fortemente consistentes. O DAX passa todas as solicitações TransactGetItems para o DynamoDB. Ao receber uma resposta do DynamoDB, o DAX retorna os resultados ao cliente, mas não armazena os resultados em cache.

Armazenamento em cache negativo

O DAX oferece suporte a entradas de cache negativas, tanto no cache de itens quanto no cache de consultas. Uma entrada de cache negativa ocorre quando o DAX não consegue localizar os itens solicitados em uma tabela subjacente do DynamoDB. Em vez de gerar um erro, o DAX armazena em cache um resultado vazio e retorna esse resultado ao usuário.

Por exemplo, suponha que uma aplicação envie uma solicitação GetItem a um cluster do DAX e que não haja itens correspondentes no cache de itens do DAX. Isso faz com que o DAX leia o item correspondente na tabela subjacente do DynamoDB. Se o item não existir no DynamoDB, o DAX armazenará um item vazio no cache de itens e retornará esse item vazio para a aplicação. Agora, suponha que a aplicação envie outra solicitação GetItem para o mesmo item. O DAX encontrará o item vazio no cache de itens e o retornará imediatamente para a aplicação. Ele não consulta o DynamoDB.

Uma entrada de cache negativa permanece no cache de itens do DAX até que o TTL do item expire, a LRU seja invocada ou o item seja modificado via PutItem, UpdateItem ou DeleteItem.

O cache de consulta do DAX manipula resultados de cache negativos de maneira semelhante. Se uma aplicação executar uma operação Query ou Scan e o cache de consultas do DAX não contiver um resultado armazenado em cache, o DAX enviará a solicitação para o DynamoDB. Se não houver itens correspondentes no conjunto de resultados, o DAX armazenará um conjunto de resultados vazio no cache de consultas e retornará esse conjunto vazio à aplicação. Solicitações Query ou Scan subsequentes produzem o mesmo conjunto de resultados (vazio), até que o TTL desse conjunto de resultados tenha expirado.

Estratégias para gravações

O comportamento de gravação simultânea do DAX é apropriado para muitos padrões de aplicações. No entanto, existem alguns padrões de aplicativo em que um modelo de gravação simultânea pode não ser apropriado.

Para aplicações sensíveis à latência, a gravação por meio do DAX incorre em um salto de rede extra. Portanto, uma gravação no DAX é um pouco mais lenta que uma gravação diretamente no DynamoDB. Se a sua aplicação for sensível à latência de gravação, você poderá reduzir essa latência gravando diretamente no DynamoDB. Para ter mais informações, consulte Write-around (gravação direta).

Para aplicações que fazem uso intensivo de gravações (como aqueles que executam carregamento de dados em massa), talvez não seja desejável gravar todos os dados via DAX porque apenas uma porcentagem pequena deles é lida pela aplicação. Quando você grava grandes quantidades de dados via DAX, ele deve chamar seu algoritmo LRU de forma a liberar espaço no cache para que os novos itens sejam lidos. Isso diminui a eficácia do DAX como um cache de leitura.

Quando você grava um item no DAX, o estado do cache de itens é alterado para acomodar o novo item. (Por exemplo, o DAX talvez precise remover dados antigos do cache de itens para liberar espaço para o novo item.) O novo item permanece no cache de itens, sujeito ao algoritmo LRU do cache e à configuração de TTL do cache. Enquanto o item persistir no cache de itens, o DAX não lerá o item novamente no DynamoDB.

Gravação simultânea

O cache de itens do DAX implementa uma política de gravação simultânea (write-through). Para ter mais informações, consulte Como o DAX processa gravações.

Quando você grava um item, o DAX garante que o item em cache seja sincronizado com o item existente no DynamoDB. Isto é útil para aplicativos que precisam repetir a leitura de um item logo depois de gravá-lo. No entanto, se outras aplicações gravarem diretamente em uma tabela do DynamoDB, o cache de itens do DAX não estará mais em sincronia com o DynamoDB.

Para ilustrar isso, considere dois usuários (Alice e Bob) que estão trabalhando com a tabela ProductCatalog. Alice acessa a tabela usando o DAX, mas Bob ignora o DAX e acessa a tabela diretamente no DynamoDB.

Diagrama de fluxo de trabalho que mostra etapas numeradas de como Alice e Bob acessam uma tabela usando o DAX e o DynamoDB.
  1. Alice atualiza um item na tabela ProductCatalog. O DAX encaminha a solicitação ao DynamoDB, e a atualização é bem-sucedida. Em seguida, o DAX grava o item em seu cache de itens e retorna uma resposta bem-sucedida a Alice. Desse ponto em diante, até que o item seja finalmente removido do cache, qualquer usuário que ler o item no DAX o verá com a atualização de Alice.

  2. Pouco depois, Bob atualiza o mesmo item do ProductCatalog gravado por Alice. No entanto, Bob atualiza o item diretamente no DynamoDB. O DAX não atualiza automaticamente o cache de itens em resposta às atualizações feitas no DynamoDB. Portanto, os usuários do DAX não veem a atualização de Bob.

  3. Alice lê novamente o item do DAX. O item está no cache de itens e, portanto, o DAX o retorna para Alice sem acessar a tabela do DynamoDB.

Nesse cenário, Alice e Bob veem representações diferentes do mesmo item do ProductCatalog. Esse será o caso até que o DAX remova o item do cache de itens ou até que outro usuário atualize o mesmo item novamente usando o DAX.

Write-around (gravação direta)

Se a sua aplicação precisa gravar grandes quantidades de dados (como um carregamento em massa de dados), talvez faça sentido ignorar o DAX e gravar os dados diretamente no DynamoDB. Essa estratégia de gravação direta (write-around) reduz a latência de gravação. No entanto, o cache de itens não permanece em sincronia com os dados no DynamoDB.

Se você optar por usar uma estratégia de gravação direta (write-around), lembre-se de que o DAX preenche o cache de itens sempre que as aplicações usam o cliente do DAX para ler os dados. Isso pode ser vantajoso em alguns casos porque garante que apenas os dados lidos com mais frequência sejam armazenados em cache (não os dados gravados com mais frequência).

Por exemplo, considere um usuário (Charlie) que deseja trabalhar com uma tabela diferente, a tabela GameScores usando o DAX. A chave de partição de GameScores é UserId e, portanto, todas as pontuações de Charlie teriam o mesmo UserId.

Diagrama de fluxo de trabalho que mostra as etapas numeradas de como Charlie trabalha com uma tabela do DynamoDB usando o DAX.
  1. Charlie deseja recuperar todas as suas pontuações e, para isso, envia uma Query para o DAX. Supondo que essa consulta não foi emitida antes, o DAX a encaminha ao DynamoDB para processamento. Ele armazena os resultados no cache de consultas do DAX e retorna os resultados a Charlie. O conjunto de resultados permanece disponível no cache de consultas até ser removido.

  2. Agora, suponha que Charlie jogue uma partida de Meteor Blasters e obtenha uma pontuação elevada. Charlie envia uma solicitação de UpdateItem ao DynamoDB, modificando um item na tabela GameScores.

  3. Por fim, Charlie decide executar novamente sua Query anterior para recuperar todos os dados de GameScores. Charlie não vê sua alta pontuação do jogo Meteor Blasters nos resultados. Isso ocorre porque os resultados da consulta vêm do cache de consultas, e não do cache de itens. Os dois caches são independentes um do outro e, portanto, uma alteração em um cache não afeta o outro.

O DAX não atualiza conjuntos de resultados no cache de consultas com os dados mais atuais do DynamoDB. Cada conjunto de resultados no cache de consultas é atual no momento em que a operação Query ou Scan foi executada. Portanto, os resultados da Query de Charlie não refletem sua operação PutItem. Esse será o caso até que o DAX remova o conjunto de resultados do cache de consultas.