Orta5 dakikalık okuma

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 — TransactWriteItems ikisinin 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:

Post item
PKSKattributes
POST#a91fMETAlikeTally (Number), body, authorId, createdAt
Like item
PKSKattributes
POST#a91fLIKE#USER#7c20likedAt

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.

Gönderi META item'ı, tek bir item koleksiyonunda LIKE# çocuklarının yanında, böylece saklanan toplamı gerçek çocuk sayısına karşı göz kararı kontrol edebilirsin.
Gönderi META item'ı, tek bir item koleksiyonunda LIKE# çocuklarının yanında, böylece saklanan toplamı gerçek çocuk sayısına karşı göz kararı kontrol edebilirsin.

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.

Güncellendi