Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

DynamoDB: o que a maioria dos times erra antes de escrever uma linha de código

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

By Joseph AllamJul 20, 20268 min read
Joseph Allam

About Joseph Allam

Databases have been my craft for a long time — long before the cloud, and deep into it across AWS and GCP. I know what breaks, what costs too much, and what needs rethinking before it hurts. I also know how to bring AI into the work: into how databases are designed, operated, and accessed by the intelligent systems being built on top of them.

My personal page

No Prime Day 2025, o Amazon DynamoDB atingiu o pico de 151 milhões de requisições por segundo. É o banco de dados por trás de alguns dos workloads mais exigentes do planeta — e é assim há quase duas décadas. A maioria dos engenheiros que trabalha com AWS já ouviu falar dele. Muitos já o usaram. Mas no DynamoDB Immersion Day que conduzi recentemente com o time da AWS, o mesmo problema apareceu em quase todas as sessões: times que haviam escolhido o DynamoDB pelos motivos certos continuavam sofrendo porque projetavam schemas do jeito errado.

Este post é sobre isso. Não sobre o básico do DynamoDB (há bastante conteúdo sobre isso por aí), mas sobre o raciocínio de design que separa os times que fazem tudo funcionar bem daqueles que acabam com custos descontrolados e tabelas que não escalam.

PerfectScale™ for Kubernetes

Ready to optimize?

Get your free Kubernetes savings analysis

O DynamoDB é a escolha certa?

Antes de entrar no design, vale ser direto sobre adequação. O DynamoDB é uma boa escolha quando você precisa de latência consistente de milissegundos de um dígito em praticamente qualquer escala, seus padrões de acesso são conhecidos e previsíveis desde o início e seu workload é intensivo em leitura/escrita, e não analiticamente complexo. Bons exemplos: APIs voltadas ao usuário, armazenamento de sessões, leaderboards, carrinhos de compras, ingestão de IoT, event sourcing.

É uma escolha ruim quando você precisa de consultas ad-hoc em muitas dimensões, seus padrões de acesso ainda estão evoluindo ou você lida com joins relacionais complexos. Se você está substituindo um banco de relatórios ou construindo algo em que os requisitos de consulta vão mudar mês a mês, vai brigar com o DynamoDB o tempo inteiro.

O tamanho do item importa mais do que a maioria dos times imagina

Antes de se comprometer com o DynamoDB, modele o custo direito. O DynamoDB cobra a capacidade de escrita em incrementos de 1 KB, arredondados para cima. No modo on-demand, um item de 20 KB consome 20 Write Request Units (WRUs); no modo provisionado, a mesma escrita consome 20 Write Capacity Units (WCUs). As leituras funcionam de forma parecida, cobradas em incrementos de 4 KB por Read Capacity Unit (RCU), então o tamanho do item determina os dois lados do seu custo de capacidade.

Parece simples, mas o tamanho do item é uma das variáveis mais negligenciadas quando os times modelam os custos do DynamoDB. Vemos com frequência clientes migrando de bancos relacionais como o PostgreSQL, onde o conceito de cobrança por byte nas escritas não existe, e suas estimativas iniciais de custo ficam bem fora da realidade porque não levaram o tamanho do item em conta corretamente. Acertar esse número antes de se comprometer muda a economia por completo.

A questão não é que o DynamoDB seja caro. A questão é que o modelo de custo é diferente do que a maioria dos engenheiros está acostumada, e as variáveis que o determinam (tamanho do item, eficiência dos padrões de acesso, capacidade on-demand vs. provisionada) precisam ser entendidas antes de você se comprometer, não depois.

A virada de modelo mental que muda tudo

Eis o que a maioria dos engenheiros traz de uma bagagem relacional para o DynamoDB: eles projetam o schema primeiro e depois pensam nas consultas.

Com um banco relacional, isso geralmente funciona. Normalize os dados, crie índices depois, escreva consultas com joins entre tabelas. O motor do banco cuida de boa parte da complexidade das consultas.

O DynamoDB não funciona assim. Diferente dos bancos relacionais, ele não otimiza consultas arbitrárias por meio de um query planner. Em vez disso, roteia cada requisição diretamente para a partição que possui os dados usando o hash da chave de partição, um dos motivos pelos quais o DynamoDB consegue entregar latência consistente de milissegundos de um dígito independentemente do tamanho da tabela. O trade-off é que o banco é otimizado para uma coisa: recuperar itens de forma eficiente usando padrões de acesso baseados em chave, pela chave primária ou por índices secundários. Todo o resto (filtrar, ordenar por atributos que não são chave, consultar relacionamentos entre entidades) ou é caro ou exige índices adicionais projetados com antecedência.

A virada é esta: você precisa conhecer seus padrões de acesso antes de projetar sua tabela. Não por alto. Especificamente. "Buscar todos os pedidos de um usuário, ordenados por data" é um insumo de design. "Buscar os 10 tickets de suporte escalados mais recentes de uma determinada conta" é um insumo de design. Seu schema é construído de trás para frente a partir dessas perguntas, não a partir do formato dos dados.

Na prática, isso significa que uma única tabela do DynamoDB costuma armazenar vários tipos de entidade lado a lado, com chaves de partição e chaves de ordenação construídas para responder a perguntas específicas com eficiência. Isso parece errado para qualquer pessoa que venha de um mundo SQL normalizado. Não é. É exatamente esse o ponto.

Chaves de ordenação compostas

Veja um exemplo comum: os pedidos de um usuário, em que você precisa consultar por status e data. A abordagem ingênua armazena status e data como atributos separados e filtra na hora da leitura. Você acaba lendo todos os pedidos daquele usuário e descartando os que não correspondem.

A abordagem que parte dos padrões de acesso codifica a consulta diretamente na chave de ordenação:

PK SK
user#123 ACTIVE#2024-07-15
user#123 ACTIVE#2024-07-01
user#123 COMPLETED#2024-06-20
user#123 COMPLETED#2024-06-10

Agora, uma única consulta com BEGINS_WITH("ACTIVE#") retorna apenas os pedidos ativos, ordenados por data, sem ler um único pedido concluído. Você paga exatamente pelo que recupera.

Isso funciona porque BEGINS_WITH é uma consulta de prefixo na chave de ordenação. O agrupamento mais amplo precisa vir primeiro, para que o DynamoDB possa usá-lo para restringir o intervalo. Uma abordagem comum é colocar a condição mais ampla à esquerda e componentes progressivamente mais específicos à direita, de modo que as consultas de prefixo consigam mirar com eficiência o subconjunto que interessa.

Global Secondary Indexes esparsos

Outro padrão que decorre do pensamento orientado a padrões de acesso: se apenas uma pequena fração dos seus itens atende a uma condição (tickets escalados, registros sinalizados, itens aguardando revisão), modele essa condição como um atributo que só existe nesses itens. Um GSI construído sobre esse atributo indexa apenas os itens em que ele está presente.

O comportamento-chave aqui é que o DynamoDB não inclui um item em um GSI quando o atributo indexado está ausente (não apenas nulo, mas completamente ausente do item). Isso significa que você pode tornar um GSI esparso por design simplesmente omitindo o atributo nos itens que não quer indexar.

Se 2% dos seus tickets são escalados, o GSI contém 2% da tabela. Consultá-lo é muitíssimo mais barato do que escanear a tabela inteira e filtrar. O GSI é esparso por design, não por acidente.

Your cloud bill shouldn't be a mystery

Optimization, automation, expertise. In one platform.

Os erros que aparecem na sua fatura

Vale nomear esses erros especificamente porque são comuns, ficam invisíveis até você olhar seus relatórios de custo e costumam ser introduzidos por engenheiros que entendem razoavelmente bem de DynamoDB.

Filter expressions

O DynamoDB oferece, sim, filtragem dos resultados de consulta após a recuperação. O problema é que uma FilterExpression não reduz as unidades de capacidade consumidas; ela só reduz o que é retornado a quem chamou. Se uma consulta lê 500 itens antes de o filtro rodar, você é cobrado por todos os dados lidos até aquele ponto, independentemente de quantos itens são retornados a quem chamou.

Isso fica caro rápido quando é a estratégia principal para restringir resultados. Parece uma cláusula WHERE do SQL, então é fácil recorrer a ela sem pensar no custo. A correção quase sempre é redesenhar a estrutura de chaves para que o banco recupere apenas o que você precisa. Filter expressions têm seu lugar, mas não deveriam sustentar sua estratégia de consulta.

Partições quentes causadas por chaves baseadas em tempo

Um padrão comum ao construir dados de séries temporais: usar o timestamp atual ou a hora atual como chave de partição. O raciocínio parece intuitivo: particionar por janela de tempo. O problema é que todas as escritas miram a mesma chave de partição lógica, impedindo o DynamoDB de distribuir o tráfego de escrita de forma uniforme entre as partições. Em qualquer volume de escrita relevante, isso leva a throttling.

O DynamoDB tem, sim, Adaptive Capacity, que redistribui automaticamente a taxa de transferência para as partições que estão recebendo mais tráfego. Para desbalanceamentos moderados, ela costuma resolver o problema de forma transparente. Mas nenhuma capacidade adaptativa compensa uma única chave de partição recebendo tráfego extremo e sustentado — e uma chave de janela de tempo sob alta carga de escrita é exatamente essa situação.

A correção é o write sharding: acrescente um sufixo aleatório à chave de partição (por exemplo, EVENTS#4, em que o sufixo é um número entre 0 e N) para espalhar as escritas por várias partições lógicas. Na leitura, consulte cada shard em paralelo e mescle os resultados. Exige mais código na aplicação, mas é o que evita que um workload de escrita de alto throughput crie gargalo em uma única partição.

Scans de tabela

O terceiro que merece destaque: full table scans. O DynamoDB suporta operações de Scan, e engenheiros às vezes recorrem a elas quando ficam sem GSIs ou precisam consultar por um atributo que não está indexado. O problema é que um scan lê todos os itens da tabela, independentemente de quantos correspondem. Em uma tabela grande, isso é lento e caro ao mesmo tempo.

Scans são perfeitamente razoáveis para migrações, exports, tarefas administrativas ou processamento em segundo plano em que a latência não importa. O problema é usá-los em caminhos de requisição voltados ao usuário. Se um scan está no fluxo crítico da sua aplicação, é quase sempre um sinal de que os padrões de acesso não foram totalmente considerados no design do schema. A correção costuma ser um novo GSI ou uma chave de ordenação redesenhada, não um scan mais rápido.

Considerações finais

O DynamoDB é genuinamente poderoso e, para os workloads certos, é um dos melhores bancos de dados que a AWS oferece. Mas a distância entre "escolheu o banco certo" e "projetou corretamente" é grande, e essa distância aparece na sua fatura da AWS antes de aparecer em qualquer outro lugar. O modelo de custo é diferente, o processo de design é diferente e os erros são diferentes de tudo o que a maioria dos engenheiros já enfrentou.

Se você está avaliando o DynamoDB para um novo workload, ou já tem tabelas em produção com custos mais difíceis de explicar do que gostaria, esse é exatamente o tipo de trabalho que a DoiT faz com clientes AWS toda semana. Nosso time de mais de 100 especialistas em nuvem pode ajudar você a validar padrões de acesso, identificar problemas de design cedo e extrair o máximo dos serviços pelos quais você já paga. Agende uma demo.