Intermediário9 min de leitura

Como modelar dados no DynamoDB

No SQL você modela entidades e relacionamentos primeiro, depois confia que o planejador de queries montará depois qualquer coisa que você pedir. O DynamoDB inverte isso. Você modela as leituras que já sabe que fará, e as chaves existem para servi-las.

Não há mecanismo de join nem planejador escolhendo uma estratégia em tempo de execução. Uma Query lê uma partição ao longo de uma chave, e esse é todo o contrato de desempenho. Então você projeta chaves para padrões de acesso conhecidos, não para um esquema arrumadinho.

A AWS diz claramente em seu guia de boas práticas: "você não deveria começar a projetar seu schema até saber quais perguntas ele precisará responder".

Este guia percorre todo o processo em um único domínio: um placar de jogo multiplayer acompanhando jogadores, as partidas que eles jogam e seu ranking por temporada. Vamos de uma lista de perguntas a um esquema de chaves funcional.

Como você modela dados no DynamoDB?

Modele as leituras primeiro, não as tabelas. Liste toda query que o app faz, depois projete uma e uma para que cada pergunta se resolva em um único Query ou GetItem. Coloque juntos os itens lidos juntos, percorra faixas de valores na chave de ordenação, e adicione uma GSI para qualquer padrão de acesso que a tabela base não consiga servir.

  • Liste as leituras primeiro, não as tabelas. As perguntas são a especificação; os substantivos são uma distração.
  • Cada pergunta precisa ser um único Query ou GetItem. Se uma pergunta precisa de um Scan, o modelo está errado.
  • Itens co-localizados compartilham uma ; qualquer coisa sobre a qual você percorre faixas vai na .
  • Uma pergunta que a tabela base não consegue responder ganha uma — nunca um Scan com filtro.

Passo 1 — Formule o problema como perguntas, não tabelas

Resista à vontade de desenhar tabelas players, matches e scores. Esse instinto é o hábito do SQL, e aqui ele está errado. Em vez disso, anote toda leitura que o app de fato realiza. Para nosso placar:

  • Buscar o perfil de um jogador por id.
  • Listar as partidas recentes de um jogador, mais novas primeiro.
  • Mostrar os N melhores jogadores de uma dada temporada, ranqueados por rating.
  • Encontrar um jogador pelo seu handle público (ex.: para uma URL de perfil).

Essas quatro perguntas — não os substantivos — são a especificação. Cada uma precisa se resolver em um único Query (ou GetItem), porque essa é a única forma de acesso que o DynamoDB serve de forma barata em escala.

Se uma pergunta só pode ser respondida escaneando a tabela, o modelo está errado, e você sentirá isso em latência e custo — veja Query vs Scan para entender por que um Scan é a armadilha a evitar.

O método inteiro é um pipeline curto e ordenado que você roda uma vez por domínio:

NãoSimSimNãoListe as entidadesEnumere os padrões de acessoProjete PK / SKpara servi-losToda leituraé um Query?Adicione uma GSIpor leitura restanteValide comdados reaisSurge umanova pergunta?Entregue o modelo

Cada passo abaixo mapeia para uma caixa: listar, enumerar, projetar chaves, adicionar índices para o resto, depois validar.

Passo 2 — Entenda as primitivas com as quais você está modelando

Uma tabela tem uma chave de partição (PK) que escolhe em qual partição física um item vive, e uma chave de ordenação (SK) opcional que ordena os itens dentro dessa partição.

Os docs de componentes centrais da AWS chamam o par de chave primária do item. Uma Query sempre mira exatamente um valor de PK e pode fazer range-scan ou filtrar a SK — esse é o kit de ferramentas inteiro.

Este design de partição única é o que permite ao DynamoDB entregar as leituras previsíveis, de baixa latência e particionadas horizontalmente descritas pela primeira vez no paper Dynamo da Amazon de 2007.

Duas consequências guiam cada decisão abaixo:

  1. Itens que são lidos juntos deveriam compartilhar uma chave de partição para que um Query os retorne em uma única requisição tarifada.
  2. Qualquer coisa sobre a qual você quer percorrer faixas (partidas recentes, top ratings) precisa viver na chave de ordenação, porque esse é o único atributo que Query pode ordenar e delimitar.

Quando uma pergunta precisa de uma forma de acesso diferente da que a tabela base fornece, você adiciona uma Global Secondary Index — uma reprojeção da tabela sob uma PK/SK diferente.

(Para GSI versus Local Secondary Index, veja GSI vs LSI.)

Passo 3 — Projete as chaves, uma pergunta de cada vez

Usamos uma única tabela com atributos de chave genéricos e sobrecarregados — a abordagem de tabela única — porque um jogador e suas partidas são lidos juntos.

Invente seus próprios prefixos; aqui PLAYER#, MATCH# e SEASON# etiquetam o tipo de entidade dentro de chaves de resto genéricas.

As perguntas 1 e 2 (perfil + partidas recentes) compartilham uma partição, então ambas se apoiam na mesma PK:

partitionIdrangeIdattributes
PLAYER#u8231PROFILEhandle, region, createdAt
PLAYER#u8231MATCH#2026-06-23T14result=win, ratingDelta=+18, mapId
PLAYER#u8231MATCH#2026-06-23T11result=loss, ratingDelta=-15, mapId

Query partitionId = "PLAYER#u8231" retorna o perfil e cada partida em uma única leitura. Para o perfil sozinho, GetItem.

Para partidas recentes, rangeId begins_with "MATCH#" com ScanIndexForward = false as percorre da mais nova primeiro — o timestamp na chave de ordenação faz a ordenação de graça.

As perguntas 3 e 4 não podem ser respondidas a partir dessa partição — elas pivotam em ranking de temporada e em handle, nenhum dos quais é a PK base. Cada uma ganha uma GSI.

Adicionamos dois pares de atributos de índice genéricos — seasonPartition / seasonSort para o índice de ranking e handlePartition / handleSort para o índice de handle — populados no mesmo item de perfil (aquele escrito no Passo 3, agora mostrado com seus atributos de índice preenchidos):

partitionIdrangeIdseasonPartitionseasonSorthandlePartitionhandleSort
PLAYER#u8231PROFILESEASON#2026-Q2RATING#1842HANDLE#nighthawkPLAYER#u8231

Agora Query no índice de temporada WHERE seasonPartition = "SEASON#2026-Q2" com ScanIndexForward = false retorna os jogadores ranqueados por rating — esse é o placar.

Um segundo índice com chave em handlePartition = "HANDLE#…" resolve um handle público para um id de jogador em uma única leitura. Uma tabela física, quatro padrões de acesso de Query único.

Uma nota de sobre RATING#1842: o DynamoDB ordena as chaves de ordenação lexicograficamente, não numericamente, então um rating precisa ser preenchido com zeros até uma largura fixa (RATING#01842) ou 9 ordenaria depois de 1000. Esta é uma pegadinha de modelagem clássica que vale acertar logo no início.

Passo 4 — Valide o modelo no DynoTable

Um esquema de chaves só ganha confiança quando você vê um Query real retornar exatamente os itens que você esperava e nada mais.

Abra a tabela no DynoTable, rode a query do placar contra o índice de temporada, e confirme que a partição volta ranqueada e delimitada — sem Scan, sem ordenação no lado do cliente.

Rodando o Query do placar de temporada contra a GSI no DynoTable e inspecionando o resultado ranqueado.
Rodando o Query do placar de temporada contra a GSI no DynoTable e inspecionando o resultado ranqueado.

Quando você construir as expressões de condição para essas queries — o begins_with, o seasonPartition = :p, o binding do placeholder :p — deixe o construtor de expressões do DynamoDB fazer isso.

Ele gera a KeyConditionExpression, as ExpressionAttributeNames e os ExpressionAttributeValues, para que uma palavra reservada como result ou um placeholder com erro de digitação nunca quebre silenciosamente uma leitura.

Passo 5 — Armadilhas e próximos passos

Algumas armadilhas para checar antes de colocar o modelo em produção:

  • Não modele relacionamentos que você nunca lê juntos. Uma GSI por pergunta é barata; uma GSI desperdiçada é custo recorrente. Adicione índices a partir da lista de perguntas, não especulativamente.
  • Fique de olho no calor da partição. Se uma PK (um jogador celebridade, uma única temporada quente) absorve a maior parte do tráfego, essa partição pode sofrer throttle. Espalhe as escritas com um sufixo de fragmento quando uma chave é comprovadamente quente — a AWS cobre isso em design de chave de partição.
  • Preencha com zeros e use ISO-8601 em tudo que é numérico ou temporal em uma chave de ordenação, para que a ordenação lexicográfica corresponda à ordem que você pretende.
  • Uma nova pergunta = uma nova chave ou índice, nunca um Scan. Quando um padrão de acesso genuinamente novo aparece depois, estenda as chaves; não disfarce com um filtro.

Modele as perguntas primeiro, projete chaves para que cada uma seja um único Query, depois prove.

Para adiantar o passo do meio, a gratuita ferramenta de Single-Table Design transforma uma lista de padrões de acesso como esta em um plano PK/SK/GSI, com itens de exemplo e dicas de custo.

Experimente o DynoTable para navegar pela sua tabela, rodar essas queries contra a tabela base e as GSIs lado a lado, e ver os padrões de acesso que você projetou retornarem exatamente o que você planejou. E para a pergunta que você não modelou, seu SQL Workbench roda JOINs, GROUP BY e agregações de verdade no lado do cliente.

Atualizado

Experimente este design de forma interativa

Esboce suas entidades e padrões de acesso na ferramenta gratuita de Single-Table Design do DynamoDB — ela sugere modelos de chave PK/SK, prevê as coleções de itens e mostra quais padrões precisam de um GSI.

Abrir a ferramenta de Single-Table Design