Intermedio6 min di lettura

DynamoDB Conteggi dei riferimenti

Un conteggio riferimenti è un numero memorizzato su un articolo principale che ne tiene traccia gli elementi secondari lo puntano: Mi piace su un post, membri in uno spazio di lavoro, risposte su a commento. Lo tieni perché contare i bambini a ogni lettura è troppo costoso.

Come mantieni il conteggio in DynamoDB?

Memorizza il totale parziale come numero sull'elemento padre e aggiornalo nella stessa scrittura che crea l'elemento figlio. UNfa atterrare entrambi o nessuno dei due, e una condizione sulla scrittura figlio impedisce i tentativi di doppio conteggio, quindi un singolo GetItem restituisce un conteggio accurato.

  • Non contare i bambini durante la lettura. Un Query per contare i Mi piace paga per ogni come l'oggetto che scansiona. Memorizza il totale sul post e leggi invece un elemento.
  • Mantieni il conteggio dove è scritto il bambino, non dopo. Sbattetelo lo stesso operazione che crea il bambino in modo che i due non vadano mai alla deriva.
  • Utilizzare aquando la scrittura e l'urto toccano elementi diversi. Un simile è un oggetto, il conteggio vive su un altro: "TransactWriteItems" fa atterrare entrambi o nessuno dei due.
  • Il footgun è un doppio conteggio. Un tentativo ripetuto o duplicato del genere esegue nuovamente il l'incremento gonfia il numero. Proteggi il bambino che scrive con una condizione.

Perché contare?

Venendo da SQL, non memorizzeresti mai il conteggio dei Mi piace: dovresti SELECT COUNT(*) FROM Mi piace WHERE post_id = ? e lascia che un indice lo renda economico. DynamoDB non ha COUNT(*) salta la lettura degli elementi.

Un Query sui Mi piace di un post legge (e fattura) ogni elemento simile in quel post partizione, anche se desideri solo il numero. In un post virale che è migliaia RCUs per rispondere "quanti Mi piace?" Questo è il numero di riferimenti letti per le armi da fuoco che esistono per uccidere.

Quindi tu: memorizza il totale parziale sul post stesso. Lettura del conteggio diventa un singolo GetItem. Il costo è che ora possiedi mantenendolo accurato.

Modella gli oggetti

Due tipi di elementi condividono una partizione in modo che il post e i suoi Mi piace si trovino in uno raccolta di elementi. Chiavi inventate:

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

L'attributo "likeTally" sull'elemento "META" è il conteggio dei riferimenti. Ogni MI PIACE# l'oggetto è un bambino. Mettendoli entrambi sotto PK = "POST#a91f" significa che un singolo Query può recupera il post e i suoi Mi piace insieme quando vuoi l'elenco.

Aumenta il conteggio in modo atomico

DynamoDB incrementa un numero con un ADD (o SET x = x + :n)— questo è un contatore atomico: DynamoDB applica il delta lato server senza di te leggendo prima il valore corrente, in modo che gli incrementi simultanei non si ostacolino a vicenda. (AWS: contatori atomici)

Mettere mi piace a un post significa due scrivere su due elementi: creare MI PIACE# elemento e aggiungi "1" a "likeTally" su "META". Se il simile atterra ma l'urto fallisce, il il conteggio è sbagliato per sempre. Hai bisogno di entrambi o di nessuno dei due.

Questo è ciò che garantisce TransactWriteItems: tutto o niente su più elementi, e annulla l'intera transazione se qualsiasi elemento viene modificato contemporaneamente (AWS: blocco pessimistico con transazioni):

{
  "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"}}
      }
    }
  ]
}

Il Put e il Update si impegnano insieme. Se uno dei due fallisce, DynamoDB lancia entrambi back e restituisce una TransactionCanceledException.

Proteggiti dai doppi conteggi

Il vero bug non è scritto a metà: la transazione lo impedisce. È il allo stesso utente piace due volte oppure un client riprova a riprodurre la richiesta. Ogni replay aggiunge un altro "1" e "likeTally" supera tranquillamente il conteggio reale.

Il ConditionExpression: attributo_not_exists(SK) sul Put è la guardia. Se l'elemento LIKE# di quell'utente esiste già, la condizione di Put fallisce, il tutto la transazione viene annullata e, aspetto critico, "ADD" non viene mai eseguito. Un Mi piace per utente, imposto dalla chiave.

Costruisci e copia queste espressioni di aggiornamento e condizione, con il diritto ExpressionAttributeValues e attribute_not_exists proteggono — nel file DynamoDB Expression Builder anziché assemblando a mano il JSON.

A differenza, e il costo

Rimuovere un Mi piace è l'immagine speculare: Elimina l'elemento LIKE# con ConditionExpression: attributo_esiste(SK) e ADD likeTally :minusOne nel stessa transazione. La condizione impedisce a una doppia differenza di determinare un conteggio negativo.

Conosci il prezzo. Una scrittura transazionale costa 2 WCUs per articolo per articoli fino a 1 KB — uno per prepararsi, uno per impegnarsi - contro 1 WCU per una semplice scrittura. Un mi piace è composto da due elementi, quindi ogni mi piace vale circa quattro WCU. Economico per azione, ma vale la pena saperlo prima di a il post di una celebrità prende una tempesta.

Vedilo in DynoTable

Quando sospetti che un conteggio sia andato alla deriva, vuoi confrontare il likeTally memorizzato rispetto al numero effettivo di bambini "LIKE#" - senza eseguire una query di conteggio in prod.

L'elemento META post accanto ai suoi figli LIKE# in una raccolta di elementi, in modo da poter confrontare il conteggio memorizzato con il conteggio dei figli reali.
L'elemento META post accanto ai suoi figli LIKE# in una raccolta di elementi, in modo da poter confrontare il conteggio memorizzato con il conteggio dei figli reali.

Per una vera riconciliazione attraverso una serie limitata di post - "che i conteggi non corrispondono il loro figlio conta?" — SQL Workbench di DynoTable esegue GROUP BY e il join lato client sulle righe che hai caricato, che semplicemente PartiQL non può esprimere.

Insidie e passaggi successivi

  • Non mantenere il conteggio fuori banda (una Lambda che conta di notte). È un cerotto su un percorso di scrittura che avrebbe dovuto essere transazionale fin dall'inizio.
  • Orologio. Un unico post estremamente popolare concentra tutti i Mi piace — e ogni riscontro di conteggio su una chiave di partizione. Il conteggio è corretto; la partizione potrebbe ancora rallentare.
  • Riconciliare raramente, riparare chirurgicamente. La deriva dovrebbe essere prossima allo zero per ogni mutazione è condizionato. Tratta una mancata corrispondenza come un bug da trovare, non come un numero da sovrascrivere.

Lettura correlata: design a tabella singola per il motivo del post e Mi piace condividono una partizione e Query vs Scan per il motivo contare i bambini al momento della lettura è lo schema che stai evitando.

Quindi scarica DynoTable per ispezionare queste raccolte di articoli e verificare il tuo corrisponde ai tuoi stessi tabelle.

Aggiornato