Orta7 dakikalık okuma

DynamoDB'de Çoka-Çok İlişkiler

Bir öğrenci birçok kursa kaydolur; bir kurs birçok öğrenciyi barındırır. SQL'de bir join tablosu ve dört yönlü bir JOIN'e uzanırsın.

DynamoDB'nin join'leri yoktur, bu yüzden ilişkinin anahtarlarda yaşaması gerekir — ve püf nokta, her kayıt kenarını her iki tarafın da doğrudan Query edebileceği bir şekilde saklamaktır.

Bu kılavuz öğrenciler ↔ kurslar problemini baştan sona işler: erişim desenleri, bunları çözen komşuluk-listesi deseni, kopyalayabileceğin özgün bir anahtar şeması ve tabloyu hiç taramadan her iki yönü nasıl geri okuyacağın.

DynamoDB'de çoka-çok ilişki nasıl modellenir?

DynamoDB'nin join'leri yoktur; bu nedenle çoka-çok ilişkileri komşuluk-listesi (adjacency-list) deseniyle modelliyorsunuz: her bağlantıyı bir tarafı anahtar alan kendi kenar öğesi olarak saklayın, ardından anahtarları değiştiren ters çevrilmiş bir GSI ekleyin. Bir kez yazılan tek bir kenar, her iki yönden gelen sorguları ucuza yanıtlar.

  • Her kaydı, herhangi bir tarafta bir liste attribute'u olarak değil, kendi kenar öğesi olarak sakla.
  • Kenarı öğrenciye göre anahtarla (PK = STU#…, SK = ENROLL#CRS#…), böylece tek bir Query bir öğrencinin tüm kurs listesini döndürür.
  • Rolleri değiştiren bir ters çevrilmiş GSI ekle (GSI1PK = CRS#…), böylece aynı kenar "bu kursta kim var?" sorusuna da yanıt verir.
  • Bir kez yazılan bir kenar, her iki yönde de ucuza okunur — tüm oyun budur.

Önce erişim desenlerini çerçevele

DynamoDB modelleme erişim-deseni önceliklidir: tek bir attribute adı seçmeden önce okumalara karar verirsin. Bir çoka-çok ilişkinin neredeyse her zaman varlık aramalarına ek olarak iki simetrik okuması vardır:

  • Bir öğrencinin profilini al ve o öğrencinin kayıtlı olduğu her kursu listele.
  • Bir kursun meta verilerini al ve o kursa kayıtlı her öğrenciyi listele.
  • Tek bir kayıt kenarını ara — bir notu güncellemek veya kurstan çıkmak için.

Sorun şu: iki liste okuması, aynı kenar kümesi boyunca zıt yönleri gösterir. Naif bir tasarım birini ucuza sunar ve diğeri için bir Scan'e zorlar — Query ve Scan'de işlenen tam ayak tuzağı (footgun).

Görev, her iki yönü de tek bir Query haline getirmektir.

Komşuluk-listesi desenini kullan

DynamoDB'nin ilişkiler için kendi rehberliği komşuluk listesidir (adjacency list): her ilişkiyi, partition key'i bir uç nokta ve sort key'i diğeri olan bir öğe olarak modelle.

AWS bunu DynamoDB Developer Guide'ın Best Practices for Managing Many-to-Many Relationships sayfasında belgeler.

Neden anahtarlar, ikinci bir tablo değil? Çünkü DynamoDB'nin sana verdiği ilkel yapı, tek bir partition'a karşı bir Query'dir.

Bir Query, tek bir partition key altındaki sort-key değerlerinin bitişik bir aralığını tek bir faturalandırılan işlemde okur — bu, motorun sunduğu tek "join"dir.

Her iki taraftan da ucuza okunan bir ilişki elde etmek için kenarı çoğaltırsın: onu bir kez öğrenciye göre anahtarlanmış olarak yaz, sonra aynı kenarı kursa göre anahtarlanmış olarak yansıtmak için bir secondary index kullan.

Bu, Single-Table Tasarım'dan gelen aşırı-yüklenmiş-anahtar düşüncesidir; bir üst-alt hiyerarşi yerine bir ilişkiye uygulanmıştır.

Şekil, aynı kenarın iki üst üste yığılmış görünümüdür — temel tablo öğrenciye göre anahtarlanmış, ters çevrilmiş GSI kursa göre anahtarlanmış:

Ters çevrilmiş GSI1 kursa göre anahtarlanmışTemel tablo öğrenciye göre anahtarlanmışaynı kenar, anahtarlar takasedilmişaynı kenar, anahtarlar takasedilmişPK STU#a91SK ENROLL#CRS#math204PK STU#a91SK ENROLL#CRS#cs101GSI1PK CRS#math204GSI1SK STU#a91GSI1PK CRS#cs101GSI1SK STU#a91

Her kenar temel tabloya bir kez yazılır ve anahtarları takas edilmiş şekilde GSI'ye yansıtılır, böylece her iki partition'a karşı bir Query ilişkiyi ucuza okur.

Köken 2007 Amazon Dynamo makalesine kadar uzanır: partition key dağıtım birimidir ve tek anahtarlı erişim hızlı yoldur.

DynamoDB'deki ilişkiler, çoka-çok okumaları o hızlı yola büküme egzersizidir.

Örneği işle: öğrenciler ↔ kurslar

Genel anahtarlar PK ve SK ile tek bir tablo kullan ve varlık türünü değere kodla. Kayıt kenarı işin kalbidir:

PKSKattributes
STU#a91PROFILEname, year, major
STU#a91ENROLL#CRS#math204 enrolledOn, grade
STU#a91ENROLL#CRS#cs101enrolledOn, grade
CRS#math204METADATAtitle, credits, term
CRS#cs101METADATAtitle, credits, term

Tek bir Query PK = "STU#a91", öğrencinin profilini ve her kaydını tek bir okumada döndürür. Yalnızca kurs kenarlarını almak için onu SK begins_with "ENROLL#" ile daralt. Bu, "bir öğrencinin kurslarını listele" sorununu çözer.

Ancak "bir kursun öğrencilerini listele" diğer yönü gösterir — ve temel tablo buna yanıt veremez, çünkü öğrenci id'si partition key'dedir, sort key'de değil.

Rolleri takas eden ters çevrilmiş bir global secondary index ekle. Kenar öğelerine, kursu partition tarafında ve öğrenciyi sort tarafında tutan genel bir GSI1PK/GSI1SK çifti ver:

PKSKGSI1PKGSI1SK
STU#a91ENROLL#CRS#math204CRS#math204STU#a91
STU#b30ENROLL#CRS#math204CRS#math204STU#b30
STU#a91ENROLL#CRS#cs101CRS#cs101STU#a91

Artık Query GSI1 WHERE GSI1PK = "CRS#math204" o kursta kayıtlı her öğrenciyi listeler — temel tablonun sunamadığı okuma. Bir kez yazılan tek bir kenar öğesi, her iki yöne de yanıt verir.

Bu bir LSI değil, bir GSI olmalı: kurs partition'ı öğrenci partition'ından tamamen farklıdır ve bir LSI temel tablonun partition key'ini paylaşır.

Index birden çok partition'a yayılır, bu yüzden global olmalıdır — bkz. GSI ve LSI.

Bir incelik: DynamoDB'deki GSI'ler asenkron olarak doldurulur. Yepyeni bir kayıt, CRS#… yönünde görünmesi bir an alabilir.

Kurs listesi okumasını nihai tutarlı (eventually consistent) olarak ele al — bunu Developer Guide global secondary index'ler için açıkça belirtir.

DynoTable'da yaz ve oku

Kaydı yazmak, kenarın kendi verisine ek olarak dört anahtar attribute'u ayarlamak demektir. Bir öğrencinin aynı kursa iki kez kaydolmasını durduran koşul, bileşik anahtar üzerinde bir attribute_not_exists(PK) korumasıdır.

Bu tam olarak, ExpressionAttributeNames'i ve placeholder değerlerini elle yazmak yerine DynamoDB Expression Builder ile görsel olarak oluşturabileceğin türden bir koşuldur.

DynoTable'da bir Query'yi GSI1'e yönlendirir, GSI1PK = "CRS#math204" ayarlar ve liste, yerinde okuyabileceğin, sıralayabileceğin ve düzenleyebileceğin bir tablo olarak geri gelir — ilişkinin her iki yönü de tek bir şemadan taranabilir.

Bir kursa kayıtlı her öğrenciyi listelemek için DynoTable'da ters çevrilmiş GSI'yi sorgulama.
Bir kursa kayıtlı her öğrenciyi listelemek için DynoTable'da ters çevrilmiş GSI'yi sorgulama.

Tuzaklar ve sonraki adımlar

  • Bir tarafı liste attribute'u olarak saklama. Öğrenci öğesi üzerinde bir courseIds dizisi, bir kursun listesine ihtiyaç duyana, dizi 400 KB öğe tavanına çarpana veya iki kayıt yarışıp birbirini ezene kadar düzenli görünür. Ayrık kenar öğeleri bağımsız olarak ölçeklenir ve güncellenir.
  • Kenar verisini kenarda tut. Kaydın grade ve enrolledOn'u, öğrenciye veya kursa çoğaltılmış değil, kenar öğesine aittir — güncellemek için her (öğrenci, kurs) çifti başına tam olarak bir satır vardır.
  • GSI yayılımına dikkat et. Ters-index yönü nihai tutarlıdır, bu yüzden bir kayıttan hemen sonraki bir okuma saniyenin bir kesri kadar gecikebilir.
  • Yalnızca listenin ihtiyaç duyduğunu yansıt. Liste görünümü yalnızca id'lere ihtiyaç duyduğunda, bir KEYS_ONLY veya dar bir yansıtma GSI'yi küçük tutar.

Çevreleyen desenlere daha derinlemesine inmek için, aşırı yüklenmiş anahtarlar için Single-Table Tasarım'ı ve ters index'in ne zaman global olması gerektiği için GSI ve LSI'yi oku.

Sonra öğrenciler ↔ kurslar şemasını gerçekten modellemek için DynoTable'ı indir — kenarları yaz, koşulu Expression Builder ile oluştur ve ilişkinin her iki yönünü de tek bir tarama yapmadan sorgula.

Güncellendi