DynamoDB'de Denormalizasyon
SQL'den geliyorsanız denormalizasyon bir günah gibi gelir — yinelenmiş veri, tek bir doğruluk kaynağı yok. DynamoDB'de ise asıl mesele budur. Join yoktur, bu yüzden ilgili veriyi ona ihtiyaç duyan öğenin üzerine kopyalar ve tek seferde geri okursunuz.
DynamoDB'de denormalizasyon nedir?
DynamoDB'de denormalizasyon, ilgili veriyi onu okuyan öğenin üzerine kopyalamak demektir; böylece tek bir sorgu her şeyi tek seferde geri verir. DynamoDB'de join olmadığından, okuma zamanında tabloları birbirine dikmek yerine yazma zamanında ön-join yaparsınız. Takas, bayatlıktır — yalnızca nadiren değişen değerleri kopyalayın.
- Join olmaması, yazma zamanında ön-join yapmanız demektir. İlgili değeri onu okuyan öğenin üzerinde saklayın, böylece bir sorgu asla ikinci bir aramaya ihtiyaç duymasın.
- İki türü vardır. İç içe veriyi tek bir öğedeki bir karmaşık nitelikte gömün ya da bir değeri birçok öğe boyunca kopyalayın.
- Tuzak bayatlıktır. Kaynak değiştiğinde, güncellemeyi fan-out edene kadar her kopya yanlıştır. Yalnızca nadiren değişen değerleri kopyalayın.
- Yazma değil, okuma satın alır. Daha fazla (ve daha dikkatli) yazmayı, ucuz, tek istekli okumalarla takas edersiniz.
Geri dönülecek bir join'in neden olmadığı
İlişkisel bir JOIN, normalize edilmiş satırları okuma zamanında yeniden
birleştirir. DynamoDB'de join yoktur — bir Query tek bir öğe koleksiyonunu okur
ve orada saklananı aynen geri verir. Sizin için iki tabloyu birbirine diken hiçbir
şey yoktur.
Yani veri zaten okumaya göre şekillendirilmiş olmalıdır. Bir ekran bir gönderiye ve yazarının adına ihtiyaç duyuyorsa, o ad, gönderi okumasının zaten dokunduğu bir yerde yaşamalıdır. 2007 tarihli Amazon Dynamo makalesi bu takası açık hâle getirdi: ölçekte öngörülebilir, tek haneli milisaniyelik okumalar elde etmek için ilişkisel özellikleri bırakın.
Desen 1 — bir karmaşık nitelikle gömme
DynamoDB nitelikleri yalnızca skalerler değil, iç içe map'ler ve list'ler de tutabilir. Yani denormalizasyonun yaygın bir biçimi, bir alt nesneyi kendi öğesine vermek yerine doğrudan üst öğesinin içine tıkmaktır.
Etiketleri ve küçük bir yazar anlık görüntüsüyle birlikte tek bir öğedeki bir gönderi:
| PK | SK | author | tags |
|---|---|---|---|
| POST#9f3 | META | {id: U#12, name: "Mara Vance"} | ["dynamodb","aws"] |
Tek bir GetItem, gönderiyi, etiketleri ve yazar bloğunu birlikte döndürür.
İkinci okuma yok. Bu, üst öğe tarafından sahiplenilen ve boyutu sınırlı veri
için harikadır — bir avuç etiket, bir yazar anlık görüntüsü.
Uyulacak sınır: tek bir DynamoDB öğesi, nitelik adları ve değerleri dâhil 400 KB ile sınırlıdır (Hizmet Kotaları). Sınırsız bir list gömün (viral bir gönderideki her yorum) ve onu aşarsınız.
Desen 2 — bir değeri öğeler boyunca kopyalama
Blog örneği ders kitabı örneğidir. Gönderileri listelersiniz ve her satırın yazarın görünen adını göstermesini istersiniz — ama onu almak için gönderi başına ikinci bir okuma istemezsiniz.
Bu yüzden gönderi oluşturulduğunda yazarın adını her gönderi öğesine yazarsınız:
| PK | SK | authorId | authorName | title |
|---|---|---|---|---|
| POST#9f3 | META | U#12 | "Mara Vance" | "Modeling 1:N" |
| POST#a71 | META | U#12 | "Mara Vance" | "Sparse GSIs" |
| POST#b04 | META | U#88 | "Lio Tan" | "Query vs Scan" |
Artık Query PK begins_with "POST#" (ya da gönderiler üzerinde bir GSI) tüm
listeyi — başlık ve yazar — satır başına arama olmadan oluşturur. Yazar adı
denormalize edilmiştir: kanonik kopya USER#12 üzerinde yaşar ve her gönderi
kendi kopyasını taşır.
Takas tam orada. Bir N+1 okumasını tek bir okumaya dönüştürdünüz; bedeli
"Mara Vance"'ı N+1 yerde tutmaktır.
Gömme ile kopyalama — hangisi
| Gömme (karmaşık nitelik) | Kopyalama (öğeler boyunca kopya) | |
|---|---|---|
| Şekil | alt öğe üst öğenin içine iç içe | aynı değer birçok öğede |
| En uygun | sınırlı, üst öğeye ait veri | birçok öğenin gösterdiği paylaşılan değer |
| Okuma | tek bir GetItem | tek bir Query |
| Güncelleme maliyeti | tek üst öğeyi yeniden yaz | her kopyaya fan-out |
| Boyut riski | 400 KB öğe sınırı | öğe başına yok |
Alt öğe yalnızca üst öğesiyle birlikte göründüğünde gömmeye uzanın. Birçok bağımsız öğe aynı paylaşılan değeri göstermesi gerektiğinde kopyalamaya uzanın.
Tuzak: bayat kopyalar
İşte ısıran kısım. Mara kendini "Mara V." olarak yeniden adlandırır. USER#12'yi
güncellersiniz. Siz onları gidip düzeltene kadar her gönderi öğesi hâlâ
"Mara Vance" der.
Yani kopyalanmış bir değeri güncellemek bir tek satır değil, bir fan-out yazmasıdır. Etkilenen her öğeyi sorgular ve her birini yeniden yazarsınız — ideal olarak yalnızca eski değeri hâlâ tutan satırlara dokunmanız için korumalı:
UPDATE POST#9f3
SET authorName = "Mara V."
WHERE authorName = "Mara Vance"
authorName'e karşı bu koşullu SET'i
Expression Builder içinde oluşturup üretilen
UpdateExpression ve ConditionExpression'ı doğrudan kodunuza kopyalayabilirsiniz.
Fan-out'un kendisi öğe başına bir yazmadır: yazarın gönderilerini sorgulayın, ardından güncellemeleri yapın. Sıra:
Veriyi kopyalamanın maliyeti: kaynaktaki her değişiklik, kopya başına bir sorgu artı bir yazmadır.
İşte bu yüzden kural yalnızca nadiren değişen değerleri kopyalayındır. Bir görünen ad, bir plan katmanı, bir kategori etiketi — sorun değil. Canlı bir sayaç ya da sık düzenlenen bir alan — kopyalamayın; fan-out sizi diri diri yer.
Normalizasyon hâlâ ne zaman kazanır
Bir değer sık değişiyorsa ya da bir öğe gerçekten öngörülemeyen desenlerle okunuyorsa, onu normalize tutun ve fazladan okumayı kabul edin. Denormalizasyon, bilinen, okuma ağırlıklı erişim desenleri için bir optimizasyondur — her yere uygulanacak bir varsayılan değildir. Gerçekten çalıştırdığınız okumaları ön-join edin, gerisini rahat bırakın.
Bu kopyalanmış niteliklerin nerede yaşadığına karar vermek için önce erişim desenlerini modelleyin — bkz. tek tablo tasarımı ve takasın okuma tarafı için Query vs Scan.
Denormalize bir tabloyu incelemek, hangi kopyaların kaydığını görmek ve fan-out güncellemesini kendi verinize karşı çalıştırmak için DynoTable'ı indirin.