Single-Table design com DynamoDB - um caso de uso real
Tags: webdev, dynamodb, singletable, aws
Published: 2023-03-18 18:36:05

**O AWS DynamoDB**

O Amazon DynamoDB é um serviço de banco de dados NoSQL totalmente gerenciado que fornece desempenho rápido e previsível com escalabilidade contínua. O DynamoDB visa manter o desempenho consistente, não importa o tamanho do banco de dados ou o número de consultas simultâneas.

Ele faz a distribuição dos dados e do tráfego de forma automática por um número suficiente de servidores, o que faz com que possa lidar com a capacidade de solicitações e de dados armazenados, mantendo o desempenho rápido e consistente.

Todas as requisições efetuadas para o DynamoDB são via API, que é o maior contraste com os demais sistemas de banco de dados conhecidos. A sua autenticação e autorização é feita via AWS IAM, sendo assim ele acaba sendo utilizado como todos os demais serviços da AWS. 

O DynamoDB suporta dois tipos de modelo para armazenamento. Ele pode ser usado como um banco de dados que chamamos de Chave-Valor (key-value), onde podemos pensar como uma tabela hash de grandes proporções, consistente, de ótimo desempenho e distribuída.
O problema do modelo chave-valor é que só conseguimos recuperar um registro por vez. Para modelos de dados mais complexos, onde precisamos retornar, por exemplo, mais registros em uma única consulta, podemos estruturar no DynamoDB, o padrão conhecido como wide-column, que é uma versão melhorada de uma hash table, onde você pode utilizar o hash para recuperar o valor e cada registro terá uma [árvore-B](https://en.wikipedia.org/wiki/B-tree) associada a ele.

**Conceitos relevantes**

Temos 5 conceitos básicos que precisamos conhecer antes de seguir falando do DynamoDB:

1. Tables 
Conjunto de registros que devem ser armazenados juntos. Difere-se dos outros sistema de banco de dados relacional, pois pode armazenar diversos tipos de dados em uma mesma tabela. Como as tabelas não possuem um esquema definido (schemaless), não é necessário declarar todas as colunas.

2. Item 
O item é um único registro em uma tabela do DynamoDB

3. Attributes
Similares as colunas em um banco de dados relacional, com a ressalva que os atributos não são  necessários em todos os itens. Os atributos tem um tipo definido, que afeta quais operações podem ser realizadas com eles, e podem ser divididos em 3 categorias
● Escalares: representam um valor simples: string, número, booleano, binário ou null
● Complexos: representam agrupamentos com atributos aninhados arbitrários, existem dois tipos deles: lists e maps. Podemos usar
tipos de atributos complexos para conter elementos relacionados
● sets: representam valores múltiplos e únicos. Há três: string, números e binários. São úteis para rastrear a exclusividade em um domínio.
Os tipos escalares são usados para a maioria dos atributos, mas os de tipo set são mais usados para itens exclusivos, facilitando as buscas e rastreamentos. 

4. Primary keys 
Ao criar uma tabela no DynamoDB, é necessário criar uma primary key. Ela pode ser simples, consistindo de um único valor, ou composta. Cada item da tabela deverá ter uma primary key, e será identificado por ela. Definir a primary key de uma tabela é a parte mais importante da modelagem de dados no DynamoDB. 
As primary keys simples são chamadas de Partition Key. Já as compostas, são as Partition Keys e as Sort Keys. As primary keys vão determinar o seu padrão de acesso aos itens da tabela. Uma chave primária simples vai permitir recuperar um único item por vez. Já chaves compostas permitem que seja possível recuperar mais itens em uma única busca.

5. Secondary indexes 
São índices para ajudar a recuperar adequadamente dados no DynamoDB de maneira mais eficiente. Ao criarmos um secondary index, precisamos especificar a chave esquema para ele, semelhante ao que é feito com as primary keys.
Secondary index podem ser:
● local: usa a mesma partition key da tabela, mas com uma sort key diferente, eficiente quando precisamos filtrar os dados. Ele deverá sempre ser criado no momento da definição da tabela.
● global: é totalmente independente da tabela principal, podemos usar qualquer atributo como partition key, e ela não precisa ser única. Pode ser definido depois que a tabela já foi criada.

**Tipos de modelagem: multi-table e single-table**
Com o DynamoDB, podemos trabalhar com mais de um tipo de modelagem para os seus dados, e alguns pontos de sua aplicação devem ser analisados antes de decidir por qual modelo seguir.
Uma premissa básica é que você não deve modelar o DynamoDB como um banco de dados relacional e deve aprender os princípios de modelagem de dados do DynamoDB.
As duas formas de modelagem de dados no DynamoDB são a multi-table e a single-table, e é importante verificar alguns pontos para decidir qual das duas usar:

**Single-table**: uma única tabela, que terá seus dados de forma desnormalizada, seguindo alguns conceitos que veremos adiante. O que precisa ser levado em questão ao decidir usar esse modelo:
- A tabela a ser definida se aplica a um único serviço, se tiver mais de um serviço na mesma aplicação o ideal é que cada um deles tenha a sua própria tabela.
- Junções de dados: dados que sempre devem ser recuperados juntos (exemplo: clientes e pedidos do cliente). Definindo um bom padrão de acesso aos dados (veremos isso mais adiante), otimiza a busca e gravação dos dados.
- Reduzir custos (leitura e gravação): é padrão para os bancos NoSQL, gravar documentos desnormalizados, com todos os dados, e dessa forma ser mais eficiente ao recuperar todas as informações numa única leitura. Porém aqui cabe um ponto de atenção, caso haja muita alteração nos dados, dado o tamanho do documento na gravação. Algumas vezes é melhor separar em tipos de documentos distintos e ter bons padrões de acesso para recuperar os dados.
- Redução de carga operacional: cada tabela do DynamoDB é uma parte separada da infra-estrutura, que requer configuração, monitoramento, alarmes e backups. Dessa forma, com uma única tabela, você terá uma carga operacional reduzida.

**Multi-table**: várias tabelas em um mesmo banco DynamoDB. Algumas necessidades podem levar a optar por esse modelo:
- Mais de um serviço na mesma aplicação.
- Vários fluxos de trabalho dentro da mesma aplicação: exemplo um fluxo de captura de dados que grava os dados em uma tabela e após um processamento atualiza agregações e ou compartilha eventos entre sistemas.
- Operações de processamento analítico e exportação de dados: O DynamoDB é um banco projetado para ter um grande número de atualizações simultâneas em registros individuais e cargas de trabalho. Ele não é bom para operações de processamento analítico, para esse tipo de necessidade os dados precisam ser separados em outras tabelas e ainda utilizar outros serviços (como o Athena ou o Redshift) para agregações de dados.

O primeiro passo para definir a modelagem a ser usada é se certificar de entender os princípios da modelagem com o DynamoDB. 

**Conceitos do dynamoDB para o single table**
Existem diversos outros conceitos relacionados ao DynamoDB que recomendamos a leitura e aprofundamento, mas para esse nosso artigo, queremos explorar mais como fazer a modelagem dos dados usando uma single table, e como colher bons resultados dessa forma de modelar os dados.

Para começar a pensar na modelagem, precisamos seguir alguns passos:

● Conhecer a sua aplicação
● Criar diagramas de entidade-relacionamento (DER): listar as diferentes entidades da sua aplicação e como elas se relacionam entre si.
● Escrever todos os padrões de acesso aos dados: com o DER em mãos, é possível começar a listar todos os padrões de acesso aos dados. 
● Modelar a estrutura de chave primária
● Satisfazer outros padrões de acesso com índices secundários

A modelagem de dados no DynamoDB é toda baseada na forma como serão realizados os acessos aos dados. A modelagem não pode ocorrer de forma genérica, permitindo uso flexível dos dados no futuro. Os dados deverão ser moldados para se ajustarem aos padrões de acesso.

**Contexto sobre a API que usamos DynamoDB**
Temos um produto (HiInbox) que consiste no tratamento de atendimentos a consumidores provenientes de diversas origens (E-Mail, mídia social, sistemas internos, API de uso externo). 
Esse produto tem a necessidade de efetuar a distribuição automática desses atendimentos, entre os diversos grupos e categorias que cada cliente pode ter, bem como num futuro breve, fazer a distribuição para os atendentes de acordo com a carga de trabalho de cada um.
Uma outra necessidade desse produto é automatizar ações específicas, como por exemplo excluir um atendimento dado determinado assunto/descrição, sem que tenha que passar por um atendente para tal ação.
Como necessidades como essa podem aparecer em outros produtos que temos, vimos aqui a oportunidade de ter um microserviço que fosse responsável por armazenar essas as regras e ações a serem executadas e também por avaliar o conjunto de informações de um determinado atendimento, aplicar essas regras e devolver a ação que o produto deve executar diante do que foi avaliado.
Assim surgiu o HiRuler, um microserviço que tem como responsabilidade a gestão de regras. As regras são definidas por critérios e ações. Elas são criadas pelo sistema origem e seu conteúdo (tanto nos critérios quanto nas ações) está totalmente ligado ao sistema origem. A análise das regras se dá quando o sistema origem envia um “documento” para o sistema de regras, que por sua vez executa os critérios e retorna as ações para o sistema origem.

**Modelagem do banco no DynamoDB**

Seguindo as etapas ditas anteriormente, vamos aos passos que seguimos para a modelagem usada nessa aplicação.

- Diagramas de entidade-relacionamento 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r96b62zqvav5iet4i1k7.jpg)

OBS: 
- Uma regra, poderá ser “filha” de uma regra previamente existente, formando então uma árvore de regras aninhadas.
- Uma regra poderá ter N critérios
- Uma regra poderá ter N ações

- Descrever todos os padrões de acesso: Analisando toda a aplicação e todas as ações que ela deveria possibilitar chegamos a seguinte matriz de padrões de acesso

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ej1sn02008wmrhi79c5c.JPG)

Dada essa definição dos padrões de acesso, passamos à etapa seguinte que é a de modelar as chaves primárias e secundárias para as operações.

- Modelando as chaves primárias e os índices secundários:
Para chegar no modelo final que usamos na nossa API, desenhamos 3 modelos de dados. Definindo então as chaves primárias e os índices globais secundários (GSI).
O primeiro deles foi um modelo mais simples, contemplando exatamente os atributos propostos no DER, e transformando os critérios e ações em atributos das regras.
Nesse modelo, o documento que seria armazenado seria somente um para cada regra, e nele teríamos todos os outros dados como atributos. 
Ao analisar esse modelo pronto, notamos que ele podia deixar brechas e que problemas para extrair as informações certamente seriam encontrados. Além de que esse documento ficaria com um tamanho considerável, dado que a quantidade de critérios e/ou ações que podemos ter numa regra não é limitado.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qee1rzna69tmnni19fvf.JPG)

Esse modelo ficou registrado na documentação interna da api, porém foi descartado.
Abaixo segue as imagens da representação dos dados para esse modelo (Primary Key e Global Secondary Index) 
- Primary Key
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8j8p8o0d3rkh7i48abqu.jpg)

- Global Secondary Index

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzsj1m4k6qhw3sv53qes.jpg)

No segundo modelo, fizemos algumas quebras, principalmente considerando os critérios e as ações como entidades separadas. Nesse modelo, teríamos 3 tipos de documentos e seriam semelhantes a representação das tabelas no DER que fizemos anteriormente.
Depois de criado, vimos que esse modelo iria dificultar alguns acessos aos dados, dado as possibilidades	que temos de manipulação principalmente nos critérios e nas ações.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4g0qbjsq75qrr6clbj1.JPG)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5slxvq15mif8g5979ec.JPG)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bh10tpnypai4ryeuuvh.JPG)
Esse modelo também ficou registrado na documentação interna da api, porém foi descartado. Abaixo segue as imagens da representação dos dados para esse modelo (Primary Key e Global Secondary Index) 

- Primary Key

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqyfg871gfko2fg3j5cz.jpg)

- Global Secondary Index

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pjg5yb7i8ee4ajht9gxl.jpg)

O terceiro e último modelo que desenhamos foi o que atendeu as nossas expectativas para acesso e controle dos dados. Nele temos também 3 tipos de documentos, semelhantes a representação das tabelas no DER que fizemos anteriormente, porém conjunto de critérios e ações são sempre um único documento. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38ub4nytcoukka1oujfz.JPG)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/piwg4xaoc42f81wqgx7n.JPG)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ed8fsj5ajxghkhupn8kt.JPG)

- Primary Key

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0swnum5352m25w1vvtj7.jpg)

- Global Secondary Index

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rztfik2cd9p25fq3yphd.jpg)

Dessa forma, para cada padrão de acesso tivemos definido o seu índice para acesso aos dados:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yjmmv9um4x2otp61jo8.JPG)

**Números da aplicação**

Essa aplicação está em uso desde início de 2022, seguem alguns números de acessos e tempos de requisições:
- Requisições referente a inclusão/alteração/remover registros: 
média de tempo: 100ms
- Requisições de consultas dos dados:
média de tempo: 150ms
- Processamento dos documentos
média de tempo: 50ms
total de documentos processados por semana: 50 mil documentos

**Conclusão**
Modelar uma base de dados com uma única tabela no DynamoDB é um exercício que requer uma visão completa do que a sua aplicação deverá entregar já no início de seu desenvolvimento. Não devemos nos contentar logo com o primeiro modelo que desenhamos, pois ele pode conter erros e isso poderá custar horas de desenvolvimento para então refazer toda a modelagem.

O exercício de modelar uma base como a que fizemos nessa aplicação requer tempo para reflexão e também revisão por pares, questionamentos sobre as escolhas dos índices que serão criados.

Uma vez bem definida a modelagem, o projeto segue sem intercorrências relativas à base de dados. 

Aqui colocamos apenas uns poucos conceitos sobre o DynamoDB e também sobre a forma de modelagem dos dados usando uma única tabela. Fica a recomendação das fontes usadas para compor esse artigo, onde é possível explorar esses tópicos aqui abordados e muito mais que não foi possível abordar aqui.

**Fontes**
[Amazon DynamoDB Documentation] (https://docs.aws.amazon.com/dynamodb/?icmpid=docs_homepage_featuredsvcs)

[Single-table vs. multi-table design in Amazon DynamoDB]
(https://aws.amazon.com/pt/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/)

[#10 Meetup - Começando com o DynamoDB: Conceitos principais para usar profissionalmente] (https://youtu.be/pdMjnuIiYo8)

[Single-Table Design with DynamoDB - Alex DeBrie, AWS Data Hero] (https://youtu.be/BnDKD_Zv0og)

[7 Common DynamoDB Patterns for Modeling and Building an App with Alex De Brie] (https://youtu.be/Q6-qWdsa8a4)

[NoSQL Workbench for DynamoDB] (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.html)

The DynamoDB Book - Alex DeBrie -  Version 1.0.1, 2020-04-16