Amazon DynamoDB: Lições aprendidas usando o design de tabela única e GraphQL em produção

Image for post
Image for post

O DynamoDB é um animal poderoso, porém complicado. Embora ele permita escalabilidade insana, ele tem algumas limitações estritas que você precisa estar ciente. Também é impossível usar um banco de dados NoSQL com uma mentalidade RDBMS. A mudança de pensamento é necessária, e esse processo é trabalhoso. Depois de quase um ano desenvolvendo uma API GraphQL totalmente serverless e rodando em produção com milhares de usuários, apresento meus aprendizados nesse esforço.

Planejar seus padrões de acesso e consultas é essencial

Não posso enfatizar o suficiente o quão importante é. Como você não pode alterar índices primários ou LSIs (Local Secondary Indexes/Índice Local Secundário), a única opção é criar uma nova tabela, migrar os dados com algumas transformações e excluir a tabela antiga. Esse processo é torturante, especialmente depois que você está em produção e deseja evitá-lo a todo custo. Se você não sabe fazer isso, recomendo dois artigos: um de Jeremy Daly e o segundo de Forrest Brazeal.

Esta etapa não é apenas uma tarefa puramente de engenharia, mas combina com as competências de negócios e produtos. É por isso que você deve participar da sessão de planejamento o mais rápido possível para entender melhor os casos de uso de negócios e propor soluções amigáveis ao desenvolvedor. Requisitos precisos resultam em um desenvolvedor feliz que são iguais a um produto entregue e o prazo cumprido.

O atributo “model” é vantajoso para tipos de entidade distintos

No nosso caso de uso, ter um atributo “model” como chave primária em um dos GSIs (Global Secondary Index/Índice Global Secundário), que sempre indicava o tipo de linha, foi muito útil. Com uma consulta simples em que o modelo era um hashKey, poderíamos obter todos os membros, canais, funções, audiências etc.

Invista algum tempo em abstrações apropriadas e crie seu próprio ORM opinativo

Ao começar a escrever algum código na camada de persistência, você notará imediatamente que muitas coisas estão se repetindo e a maioria das chamadas de API exige parâmetros complexos, como UpdateExpression ou ExpressionAttributeValues. Estes devem ser abstraídos em assinaturas de função mais diretas.

Aqui estão algumas idéias:

  • Cada chamada exigirá TableName(exatamente o mesmo com design de tabela única) e IndexName. Escrever isso em cada chamada é duplicação desnecessária.

Se você não tiver certeza do que precisa, considere usar o DynamoDB Toolbox, uma excelente biblioteca não-ORM do Jeremy Daly. Infelizmente, isso não estava disponível um ano atrás, quando meu projeto começou, mas se eu fosse começar um projeto hoje, eu a usaria sem pensar!

Use “keepAlive HTTP” como truque de otimização de Matt Lavin

Por padrão, sempre que você faz uma operação com o DynamoDB, um novo handshake de três vias é estabelecido. Isso leva tempo desnecessário. Você pode corrigir isso substituindo httpOptions.agent por um personalizado nas opções do AWS SDK da seguinte maneira:

const agent = new https.Agent({
keepAlive: true,
maxSockets: 50,
rejectUnauthorized: true
});
AWS.config.update({ httpOptions: { agent }});

Você pode ver a diferença neste post que traduzi do Yan Cui.

Remova “dynamodb:Scan” da sua lista de IAM Roles/Policies

Esta dica eu vi com Jared Short. Se você deseja impedir que seus desenvolvedores escrevam scans (o que é uma prática ruim), apenas negue a ação dynamodb:Scan no IAM em que os desenvolvedores e funções do aplicativo usam. Técnica brutal, porém super simples e eficaz, que utiliza o IAM não apenas para segurança, mas também para aplicar as melhores práticas.

Use o X-Ray e Contributor Insights para encontrar gargalos e padrões de acesso problemáticos

Mesmo que não tenhamos usado o Contributor Insights no projeto mencionado, porque ele foi anunciado como parte do “pre re:Invent hype release train”, eu dei uma chance para ele no meu projeto paralelo. Essa ferramenta de diagnóstico é definitivamente útil para ajustar o desempenho e encontrar gargalos, localizando as chaves de partição acessadas com mais frequência.

No entanto, o que usamos ativamente em nosso aplicativo em produção é o AWS X-Ray. Com os subsegmentos, conseguimos descobrir quais subconsultas de nossas consultas complexas do GraphQL estavam causando lentidão nas respostas, nos possibilitando reescrever essas partes do aplicativo.

O que também foi útil foi o Mapa de Serviço. Apenas uma olhada mostrou que nosso problema está na função do autorizador personalizado, que buscava muitos dados e causava lentidão em toda a API.

Esteja ciente da falta de integridade referencial e suas consequências

Como o DynamoDB é eventualmente consistente e não garante a integridade dos dados (o que eu quero dizer com isso, por exemplo, entidades fracas em relações Muitos-para-Muitos, não é garantido que o modelo referenciado exista), você precisa prestar atenção especial no código do aplicativo ao verificar nullou undefined. Isso pode levar a inúmeros ReferenceErrors.

No nosso caso, quando estávamos usando o GraphQL, isso também levou a casos que violavam o contrato especificado no esquema. Alguns atributos como member: Member! (!significa que a API garante que o membro modelo está presente) começou a retornar erros.

Lição aprendida: assuma sempre que alguns dados podem estar ausentes.

Considere remover registros órfãos e entidades fracas de forma assíncrona

Imagine um cenário em que você tenha um sistema com um grupo de entidades que possa ter muitos membros. Os membros são vinculados ao grupo usando registros separados em que pk: Group-ID e sk: Member-ID. Nesse cenário no mundo SQL, recursos como FOREIGN KEYS e ON DELETE CASCADEgarantiriam que, uma vez excluído o "Grupo", todos os registros de "associação" também fossem excluídos.

Infelizmente, esse não é o caso no DynamoDB.

Você pode dizer: “…vamos fazer isso de forma síncrona!”. Isso não é uma boa idéia. Isso provavelmente levaria muito tempo dentro da Lambda e deveria ser distribuído.

Mas você pode perguntar: “…por que remover? O DynamoDB não é infinitamente escalável? Quais são os contras de remover?”

  • Economiza espaço, o que significa menos dinheiro gasto. Lembre-se de que custa 0,25 USD por GB-mês

Use VPC Endpoints se possível

VPC Endpoints são pequenas coisas poderosas que permitem que recursos dentro de sua VPC interajam com outros serviços da AWS sem sair da rede da Amazon. Esse truque não apenas aumenta a segurança, mas também torna as interações com o DynamoDB mais rápidas. Ah, e suas instâncias não precisam de IP público. Lembre-se de que, assim como o próprio DynamoDB, isso também possui algumas limitações, que incluem:

  • Você não pode acessar o DynamoDB Streams

Você pode ler mais sobre VPC Gateway Endpoints para DynamoDB aqui.

Evite armazenar itens grandes

No começo, cometemos esse erro — começamos a armazenar blobs de avatar dentro do atributo avatar. Não foi uma boa ideia. Como uma única consulta retorna apenas um conjunto de resultados que se encaixa dentro do limite de tamanho de 1 MB, algumas vezes vimos algumas chamadas retornando apenas três itens com eficácia, tornando tudo mais lento do que deveria ser. Felizmente, resolver o problema foi bem fácil — fizemos o upload de todos os blobs para o S3 e salvamos o link que o referenciava na tabela.

Sempre use “Limit” em consultas e paginação

No nosso caso, o DynamoDB atendeu ao propósito da camada de persistência da API da plataforma da comunidade. Em casos de uso como esse, de acordo com nossas análises, é mais provável que os usuários se interessem nos 10 últimos anúncios ou mensagens em um canal. Se eles gostariam de ver mais, eles podem solicitá-las e o aplicativo usaria paginação. Essa abordagem de retornar uma quantidade mínima de dados inicialmente nos deu uma série de benefícios:

  • O banco de dados retornou uma quantidade menor de dados; portanto, nossa API também retornou uma quantidade menor de dados, tornando tudo mais rápido e responsivo

Favoreça “FilterExpressions” ao invés de funções “Array.filter”

Embora seja tentador usar os recursos nativos do Array.filterporque é mais elegante, prefira usar o FilterExpressions, apesar de ser um pouco complicado. Eu sei que requer a criação de uma seqüência de caracteres que mapeia para ExpressionAttributeValues e ExpressionAttributeNames, mas vale a pena no desempenho. Como a filtragem é feita no nível do banco de dados, menos dados são consumidos e menos dados são retornados. Isso converte diretamente em menos dinheiro gasto em transferência de dados, computação e memória necessários para processá-lo.

Esteja ciente dos limites do DynamoDB

Passe algum tempo lendo os documentos sobre limitações, sério, isso nos salvaria de muitas frustrações. Algumas coisas que foram surpreendentes para nós incluem:

  • BatchWriteItem suporta até 25 itens. Isso significa que, se você quiser escrever 100 itens, precisará dividir seu array em 4 partes e executar o BatchWriteItem quatro vezes, aguardando com Promise.all.

Sempre use “ExpressionAttributeNames”

Existem várias palavras-chave reservadas no DynamoDB que, ao invés de adivinhar se os nomes dos seus atributos são reservados ou não, pense que todo nome de atributo é reservado. Isso deve ser ocultado dos desenvolvedores pela sua abstração do tipo ORM, que mencionei anteriormente.

TTL não garante que seus itens serão removidos imediatamente

Eu me deparei com essas informações enquanto navegava no Twitter um dia. O atributo time-to-live (TTL) não garante que o item seja removido imediatamente após o tempo selecionado. Ele será removido com um atraso, às vezes levando até 48h. Isso pode levar você a retornar resultados incorretos se depender apenas desse mecanismo.

Para evitar isso, como afirma a nota oficial: “use uma expressão de filtro que retorne apenas itens em que o valor de expiração do tempo de vida útil é maior que o tempo atual”.

Use SQS para armazenar em buffer grandes operações de “Write”, “Update” e “Delete”

Um truque comum para impedir que sua tabela consuma muito WCU (Write Capacity Units/Unidades de Capacidade de Gravação) ou tenha suas solicitações limitadas devido à entrada de muitos dados é executar operações de buffer-write e executá-las de forma assíncrona, se possível. É a recomendação oficial da AWS, eu também escrevi um post sobre isso.

Se você precisar de conhecimentos sobre DynamoDB ou Serverless, não hesite em entrar em contato comigo.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store