O blog da AWS
Escolhendo a chave de partição correta no DynamoDB
Por Gowri Balasubramanian, Arquiteto de Soluções Senior da AWS
Este post aborda considerações e estratégias importantes para escolher a chave de partição correta ao projetar um esquema de banco de dados NoSQL que usa o Amazon DynamoDB. Escolher a chave de partição certa é uma etapa importante para projetar e criar aplicativos escaláveis e confiáveis no DynamoDB.
O que é uma chave de partição?
O DynamoDB suporta dois tipos de chaves primárias:
- Chave de partição (partition key): Uma chave primária simples, composta por um único atributo chamado chave de partição. Os atributos no DynamoDB são semelhantes de várias maneiras ao que conhecemos como colunas em outros sistemas de banco de dados.
- Chave de partição (partition key) e chave de classificação (sort key): Chamado de chave primária composta, esse tipo de chave consiste em dois atributos. O primeiro atributo é a chave de partição e o segundo atributo é a chave de classificação. Veja a seguir um exemplo.
Por que preciso de uma chave de partição?
O DynamoDB armazena dados como grupos de atributos, conhecidos como itens. Os itens são semelhantes às linhas ou registros de outros sistemas de banco de dados. O DynamoDB armazena e recupera cada item com base no valor da chave primária, que deve ser exclusivo. Os itens são distribuídos entre unidades de armazenamento de 10 GB cada, chamadas partições (armazenamento físico interno do DynamoDB). Cada tabela tem uma ou mais partições, conforme mostrado na ilustração a seguir. Para obter mais informações, consulte Partições e distribuição de dados.
O DynamoDB usa o valor da chave de partição como parâmetro de entrada para uma função de hash interna. O resultado da função hash determina a partição na qual o item é armazenado. A localização de cada item é determinada pelo valor de hash de sua chave de partição
Todos os itens com mesmo valor de chave de partição são armazenados juntos e, para chaves compostas, são classificados (ou ordenados) pelo valor da chave de classificação. O DynamoDB divide partições por chave de classificação se o tamanho da coleção crescer mais de 10 GB.
Chaves de partição e limitação de solicitação
O DynamoDB distribui uniformemente o desempenho provisionado – unidades de capacidade de leitura (RCUs) e unidades de capacidade de gravação (WCUs) – entre partições e suporta automaticamente padrões de acesso usando o desempenho que você provisionou. No entanto, se o padrão de acesso exceder 3.000 RCU ou 1.000 WCU para um único valor de chave de partição, as requisições estarão sujeitas a um controle de utilização (throttling) retornando a mensagem de erro ProvisionedThroughputExceededException
.
Leituras ou gravações acima do limite podem ser devido a um dos seguintes problemas:
- Distribuição desigual de dados devido à escolha incorreta da chave de partição
- Acesso frequente a uma mesma chave em uma partição (o item mais popular, também conhecido como “chave de partição quente” ou hot key)
- Taxa de requisições maior do que o desempenho provisionado
Para evitar a limitação de requisições, crie sua tabela do DynamoDB com a chave de partição apropriada para atender aos seus requisitos de acesso e fornecer uma distribuição uniforme dos dados.
Recomendações para chaves de partição
Use atributos de alta cardinalidade. Esses são atributos que têm valores diferentes para cada item, como e-mail, código_do_funcionário
, código_do_cliente
, número_da_sessão
, número_do_pedido
, etc.
Use atributos compostos. Tente combinar mais de um atributo para formar uma chave exclusiva, se ela atender ao seu padrão de acesso. Por exemplo, considere uma tabela Pedidos com código_cliente+código_produto+código_país como chave de partição e data_do_pedido como a chave de classificação.
Armazene em memória (cache) os itens mais populares. Quando houver um grande volume de tráfego de leitura, considere usar o Amazon DynamoDB Accelerator (DAX). O cache atua como um filtro de baixo impacto que impede que leituras de itens excepcionalmente populares inundem partições. Por exemplo, considere uma tabela que tenha informações sobre transações para produtos. Espera-se que algumas ofertas sejam mais populares do que outras durante grandes eventos promocionais, como Black Friday ou Cyber Monday. O DAX é um serviço de cache gerenciado especialmente projetado para o DynamoDB que não exige que os desenvolvedores gerenciem a invalidação de cache, carga de dados no cache, ou o gerenciamento de cluster. Além disso, o DAX é nativamente compatível com as chamadas de API do DynamoDB, para que os desenvolvedores possam incorporá-lo facilmente em aplicativos pré-existentes.
Adicione números ou dígitos aleatórios de um intervalo predeterminado para casos de uso com grande volume de escritas. Suponha que você espere um grande volume de gravações para uma chave de partição (por exemplo, mais de 1000 gravações de 1KB por segundo). Nesse caso, use um prefixo ou sufixo adicional (um número fixo no intervalo padrão, por exemplo, 1—10) e adicione-o à chave de partição.
Por exemplo, considere uma tabela de transações de faturamento. Uma única fatura pode conter milhares de transações por cliente. Como garantimos a exclusividade e a capacidade de visualizar e atualizar os detalhes da fatura para clientes de alto volume?
Abaixo está o design de tabela recomendado para este cenário:
- Chave de partição: Adicione um sufixo aleatório (1-10 ou 1-100) com
NúmeroFatura
, dependendo do número de transações porNúmeroFatura
. Por exemplo, suponha que um únicoNúmeroFatura
contenha até 50.000 itens de 1 KB de tamanho e você espera cerca de 5.000 gravações por segundo. Nesse caso, você pode usar a seguinte fórmula para estimar o intervalo de sufixos: {Número de gravações por segundo * [arredondamento para cima (tamanho do item em KB ,0) * 1 KB] /1000}. Aplicando a fórmula neste cenário, precisaríamos de um mínimo de cinco partições para distribuir as gravações e, portanto, você pode querer definir o intervalo de 1-5.
- Chave de classificação:
CódigoTransaçãoCliente
Chave de partição | Chave de classificação | Atributo1 |
NúmeroFatura+sufixo |
CódigoTransaçãoCliente |
DataFaturamento |
121212-1 | Cliente1_Trans1 | 2016-05-17 01.36.45 |
121212-1 | Cliente1_Trans2 | 2016-05-18 01.36.30 |
121212-2 | Cliente2_Trans1 | 2016-06-15 01.36.20 |
121212-2 | Cliente2_Trans2 | 2016-07-01 01.36.15 |
- Essa combinação nos dá uma boa distribuição entre as partições. Ela também nos oferece a capacidade de usar a chave de classificação para filtrar por um cliente específico (por exemplo, onde
NúmeroFatura
= “121212-1” eCódigoTransaçãoCliente
começa com “Cliente1”).
- Como temos um número aleatório anexado à nossa chave de partição (1—5), precisamos consultar a tabela cinco vezes para um determinado número. Nossa chave de partição pode ser “121212-[1-5]”, então precisamos verificar onde está a chave de partição “121212-1” e
CódigoTransaçãoCliente
começa com “Cliente1”. Precisamos repetir isso para 121212-2, até 121212-5 e depois mesclar os resultados.
Nota:
Depois que o intervalo de sufixos é decidido, não há uma maneira fácil de distribuir os dados ainda mais porque as modificações do sufixo também exigem alterações na aplicação. Portanto, considere como a chave de partição quente pode ser e adicione um sufixo aleatório suficiente (armazenado em buffer) para acomodar o crescimento futuro.
Essa opção induz latência adicional para leituras devido ao número X de solicitações de leitura por consulta
Conforme mencionado na documentação do DynamoDB, uma estratégia de escrita aleatória pode melhorar significativamente o desempenho. Mas é difícil ler um item específico porque você não sabe qual valor de sufixo foi usado ao escrever o item.
Para facilitar a leitura de itens individuais, considere o particionamento usando sufixos calculados, conforme explicado em Uso da fragmentação de gravação para distribuir uniformemente as cargas de trabalho no Guia do desenvolvedor do DynamoDB. Por exemplo, suponha que um grande número de transações de fatura esteja sendo processado, mas o padrão de leitura é retornar um pequeno número de itens para um código-identificador
determinado pelo intervalo de datas. Nesse caso, é mais eficaz distribuir os elementos entre um intervalo de partições usando um determinado atributo, neste caso código-identificador
. Você pode aplicar um algoritmo de hash ao código-identificador
para copiar a chave de partição em vez de usar a estratégia de números aleatórios. Dessa forma, você saberá qual partição consultar e recuperar os resultados da tabela.
Assim como acontece com as tabelas, é recomendável considerar uma abordagem de particionamento de sufixo para índices secundários globais (global secondary indexes ou GSI) se você estiver antecipando um cenário de chave de partição quente com um índice secundário global.
Por exemplo, considere o seguinte design de esquema de uma tabela TransaçõesFatura
. Ele tem uma linha de cabeçalho para cada fatura e contém atributos como o valor total devido e o país da transação (país_transação
), que são exclusivos para cada fatura. Supondo que precisemos encontrar a lista de faturas emitidas para cada país de transação, podemos criar um índice secundário global com país_transação
como sua chave de partição. No entanto, essa abordagem leva a um cenário de gravação de chave de partição quente, porque o número de faturas por país é distribuído de forma desigual.
A tabela a seguir mostra o design recomendado com uma abordagem de particionamento de gravação.
Chave de partição da tabela | chaves de classificação da tabela | Atributo1 | Atributo 2 (chave de partição do GSI) | Atributo 3 (chave de classificação do GSI) | Atributo 4 | Atributo 5 |
NúmeroFatura |
Pedido de chaves | Faturamento | Prefixo aleatório | país_transação |
Montante total | Moeda |
121212 | Maestrelement | 2018-05-17 T1 | (1-N) | USA | 10,000 | USD |
121213 | Maestrelement | 2018-04-01 T2 | (1-N) | USA | 500,000 | USD |
121214 | Maestrelement | 2018-04-01 T2 | (1-N) | FRA | 500,000 | EUR |
Abaixo estão os índices secundários globais (GSI) do cenário anterior
Chave de partição GSI | Chave de pedido GSI | Atributo projetado 1 | Atributo projetado 2 |
Prefixo aleatório | país_transação |
NúmeroFatura |
Outros Atributos |
(1-N) | USA | 121212 | |
(1-N) | USA | 121213 | |
(1-N) | FRA | 121214 |
No exemplo acima, talvez você queira identificar a lista de números de fatura associados aos EUA. Nesse caso, você pode fazer uma consulta no índice secundário global (GSI) com chave_de_partição=(1-N)
e país_transação=EUA
.
Anti-padrões para chaves de partição
Uso de sequências exclusivas ou IDs gerados pelo próprio banco de dados como uma chave de partição, especialmente quando você estiver migrando de bancos de dados relacionais. É comum usar sequências (schema.sequence.NextVal) como chave primária para exigir exclusividade nas tabelas de uma base Oracle. As sequências geralmente não são usadas para acessar os dados.
A seguir, um exemplo de experimento de esquema para uma tabela de pedidos que foi migrada do Oracle para o DynamoDB. A chave de partição da tabela principal (TransactionID) está sendo atualizada com um UID. Um GSI é criado com atributos OrderID e Order_Date para fins de consulta.
Chave de partição | Atributo 1 | Atributo 2 |
TransactionID | OrderID | Order_Date |
1111111 | Cliente1-1 | 2016-05-17 01.36.45 |
1111112 | Cliente1-2 | 2016-05-18 01.36.30 |
1111113 | Cliente2-1 | 2016-05-18 01.36.30 |
Os possíveis problemas com essa abordagem são os seguintes:
- Você não pode usar o
TransactionID
para fins de consulta, portanto, você perde a capacidade de usar a chave de partição para realizar uma pesquisa rápida de dados - Apesar de poder consultar os dados atrvés do GSI, ele só suporta consistência eventual, com custos adicionais para leituras e gravações
Nota: Você pode usar o recurso de Expressões de condição em vez de sequências para impor exclusividade e evitar sobrescrever um item
Usar atributos de baixa cardinalidade como código_do_produto como chave de partição e data_do_pedido como chave de classificação aumenta consideravelmente a probabilidade de problemas de partição quente. Por exemplo, se um produto for mais popular, as leituras e gravações dessa chave serão altas, resultando em problemas de limitação (throttling).
Com exceção do comando SCAN
, as operações da API do DynamoDB exigem um operador de igualdade (EQ) na chave de partição para tabelas e GSIs. Assim, a chave de partição deve ser algo que a aplicação possa consultar facilmente com uma pesquisa simples. Um exemplo é o uso de chave=valor (key=value),
que retorna um único item ou alguns itens. Há um limite de 1 MB para itens que podem ser retornados por meio de uma única operação de consulta. Isso significa que você deve trabalhar com paginação usando o atributo lastEvaluatedKey
se esse limite de 1MB for excedido, o que não é ideal
Resumindo: Não utilize chaves primárias do banco de dados de origem sem antes analisar o modelo de dados e os padrões de acesso da tabela do DynamoDB de destino.
Conclusão
Quando se trata de estratégias de chave de partição do DynamoDB, nenhuma solução única se encaixa em todos os casos de uso. Você precisa avaliar várias abordagens com base na ingestão de dados e no padrão de acesso e, em seguida, escolher a chave mais apropriada com a menor chance de atingir problemas de limitação (throttling). Junto com o melhor design de chave de partição, o recurso de capacidade adaptável do DynamoDB pode proteger sua aplicação contra problemas que limitam as consultas por causa de um padrão de acesso a dados irregular.
Para obter mais instruções sobre como projetar esquemas para vários cenários, consulte Design do NoSQL para o DynamoDB no Guia do desenvolvedor do DynamoDB.
Este artigo foi traduzido do Blog da AWS em Inglês.
Sobre o autor
Gowri Balasubramanian é Arquiteto de Soluções Senior na Amazon Web Services. Ele trabalha com clientes da AWS para fornecer orientação e assistência técnica sobre NoSQL e serviços de banco de dados relacional, ajudando-os a melhorar o valor de suas soluções ao usar a AWS.
Sobre o tradutor
Camilo Leon é Arquiteto de Soluções Senior especializado em base de dados na AWS.