Itens Singleton no DynamoDB
Um item singleton é uma única linha com uma chave fixa e hardcoded que guarda estado para sua aplicação inteira — não um registro por usuário ou por pedido, mas um registro, ponto. Feature flags, um blob de config, um kill-switch global: o tipo de coisa que um app relacional manteria em uma tabela de configurações de uma linha só.
Vindo do SQL, você apelaria para uma tabela config com id = 1 e um SELECT * FROM config. No DynamoDB você faz a mesma coisa com uma partition key hardcoded
— e como você sempre sabe essa chave, você a lê com um GetItem, não com um
Queryou umScan.
O que é um item singleton no DynamoDB?
Um item singleton é uma única linha do DynamoDB armazenada sob uma chave fixa e hardcoded que guarda estado global para toda a sua aplicação — feature flags, um blob de config, uma versão do sistema — em vez de um registro por usuário ou pedido. Como você sempre sabe a chave, você a lê com um GetItem e a atualiza com update expressions e condition expressions.
- Um singleton é um item com chave constante. Você faz hardcode da
PK/SKno seu código (ex.:CONFIG#GLOBAL) em vez de usar um template com id de usuário ou de pedido. - Leia com
GetItem, nunca comScan. Você sempre sabe a chave completa, então uma leitura pontual é um RCU consistente e previsível — sem filtro, sem varredura de tabela. - É uma hot key por definição. Toda requisição pode tocar a mesma partição, então faça cache dela e mantenha o item pequeno; não a transforme em gargalo de escrita.
- Mute-a com segurança usando update + condition expressions, não read-modify-write na sua aplicação — é aí que mora a condição de corrida de lost-update.
Reconheça o padrão
Você tem estado global quando o dado não está escopado a nenhuma entidade. Alguns sinais:
- Uma flag que é a mesma para todos (
signup_enabled = false). - Um blob de parâmetros que seu app lê no boot (rate limits, cotas padrão).
- Um contador ou número de versão para o sistema inteiro, não por linha.
Qualquer coisa escopada a um usuário, tenant ou pedido não é um singleton — isso é um item comum com chave pelo id daquela entidade. O singleton é a fatia global restante que não tem outro lugar para morar.
Dê a ele uma chave constante
Todo o padrão depende de uma decisão: a chave é um literal, não um template. Para um item global de feature flags em uma tabela única sobrecarregada, escolha um prefixo fixo e um valor fixo:
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_enabled, maintenance_mode, ai_search_enabled |
PK = "SETTINGS#APP" e SK = "FLAGS#V1" estão embutidos no código. Não há id de
usuário, nem id de tenant — a aplicação pede exatamente este item toda vez. Essa
previsibilidade é o ponto: uma chave conhecida é um GetItem, e um GetItem é a
leitura mais barata e consistente que o DynamoDB oferece.
O sufixo V1 é deliberado. Se o schema da flag mudar de formato depois, você
escreve um item FLAGS#V2 e migra os leitores, em vez de mutar o item vivo no
lugar. Versionar a chave do singleton te dá uma costura limpa de migração.
Leia com GetItem
Como a chave é totalmente conhecida, você nunca faz Query e nunca faz Scan
para um singleton. Um Scan lê a tabela inteira e filtra no cliente — o clássico
footgun do Scan — e é um exagero absurdo para buscar uma
linha que você consegue endereçar diretamente.
Um GetItem contra SETTINGS#APP / FLAGS#V1 retorna as flags em uma única
leitura fortemente ou eventualmente consistente. A AWS cobra um GetItem de um
item ≤ 4 KB como 0,5 RCU eventualmente consistente ou 1 RCU fortemente consistente
(docs de capacidade de leitura/escrita da AWS).
Mantenha o singleton pequeno e esse custo fica fixo para sempre.
O caminho de leitura é só isto: o app dá boot ou uma requisição chega, você faz
GetItem da chave fixa, você faz cache do resultado. Aqui está o fluxo.
A chave fixa transforma uma busca global em uma leitura pontual com um caminho de default embutido.
Note o ramo não: um singleton ausente nunca deve te derrubar. Caia no valor
seguro (feature off, manutenção on) para que uma lacuna de primeiro deploy
ou uma chave errada falhe fechado, não aberto.
Atualize sem condição de corrida
A armadilha é atualizar um singleton com read-modify-write na sua aplicação: você
faz GetItem das flags, vira uma em memória e então faz PutItem da coisa toda
de volta. Dois escritores concorrentes leem o item antigo e o segundo Put
atropela a mudança do primeiro. Lost update.
Dois recursos do DynamoDB matam a corrida sem locking no lado da aplicação:
- Update expressions mutam um atributo no lado do servidor, deixando o resto
intocado. Sem precisar refazer
Putdo item inteiro. - Condition expressions fazem a escrita só ter sucesso se o item ainda estiver
do jeito que você espera, então uma escrita defasada é rejeitada com
ConditionalCheckFailedException(docs de condition expression da AWS).
Para virar uma flag, mire apenas esse atributo com um SET e proteja-o com um
incremento de versão para que escritores concorrentes não se atropelem:
# UpdateItem
Key PK=SETTINGS#APP SK=FLAGS#V1
UpdateExpression SET signup_enabled = :on, schema_version = :next
ConditionExpression schema_version = :current
Se dois escritores correm, a checagem schema_version = :current do segundo falha
e ele tenta de novo contra o valor fresco. Você pode esboçar os nomes, valores e
esse formato exato de expressão no
DynamoDB Expression Builder antes de fiá-lo
no código. Para um olhar mais profundo nos operadores, veja o guia de
idiomas de update expression.
Cuide da hot key
Um singleton é, por construção, uma hot key — toda parte do seu app pode ler a mesma partição. Isso é ok para leituras se você fizer cache, mas é o único risco real do padrão.
- Cacheie agressivamente. Leia as flags uma vez por processo (ou a cada N segundos), não a cada requisição. O valor do singleton é a coisa mais barata de memoizar.
- Não a transforme em hot spot de escrita. Uma flag virada por um admin algumas vezes ao dia não é nada. Um singleton que você incrementa a cada requisição é um gargalo de throughput de partição — isso é um problema de contador, não de singleton.
- Mantenha-a pequena. O custo de leitura escala com o tamanho do item em blocos de 4 KB. Um blob de config inchado deixa todo boot mais caro do que precisa.
Se você realmente precisa de um contador global de escrita alta, o singleton é o formato errado — fragmente-o em N itens e some na leitura. Esse é um padrão diferente.
Singleton vs item por entidade
A linha é simplesmente a que o dado está escopado.
| Item singleton | Item por entidade | |
|---|---|---|
| Chave | Constante hardcoded (SETTINGS#APP) | Template com um id (USER#42) |
| Quantos | Exatamente um | Um por usuário / pedido / tenant |
| Leitura típica | GetItem na chave conhecida | GetItem ou Query por entidade |
| Escopo | Aplicação inteira | Uma única entidade |
| Use para | Flags globais, config, versão do sistema | Perfis, pedidos, qualquer coisa por id |
Se você se pegar querendo dois singletons do mesmo tipo, você não tem um singleton — você tem um item por entidade, e a entidade é a coisa pela qual você esqueceu de fazer a chave (config por tenant, digamos).
Armadilhas e próximos passos
- Não faça
Scanpara ele. Você sabe a chave; endereçe-a diretamente. - Não faça read-modify-write nele. Use update + condition expressions.
- Não deixe ele sumir silenciosamente. Caia no valor seguro num cache miss.
- Não o sobrecarregue com escritas de alta frequência. Isso é trabalho de contador fragmentado.
O singleton vive confortavelmente dentro de um single-table design — é só mais uma coleção de itens com uma chave fixa ao lado das suas linhas de entidade.
Experimente o DynoTable para navegar pela sua tabela, encontrar a linha singleton pela sua chave fixa e editar flags na mão enquanto você constrói o caminho de escrita.