DynamoDB'de Bire-Çok İlişkiler
Bir SaaS kontrol düzleminde neredeyse her zaman bir kapsama hiyerarşisi vardır: bir
çalışma alanı birçok projeye sahiptir. SQL'de projeler tablosuna bir
workspace_id yabancı anahtarı koyar ve JOIN yapardınız.
DynamoDB'de join'ler ve yabancı anahtarlar yoktur, bu yüzden ilişki
anahtar şemasının kendisinde yer almak zorundadır. Doğru yapıldığında, "bir
çalışma alanını ve içindeki her projeyi yükle" işlemi, bir okuma artı takip eden
bir tarama yerine tek bir Query'e dönüşür.
DynamoDB'de bire-çok ilişkiyi nasıl modellersiniz?
Ebeveyne ve tüm alt kayıtlarına aynı verin ki tek bir paylaşsınlar, sonra onları sıralama anahtarıyla birbirinden ayırın. DynamoDB'de join veya yabancı anahtar yoktur, bu yüzden ilişki anahtar şemasının kendisinde yer alır. Bir ebeveyni artı her alt kaydını yüklemek böylece bir join yerine tek bir Query'e dönüşür.
- Varlıkları değil, okumaları modelleyin. Bire-çok ilişki yalnızca "bir çalışma alanının projelerini listele"ye hizmet etmek için vardır — anahtarları bu sorgunun etrafında şekillendirin.
- Ebeveyni alt kaydın kodlayın. Çalışma alanına ve tüm projelerine aynı bölüm anahtarı değerini verin ki tek bir düşsünler.
- Böylece liste okuması tek bir
Query'dir. Ebeveyn artı alt kayıtları birlikte geri gelir — join yok, ikinci gidiş-dönüş yok (birQuerysayfa başına 1 MB'a kadar döndürür, bunun ötesindeLastEvaluatedKeyile sayfalayarak). - dikkat edin. Tek bir devasa kiracı tüm trafiğini tek bir bölümde yoğunlaştırır; dev bir çalışma alanı, parçalanmış (sharded) bir anahtara ve yayılan (fan-out) bir okumaya ihtiyaç duyabilir.
Önce erişim deseni
DynamoDB modellemesi varlık-öncelikli değil, erişim-deseni-önceliklidir — tek tablo tasarımının ardındaki aynı disiplin. Herhangi bir anahtar seçmeden önce, uygulamanın gerçekten yaptığı okumaları yazın:
- Bir çalışma alanının ayarlarını getir.
- Bir çalışma alanındaki her projeyi, en yenisi önce olacak şekilde listele.
- Belirli bir projeyi kimliğine göre getir.
"Bir çalışma alanı, birçok proje" ilişkisi yalnızca 2 numaralı okuma yüzünden önemlidir. Bir çalışma alanının projelerini birlikte listelemeye asla ihtiyaç duymasaydınız, ilişkiyi hiç modellemezdiniz — projeleri bağımsız olarak saklardınız.
Yani soru asla soyut olarak "bire-çok ilişkiyi nasıl temsil ederim?" değildir. "Bu ilişki hangi sorgulara hizmet etmeli?" sorusudur. Buna cevap verin, sonra anahtarları bunun etrafında şekillendirin.
Bir yabancı anahtar burada neden işe yaramaz
DynamoDB'de her GetItem ve Query bir bölüm anahtarını hedefler ve servis, öğeyi
tutan bölümü bulmak için o anahtarı hash'ler.
AWS bunu Temel Bileşenler belgelerinde doğrudan söylüyor: bölüm anahtarı değeri, verinin nerede yaşadığına karar veren dahili bir hash fonksiyonunun girdisidir.
Bu hash tabanlı yerleştirme, tutarlı hashleme ile anahtarların düğümler arasında dağıtıldığı orijinal 2007 tarihli Dynamo: Amazon's Highly Available Key-value Store makalesinden gelen mirastır.
Bir proje öğesindeki çıplak bir workspace_id özniteliği bu mekanizma için
görünmezdir — DynamoDB onu "takip" edemez.
İlgili öğeleri tek bir istekte getirmek için, ebeveynin kimliği projenin
bölüm anahtarına kodlanmalıdır ki bir çalışma alanının tüm öğeleri aynı bölüme
hash'lensin ve tek bir Query onları süpürebilsin.
İşlenmiş örnek: çalışma alanları ve projeler
Genel, aşırı yüklenmiş (overloaded) bir anahtar şeması kullanın. Bölüm anahtarına
EntityRef, sıralama anahtarına Detail deyin. Çalışma alanı kimliği, hem
çalışma alanı öğesi hem de altındaki her proje için EntityRef'e gider:
| EntityRef | Detail | attributes |
|---|---|---|
| WS#acme | META | displayName, region, seatLimit |
| WS#acme | PROJ#2026-0007 | title, status, createdBy |
| WS#acme | PROJ#2026-0042 | title, status, createdBy |
| WS#acme | PROJ#2026-0118 | title, status, createdBy |
| WS#globex | META | displayName, region, seatLimit |
| WS#globex | PROJ#2026-0009 | title, status, createdBy |
Çalışma alanı ve tüm projeleri EntityRef = "WS#acme" değerini paylaşır, bu yüzden
tek bir bölümde birlikte yaşayan tek bir öğe koleksiyonu oluştururlar.
Detail sıralama anahtarı onları ayırır: META çalışma alanı kaydıdır ve her proje,
projelerin doğal olarak sıralanması için sıfırla doldurulmuş, zaman sıralı bir kimlikle
birlikte bir PROJ# öneki taşır.
Görsel olarak, ebeveyn ve alt kayıtları tek bir bölüm içinde, sıralama anahtarına göre sıralanmış olarak istiflenir:
EntityRef = "WS#acme" üzerinde tek bir Query, tüm yığını — ebeveyn artı her alt
kaydı — tek bir okumada süpürür.
Artık üç erişim deseninin her biri tek bir çağrıya çöker:
- Çalışma alanı ayarları —
GetItem(EntityRef="WS#acme", Detail="META"). - Projeleri en yenisi önce listele —
Detail begins_with "PROJ#"ileQuery(EntityRef="WS#acme"), azalan sırada çalıştırılır (ScanIndexForward = false). - Tek bir proje —
GetItem(EntityRef="WS#acme", Detail="PROJ#2026-0042").
İkincisi işin bütün amacıdır: ebeveyn ve alt kayıtları tek bir Query'den geri
gelir, join yok ve ikinci gidiş-dönüş yok — DynamoDB sayfa başına 1 MB'a kadar döndürür
ve gerisini getirmeniz için size bir LastEvaluatedKey verir. Bu, bir yabancı anahtar
özniteliği ve bir Scan ile yapamayacağınız hamledir.
O begins_with koşulunu elle yazmak zahmetlidir — anahtar-koşul ve yansıtma-ifadesi
(projection-expression) sözdizimi zorlar.
DynamoDB İfade Oluşturucu, grameriyle boğuşmayasınız
diye KeyConditionExpression'ı, #name/:value yer tutucu eşlemelerini ve çalıştırmaya
hazır bir SDK parçacığını üretir:
KeyConditionExpression "#er = :er AND begins_with(#d, :p)"
ExpressionAttributeNames { "#er": "EntityRef", "#d": "Detail" }
ExpressionAttributeValues { ":er": "WS#acme", ":p": "PROJ#" }
Öğe koleksiyonunu DynoTable'da inceleyin
Bu düzenin getirisi görseldir: bir EntityRef'i paylaşan her satır, birbirinin yanında
oturan çalışma alanı artı alt kayıtlarıdır.
DynoTable onları gruplar, böylece bire-çok ilişkiyi ayrı tablolar arasında tahmin etmeye çalışmak yerine tek bir bitişik blok olarak görürsünüz.

Tuzaklar ve alternatif şekil
Dikkat edilecek birkaç şey:
- Sıcak bölümler. Bir çalışma alanına ait her öğe tek bir bölümde yaşar, bu yüzden
tek bir çok büyük veya çok yoğun kiracı trafiği yoğunlaştırır. AWS'nin tanımladığı
uyarlanabilir kapasite
davranışı ılımlı çarpıklığı emer, ancak milyonlarca projesi olan bir çalışma alanı
parçalanmış bir anahtara (ör.
WS#acme#01 … #10) ve yayılan bir okumaya ihtiyaç duyabilir. - Öğe koleksiyonu boyutu. Bir yerel ikincil dizinle (LSI), tek bir bölümün öğe koleksiyonu 10 GB ile sınırlıdır; bir LSI olmadan böyle bir sınır yoktur. Burada dizin türlerini tartıyorsanız, GSI ve LSI karşılaştırmasına bakın.
Scan'e değil, hepQuery'e uzanın. Bütün tasarım, tek bir bölümüQueryyapabilesiniz diye vardır. "Bir çalışma alanının projelerini bulmak" için filtrelenmiş birScan'e geri dönmek, modeli çöpe atar ve tüm tabloyu okur — Query ve Scan bölümünde ele alınan tuzak.
Projeleri çalışma alanları arası listelemeniz gerçekten gerekiyorsa (diyelim ki
küresel olarak tüm status = ACTIVE projeler), temel tablo buna cevap veremez — bölüm
anahtarı çalışma alanı kapsamlıdır.
Bu, bu ilişkiyi yeniden şekillendirmenin değil, projeleri farklı bir öznitelik üzerinde yeniden bölümleyen bir ikincil dizinin işidir.
Sonraki adımlar
Erişim desenlerini modelleyin, ebeveyni alt kaydın bölüm anahtarına kodlayın, ve
bire-çok okuması tek bir Query olur. Anahtar koşulunu DynamoDB İfade Oluşturucu
ile oluşturun ve doğrulayın — erişim desenlerinin kendisinden başlamayı tercih
ederseniz, ücretsiz Single-Table Design aracı
PK/SK/GSI planını örnek öğelerle birlikte taslaklar.
Sonra bu şemayı yüklemek, çalışma alanı→projeler öğe koleksiyonuna canlı olarak göz
atmak ve her sorgunun tam olarak bir okuma yaptığını doğrulamak için DynoTable'ı
indirin. Çalışma alanlarını ve projeleri birleştirilmiş, ilişkisel bir
görünüm olarak görmeyi tercih ederseniz, DynoTable'ın SQL
Workbench'i o JOIN'i de çalıştırır.


