DynamoDB Reference Count'ları
Bir reference count, bir ebeveyn item üzerinde sakladığın ve kaç çocuk item'ın ona işaret ettiğini izleyen bir sayıdır — bir gönderideki beğeniler, bir workspace'teki üyeler, bir yorumdaki yanıtlar. Onu tutarsın çünkü her okumada çocukları saymak fazla pahalıdır.
DynamoDB'de bir sayı nasıl korunur?
Yürüyen toplamı ebeveyn item üzerinde bir sayı olarak sakla ve onu çocuğu oluşturan aynı yazmayla güncelle. Bir ikisinin de inmesini ya da hiçbirinin inmemesini sağlar; çocuk yazmasındaki bir condition ise yeniden denemelerin çift saymını önler — böylece tek bir GetItem doğru bir sayı döndürür.
- Okuma anında çocukları sayma. Beğenileri saymak için bir
Query, taradığı her beğeni item'ı için öder. Toplamı gönderide sakla ve bunun yerine tek bir item oku. - Sayıyı çocuğun yazıldığı yerde koru, sonradan değil. Çocuğu oluşturan aynı operasyonda onu arttır ki ikisi asla birbirinden sapmasın.
- Yazma ve artış farklı item'lara dokunduğunda bir transaction kullan. Bir
beğeni tek bir item, sayı bir başkasında yaşar —
TransactWriteItemsikisinin de inmesini ya da hiçbirinin inmemesini sağlar. - Ayak kapanı çift sayımdır. Artışı yeniden çalıştıran, yeniden denenmiş ya da çoğaltılmış bir beğeni sayıyı şişirir. Çocuk yazmayı bir condition ile koru.
Neden saymalı ki
SQL'den geliyorsan, asla bir beğeni sayısı saklamazdın — SELECT COUNT(*) FROM likes WHERE post_id = ? yapar ve bir index'in onu ucuza getirmesine izin verirdin.
DynamoDB'nin item okumayı atlayan bir COUNT(*)'ı yoktur.
Bir gönderinin beğenileri üzerindeki bir Query, yalnızca sayıyı istesen bile o
partition'daki her beğeni item'ını okur — ve onun için faturalandırır. Viral bir
gönderide bu, "kaç beğeni?" sorusunu cevaplamak için binlerce RCU demektir.
Reference count'ların öldürmek için var olduğu okuma ayak kapanı budur.
Yani sen denormalize edersin: yürüyen toplamı gönderinin kendisinde sakla.
Sayıyı okumak tek bir GetItem'a dönüşür. Bedeli, onu doğru tutmaya artık senin
sahip olmandır.
Item'ları modelle
İki item tipi bir partition'ı paylaşır, böylece gönderi ve beğenileri tek bir item koleksiyonunda durur. Uydurulmuş key'ler:
| PK | SK | attributes |
|---|---|---|
| POST#a91f | META | likeTally (Number), body, authorId, createdAt |
| PK | SK | attributes |
|---|---|---|
| POST#a91f | LIKE#USER#7c20 | likedAt |
META item'ı üzerindeki likeTally attribute'ı reference count'tur. Her LIKE#
item'ı bir çocuktur. Her ikisini de PK = "POST#a91f" altına koymak, listeyi
gerçekten istediğinde tek bir Query'nin gönderiyi ve beğenenlerini bir arada
çekebileceği anlamına gelir.
Sayıyı atomik olarak arttır
DynamoDB bir sayıyı bir ADD (ya da SET x = x + :n) update expression'ıyla
arttırır — bu bir atomic counter'dır: DynamoDB delta'yı sunucu tarafında, sen
önce geçerli değeri okumadan uygular, dolayısıyla eşzamanlı artışlar birbirini
ezmez.
(AWS: atomic counter'lar)
Problem: bir gönderiyi beğenmek iki item'a iki yazmadır — LIKE# item'ını
oluştur ve META üzerindeki likeTally'ye 1 ekle. Beğeni iner ama artış
başarısız olursa, toplam sonsuza kadar yanlıştır. Sana ya ikisi ya da hiçbiri lazım.
TransactWriteItems'ın garanti ettiği şey budur — birden çok item arasında
ya hep ya hiç, ve herhangi bir item eşzamanlı olarak değiştirilirse tüm
transaction'ı iptal eder
(AWS: transaction'larla 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 edilir. Biri başarısız olursa, DynamoDB ikisini
de geri alır ve bir TransactionCanceledException döndürür.
Çift sayıma karşı koru
Asıl bug yarı yazılmış bir beğeni değil — transaction onu önler. Aynı
kullanıcının iki kez beğenmesi, ya da bir istemci yeniden denemesinin isteği
yeniden oynatmasıdır. Her yeniden oynatma bir 1 daha ekler ve likeTally gerçek
sayının üzerine sessizce kayar.
Put üzerindeki ConditionExpression: attribute_not_exists(SK) korumadır. O
kullanıcının LIKE# item'ı zaten varsa, Put'un koşulu başarısız olur, tüm
transaction iptal edilir ve — kritik olarak — ADD asla çalışmaz. Key tarafından
zorlanan, kullanıcı başına bir beğeni.
Bu update ve condition expression'larını — doğru ExpressionAttributeValues ve
attribute_not_exists korumasıyla — JSON'u elle birleştirmek yerine
DynamoDB Expression Builder içinde oluştur ve
kopyala.
Beğeniyi geri al, ve maliyet
Bir beğeniyi kaldırmak ayna görüntüsüdür: LIKE# item'ını
ConditionExpression: attribute_exists(SK) ile Delete et ve aynı transaction'da
ADD likeTally :minusOne yap. Condition, çift geri-alımın toplamı negatife
sürmesini durdurur.
Fiyatı bil. Transaction'lı bir yazma, 1 KB'a kadar item'lar için item başına 2 WCU'ya mal olur — biri hazırlamak, biri commit etmek için — düz bir yazma için 1 WCU'ya karşı. Bir beğeni iki item, dolayısıyla her beğeni kabaca dört WCU. Eylem başına ucuz, ama bir ünlü gönderisi bir beğeni-fırtınası alırken bilmeye değer.
DynoTable'da gör
Bir toplamın kaydığından şüphelendiğinde, prod'da bir sayma sorgusu çalıştırmadan
saklanan likeTally'yi gerçek LIKE# çocuklarının sayısıyla karşılaştırmak
istersin.

Sınırlı bir gönderi kümesi üzerinde gerçek bir mutabakat için — "hangi toplamlar
çocuk sayılarıyla eşleşmiyor?" — DynoTable'ın SQL Workbench'i GROUP BY'ı ve
birleştirmeyi yüklediğin satırlar üzerinde istemci tarafında çalıştırır, ki düz
PartiQL bunu ifade edemez.
Tuzaklar ve sonraki adımlar
- Sayıyı bant-dışı koruma (geceleri yeniden sayan bir Lambda). Bu, en başından transaction'lı olması gereken bir yazma yolu üzerine bir yara bandıdır.
- Hot partition'lara dikkat et. Tek bir çılgınca popüler gönderi her beğeniyi — ve her toplam artışını — tek bir partition key'inde yoğunlaştırır. Sayı doğru; partition yine de kısıtlayabilir (throttle).
- Nadiren mutabakat yap, cerrahi onar. Her mutasyon koşullandırılmışsa sapma sıfıra yakın olmalı. Bir uyuşmazlığı, üzerine yazılacak bir sayı değil, bulunacak bir bug olarak ele al.
İlgili okumalar: gönderi ve beğenilerin neden bir partition'ı paylaştığı için single-table design, ve okuma anında çocukları saymanın neden kaçındığın desen olduğu için Query vs Scan.
Sonra bu item koleksiyonlarını incelemek ve toplamlarını kendi tablolarına karşı doğrulamak için DynoTable'ı indir.


