Intermediário6 min de leitura

Desnormalização no DynamoDB

Vindo do SQL, desnormalização soa como pecado — dados duplicados, sem fonte única da verdade. No DynamoDB é o objetivo todo. Não há joins, então você copia os dados relacionados para o item que precisa deles e os lê de volta de uma vez.

O que é desnormalização no DynamoDB?

Desnormalização no DynamoDB significa copiar os dados relacionados para o próprio item que os lê, de modo que uma única consulta retorne tudo de uma vez. Como o DynamoDB não tem joins, você pré-junta os dados na hora da escrita em vez de costurar tabelas na hora da leitura. A contrapartida é a defasagem — duplique apenas valores que raramente mudam.

  • Sem joins significa pré-juntar na hora da escrita. Armazene o valor relacionado no item que o lê, para que uma consulta nunca precise de uma segunda busca.
  • Dois sabores. Embuta dados aninhados em um atributo complexo em um item, ou duplique um valor em muitos itens.
  • A cilada é a defasagem. Quando a fonte muda, toda cópia fica errada até você propagar a atualização. Só duplique valores que raramente mudam.
  • Isso compra leituras, não escritas. Você troca escritas mais numerosas (e mais cuidadosas) por leituras baratas, de requisição única.

Por que não há joins em que se apoiar

Um JOIN relacional remonta linhas normalizadas na hora da leitura. O DynamoDB não tem join — um Query lê uma coleção de itens e devolve exatamente o que está armazenado ali. Nada costura duas tabelas para você.

Então os dados já têm que estar moldados para a leitura. Se uma tela precisa de um post e do nome do autor, esse nome precisa viver em algum lugar que a leitura do post já toque. O paper Amazon Dynamo de 2007 tornou essa troca explícita: abrir mão de recursos relacionais para obter leituras previsíveis, de milissegundos de um dígito, em escala.

Padrão 1 — embutir com um atributo complexo

Atributos do DynamoDB podem conter mapas e listas aninhados, não só escalares. Então uma forma comum de desnormalização é enfiar um objeto filho diretamente dentro do item pai em vez de dar a ele o próprio item.

Um post com suas tags e um pequeno snapshot do autor, tudo em um item:

PKSKauthortags
POST#9f3META{id: U#12, name: "Mara Vance"}["dynamodb","aws"]

Um GetItem retorna o post, as tags e o bloco do autor juntos. Sem segunda leitura. Isso é ótimo para dados pertencentes ao pai e limitados em tamanho — um punhado de tags, um snapshot de autor.

O limite a respeitar: um único item do DynamoDB chega no máximo a 400 KB, nomes e valores de atributos incluídos (Service Quotas). Embuta uma lista ilimitada (todo comentário de um post viral) e você vai estourar isso.

Padrão 2 — duplicar um valor entre itens

O caso do blog é o de manual. Você lista posts e quer que cada linha mostre o nome de exibição do autor — mas não quer uma segunda leitura por post para buscá-lo.

Então você escreve o nome do autor em cada item de post quando o post é criado:

PKSKauthorIdauthorNametitle
POST#9f3METAU#12"Mara Vance""Modeling 1:N"
POST#a71METAU#12"Mara Vance""Sparse GSIs"
POST#b04METAU#88"Lio Tan""Query vs Scan"

Agora Query PK begins_with "POST#" (ou um GSI sobre posts) renderiza a lista inteira — título e autor — sem busca por linha. O nome do autor está desnormalizado: a cópia canônica vive em USER#12, e cada post carrega sua própria cópia.

A troca está bem ali. Você transformou uma leitura N+1 em uma leitura, ao custo de manter "Mara Vance" em N+1 lugares.

Embutir vs. duplicar — qual deles

Embutir (atributo complexo)Duplicar (copiar entre itens)
Formafilho aninhado dentro do paimesmo valor em muitos itens
Melhor paradados limitados, do paium valor compartilhado que muitos itens exibem
Leituraum GetItemum Query
Custo de atualizaçãoreescrever o único item paipropagar para cada cópia
Risco de tamanholimite de 400 KB por itemnenhum por item

Recorra a embutir quando o filho só aparece com o pai. Recorra a duplicar quando muitos itens independentes precisam exibir o mesmo valor compartilhado.

A cilada: cópias defasadas

Eis a parte que morde. Mara se renomeia para "Mara V.". Você atualiza USER#12. Todo item de post ainda diz "Mara Vance" até você ir consertá-los.

Então atualizar um valor duplicado é uma escrita em leque (fan-out), não uma linha única. Você consulta cada item afetado e reescreve cada um — idealmente protegido para que você só toque linhas que ainda têm o valor antigo:

UPDATE POST#9f3
SET authorName = "Mara V."
WHERE authorName = "Mara Vance"

Você pode compor esse SET condicional contra authorName no Expression Builder e copiar a UpdateExpression e a ConditionExpression geradas direto para o seu código.

O fan-out em si é uma escrita por item: consulte os posts do autor, depois emita as atualizações. A sequência:

"DynamoDB"App"DynamoDB"App"Atualizar nome de USER"Consultar posts do autor""POST"Atualizar cada authorName"

O custo de duplicar dados: toda mudança na fonte é uma consulta mais uma escrita por cópia.

É por isso que a regra é só duplicar valores que raramente mudam. Um nome de exibição, um nível de plano, um rótulo de categoria — tudo bem. Um contador ao vivo ou um campo editado com frequência — não; o fan-out vai te comer vivo.

Quando a normalização ainda vence

Se um valor muda com frequência, ou um item é lido por padrões genuinamente imprevisíveis, mantenha-o normalizado e aceite a leitura extra. A desnormalização é uma otimização para padrões de acesso conhecidos e com leituras pesadas — não um padrão a aplicar em todo lugar. Pré-junte as leituras que você de fato roda, e deixe o resto em paz.

Para decidir onde esses atributos duplicados vivem, modele os padrões de acesso primeiro — veja single-table design e, para o lado de leitura da troca, Query vs Scan.

Baixe o DynoTable para inspecionar uma tabela desnormalizada, identificar quais cópias ficaram defasadas e rodar a atualização em leque contra seus próprios dados.

Atualizado