DynamoDB Referans Sayaçları
Bir referans sayacı, kaç çocuk öğenin ona işaret ettiğini izleyen, parent öğede sakladığın bir sayıdır — bir posttaki like'lar, bir workspace'teki üyeler, bir yorumdaki yanıtlar. Tutarsın çünkü çocukları her okumada saymak çok pahalıdır.
DynamoDB'de bir sayacı nasıl tutarsın?
Çalışan toplamı parent öğede bir sayı olarak sakla ve çocuğu oluşturan yazmayla aynı yazmada güncelle. Bir ikisinin de inmesini veya hiçbirinin inmemesini sağlar ve çocuk yazmasındaki bir koşul retry'ların çift saymasını durdurur — böylece tek bir GetItem doğru bir sayı döner.
- Çocukları okuma zamanında sayma. Like'ları saymak için bir
Query, taradığı her like öğesi için öder. Toplamı postta sakla ve bunun yerine bir öğe oku. - Sayacı çocuğun yazıldığı yerde tut, sonra değil. Çocuğu oluşturan aynı işlemde artır ki ikisi asla sapmasın.
- Yazma ve artırma farklı öğelere dokunuyorsa bir
kullan. Bir like bir öğedir, sayı başka birinde yaşar —
TransactWriteItemsikisinin de inmesini veya hiçbirinin inmemesini sağlar. - Tuzak çift saymadır. Artırmayı yeniden koşan retry edilmiş veya çoğaltılmış bir like sayıyı şişirir. Çocuk yazmasını bir koşulla koru.
Neden hiç say
SQL'den geliyorsan asla bir like-count saklamazdın — SELECT COUNT(*) FROM likes WHERE post_id = ? yazıp bir indeksin bunu ucuz yapmasına bırakırdın. DynamoDB'de
öğeleri okumayı atlayan bir COUNT(*) yoktur.
Bir postun like'ları üzerinde bir Query, yalnızca sayıyı istesen bile o
partition'daki her like öğesini okur — ve faturalandırır. Viral bir postta "kaç
like?" yanıtlamak binlerce RCU'dur. Referans sayaçlarının öldürmek için var
olduğu okuma tuzağı budur.
Bu yüzden edersin: çalışan toplamı postun
kendisinde sakla. Sayacı okumak tek bir GetItem olur. Maliyet, onu doğru
tutmanın artık sana ait olmasıdır.
Öğeleri modelle
İki öğe tipi bir partition paylaşır ki post ve like'ları bir item collection içinde otursun. Uydurulmuş anahtarlar:
| PK | SK | attributes |
|---|---|---|
| POST#a91f | META | likeTally (Number), body, authorId, createdAt |
| PK | SK | attributes |
|---|---|---|
| POST#a91f | LIKE#USER#7c20 | likedAt |
META öğesindeki likeTally attribute'u referans sayacıdır. Her LIKE# öğesi
bir çocuktur. İkisini de PK = "POST#a91f" altına koymak, listeyi istediğinde
tek bir Query'nin postu ve beğenenleri birlikte getirmesi demektir.
Sayacı atomik artır
DynamoDB bir sayıyı bir ADD (veya SET x = x + :n) ile artırır — bu bir atomik sayaçtır: DynamoDB delta'yı sunucu
tarafında uygular, önce mevcut değeri okumadan, böylece eşzamanlı artırmalar
birbirini ezmez.
(AWS: atomik sayaçlar)
Bir postu beğenmek iki öğeye iki yazmadır — LIKE# öğesini oluştur ve
META üzerindeki likeTally'ye 1 ekle. Like iner ama artırma düşerse tally
sonsuza dek yanlıştır. İkisini de veya hiçbirini istersin.
TransactWriteItems'ın garanti ettiği budur — birden fazla öğe boyunca hepsi ya
hiçbiri, ve herhangi bir öğe eşzamanlı değiştirilirse tüm transaction'ı iptal
eder
(AWS: işlemlerle kötümser kilitleme):
{
"TransactItems": [
{
"Put": {
"TableName": "Social",
"Item": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "LIKE#USER#7c20"},
"likedAt": {"N": "1750636800"}
},
"ConditionExpression": "attribute_not_exists(SK)"
}
},
{
"Update": {
"TableName": "Social",
"Key": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "META"}
},
"UpdateExpression": "ADD likeTally :one",
"ExpressionAttributeValues": {":one": {"N": "1"}}
}
}
]
}Put ve Update birlikte commit olur. Biri düşerse DynamoDB ikisini de geri alır
ve bir TransactionCanceledException döner.
Çift saymaya karşı koru
Asıl bug yarı yazılmış bir like değildir — transaction bunu önler. Aynı
kullanıcının iki kez beğenmesi veya bir istemci retry'sinin isteği yeniden
oynamasıdır. Her replay başka bir 1 ekler ve likeTally sessizce gerçek sayının
üstüne sapar.
Put üzerindeki ConditionExpression: attribute_not_exists(SK) korumadır. O
kullanıcının LIKE# öğesi zaten varsa Put'un koşulu düşer, tüm transaction
iptal edilir ve — kritik olarak — ADD asla çalışmaz. Kullanıcı başına bir like,
anahtarla zorlanır.
Bu update ve condition expression'ları — doğru ExpressionAttributeValues ve
attribute_not_exists korumasıyla — JSON'u elle birleştirmek yerine DynamoDB
Expression Builder'da kur ve kopyala.
Unlike ve maliyet
Bir like'ı kaldırmak ayna görüntüsüdür: aynı transaction'da
ConditionExpression: attribute_exists(SK) ile LIKE# öğesini Delete et ve
ADD likeTally :minusOne. Koşul, çift-unlike'ın tally'yi negatife çekmesini
durdurur.
Fiyatı bil. 1 KB'a kadar öğeler için işlemsel bir yazma öğe başına 2 WCU tutar — biri hazırlık, biri commit — düz bir yazma için 1 WCU'ya karşı. Bir like iki öğedir, yani her like kabaca dört WCU'dur. Eylem başına ucuz ama bir ünlü postu like fırtınası almadan önce bilmeye değer.
DynoTable'da görüntüleyin
Bir tally'nin saptığından şüphelenince, saklanan likeTally'yi gerçek LIKE#
çocuk sayısıyla karşılaştırmak istersin — prod'da bir count sorgusu çalıştırmadan.

Sınırlı bir post kümesi boyunca gerçek bir mutabakat için — "hangi tally'ler
çocuk sayılarına uymuyor?" — DynoTable'ın SQL Workbench'i düz PartiQL'in ifade
edemediği GROUP BY ve join'i yüklediğin satırlar üzerinde istemci tarafında
çalıştırır.
Tuzaklar ve sonraki adımlar
- Sayacı bant dışında tutma (geceleri yeniden sayan bir Lambda). Bu, baştan işlemsel olması gereken bir yazma yolu üzerinde yara bandıdır.
- 'lara dikkat. Tek bir vahşi popüler post her like'ı — ve her tally artırmasını — bir partition key'de yoğunlaştırır. Sayı doğrudur; partition yine de kısıtlayabilir.
- Nadiren mutabakat et, cerrahi onar. Her mutation koşulluysa drift neredeyse sıfır olmalıdır. Uyuşmazlığı üzerine yazılacak bir sayı değil, bulunacak bir bug olarak ele al.
İlgili okuma: post ve like'ların neden bir partition paylaştığı için single-table design, ve çocukları okuma zamanında saymanın kaçındığın desen olduğu için Query vs Scan.
Sonra bu item collection'ları incelemek ve tally'lerini kendi tablolarına karşı doğrulamak için DynoTable'ı indir.


