Bir DynamoDB GSI Neden Nihai Tutarlıdır
Bir öğe yazarsın, hemen onun için bir Global Secondary Index sorgularsın ve geri
hiçbir şey almazsın — yazma başarılı olmuş ve temel tabloya bir GetItem öğeyi gayet
iyi döndürse bile.
Bozuk bir şey yok. GSI'lerin en şaşırtıcı özelliğine çarptın: bir GSI'nin her okuması nihai tutarlıdır (eventually consistent). Bir yazmadan sonra, index'in henüz yetişmediği kısa bir pencere vardır.
DynamoDB GSI'leri nihai tutarlı mıdır?
Evet — bir Global Secondary Index'in her okuması nihai tutarlıdır (eventually consistent) ve bundan vazgeçmenin bir yolu yoktur. Yazman önce temel tabloya işlenir, sonra index'e asenkron olarak yayılır; bu yüzden bir yazmadan hemen sonra yapılan bir sorgu eski (stale) ya da eksik satırlar döndürebilir. DynamoDB bir GSI için ConsistentRead bayrağı sunmaz.
- Bir GSI, ayrı, asenkron olarak çoğaltılan bir tablodur — yazman önce temel tabloya işlenir, sonra index'e yayılır.
- Bir GSI için
ConsistentReadbayrağı yoktur. Temel tablonun aksine, boşluğu kapatmak için güçlü bir okumayı zorlayamazsın. - Kendi yazmalarını GSI'den değil, temel tablodan oku. Bir yazmadan hemen sonra birincil anahtarı zaten elinde tutuyorsun.
- Benzersizliği bir GSI sorgusuyla değil, koşullu bir yazmayla zorla. Yayılım boşluğu, bir "bu alındı mı?" kontrolünü bir yarışa dönüştürür.
Belirti: "kendini bulamayan" bir kayıt
Bir kullanıcı-hesapları servisi için bir Members tablosu al. Temel tablo dahili bir
id ile anahtarlanmıştır, ama kullanıcılar e-posta ile giriş yapar, bu yüzden bir
e-posta-arama GSI'si vardır:
| PK | SK | displayName | |
|---|---|---|---|
| ACC#a1f9c | PROFILE | ada@northwind.test | Ada L. |
| GSI1PK | GSI1SK |
|---|---|
| ada@northwind.test | ACC#a1f9c |
Kayıt akışı arka arkaya iki şey yapar: yeni üyeyi PutItem eder, sonra başka kimsenin
o adresi almadığını kontrol etmek ve profili yüklemek için
Query EmailIndex WHERE GSI1PK = "ada@northwind.test" yapar.
Bu iki çağrıyı birkaç milisaniye arayla çalıştır ve Query sıfır öğe
döndürebilir. Bunu bir saniye sonra tekrar yap ve satır oradadır. Yazma başarısız
olmadı — index henüz güncellenmemişti.
Bu neden olur: GSI'ler asenkron olarak çoğaltılır
Bir GSI, kendi partition'ları ve kendi anahtar şeması olan ayrı, dahili olarak yönetilen bir tablodur. Temel-tablo yazmanla aynı işlem (transaction) içinde sürdürülmez.
PutItem yaptığında, DynamoDB temel tabloya dayanıklı olarak işler, yazmanı onaylar ve
sonra değişikliği her GSI'ye asenkron olarak yayar. AWS
GSI dokümantasyonu
bunu açıkça belirtir: GSI'ler yalnızca nihai tutarlı okumaları destekler.
Bir temel-tablo yazması ile index güncellemesi arasındaki yayılım gecikmesi genellikle bir saniyenin kesridir — ama yük altında garanti edilmez ve sınırlandırılmaz. Sanki sınırlıymış gibi tasarlamak tuzaktır.
Bu bir hata değil; orijinal Dynamo tasarım ödünleşimidir. 2007 Amazon Dynamo makalesi, güçlü tutarlılık yerine erişilebilirliği ve partition toleransını seçti.
GSI'ler o soyu miras alır. Gevşek bağlantı, index'in temel tablodan bağımsız olarak ölçeklenmesini ve yazılabilir kalmasını sağlayan şeydir.
200 OK ile "değişikliği çoğalt" arasındaki boşluk, index okumanın eski (stale) olduğu
penceredir. Onu kapatan hiçbir tutarlı-okuma bayrağı yoktur.
Temel tablonun aksine — güçlü tutarlı bir GetItem/Query'yi zorlamak için
ConsistentRead = true geçtiğin yer — bir GSI o seçeneği düpedüz reddeder.
Bir LSI güçlü olarak okunabilir, çünkü temel tablonun partition'larını paylaşır; o ayrımın neden var olduğu için bkz. GSI ve LSI.
Daha ince bir tuzak: yalnızca eksik yeni değerler değil, eski eski değerler
Eksik-satır durumu bariz olandır. Daha sessiz hata, eski bir önceki değeri okumaktır.
Diyelim ki Ada e-postasını ada@northwind.test'ten ada.l@northwind.test'e
değiştiriyor. Temel tablo atomik olarak güncellenir, ama bir an için GSI hâlâ eski
index girişini döndürebilir.
Yeni değere karşı bir arama ıskalar, terk edilmiş değer ise hâlâ çözülür.
Daha kötüsü: GSI'yi sorgular ve okuduğun şeye göre geri yazarsan, artık var olmayan bir değere göre işlem yapabilirsin. Herhangi bir GSI okumasını, gerçeğin gerisinde kalabilen bir anlık görüntü olarak ele al.
Etrafında tasarla — onunla savaşma
Yayılım penceresi gerçektir, bu yüzden düzeltme mimaridir, ayarladığın bir yeniden deneme düğmesi değil. Dört desen, kabaca tercih sırasıyla:
Kendi yazmalarını temel tablodan oku. Bir yazmadan hemen sonra birincil anahtarı (
ACC#a1f9c) zaten elinde tutuyorsun, bu yüzden GSI'yi sorgulamak yerine temel tabloda güçlü tutarlı birGetItemyap.GSI, diğer erişim deseni içindir — "elimde bir e-posta var, hesabı bul" — az önce yaptığın yazmayı onaylamak için değil.
Benzersizliği GSI ile değil, bir koruma öğesiyle zorla. Bir e-postanın alınmadığını kanıtlamak için asla bir GSI sorgusuna güvenme — yayılım boşluğu bunu, eşzamanlı iki kaydın ikisinin de kaybedebileceği bir yarış haline getirir.
Bunun yerine, bir
ConditionExpression'ıattribute_not_exists(PK)olan birTransactWriteItemsiçinde, e-postanın kendisi üzerinde anahtarlanmış (PK = "EMAIL#ada@northwind.test") özel bir benzersizlik öğesi yaz.Atomik olarak uygulanan güçlü tutarlı temel-tablo koşulları, benzersizliği gerçekten zorlayan şeydir.
TransactWriteItems: - Put member item (PK = ACC#a1f9c, SK = PROFILE) - Put uniqueness item (PK = EMAIL#ada@northwind.test) ConditionExpression: attribute_not_exists(PK)İkinci bir kayıt aynı adres için yarışırsa, koşulu başarısız olur ve tüm işlem reddedilir — GSI yok, yayılım gecikmesi yok, çift talep yok.
O
attribute_not_existskoşulunu, koda bağlamadan önce DynamoDB Expression Builder ile oluştur ve önizle.Gecikmeyi UX'te tolere et. GSI okuması gerçekten doğru araç olduğunda (var olan bir kullanıcı için e-posta ile giriş), pencere saniyenin altındadır ve zararsızdır — yerleşik bir hesap çok önce yayılmıştır.
Güçlü tutarlı temel-tablo yolunu yalnızca yazmadan-sonra-okuma anına ayır.
Yeniden sorgula, varsayma. Bir iş akışı yepyeni bir öğeyi GSI üzerinden gözlem etmek zorundaysa, boş bir sonucu "henüz görünmüyor" olarak ele al, "yok" olarak değil ve kısa bir geri çekilme (backoff) sonrası yeniden sorgula.
Ama tahmin işini tamamen ortadan kaldıran 1 ve 2 numaralı desenleri tercih et.
Yayılım boşluğunu kendin gör
Sezgi geliştirmenin en hızlı yolu bunu olurken izlemektir. DynoTable'da temel tabloya bir öğe koyar ve hemen ikinci bir sekmede GSI'yi sorgularsın.
Yüklü bir tabloda, index'in temel verinin gerisinde kaldığını ara sıra yakalayacaksın, sonra bir sonraki yenilemede yakınsadığını izleyeceksin.
Gecikmeyi kendi verinle görmek, "kendi yazmalarını temel tablodan oku" kuralını herhangi bir diyagramdan çok daha iyi yerleştirir.
Tuzaklar ve sonraki adımlar
- Mantığı bir GSI yazma-sonrası-okumasına kilitleme. Benzersizlik kontrolleri, "yazmam yerine oturdu mu" onayları ve oku-değiştir-yaz döngüleri, güçlü tutarlı temel tabloya aittir.
- Bir GSI'de
ConsistentRead'e uzanma — buna izin verilmez ve hata verir. - Temel anahtar zaten yanıtladığında bir erişim desenini bir GSI olarak modelleme. Bir okumayı birincil anahtardan sun ve yayılım penceresini tamamen atla.
Doğru anahtar şeklini seçmek
single-table tasarımda tüm oyundur; bir Query'nin bir
Scan'i ne zaman yendiğini bilmek seni en baştan index'ten uzak tutar
(Query ve Scan).
Benzersizlik ConditionExpression'ını
DynamoDB Expression Builder'da oluştur ve test et.
Sonra temel-tablo yazmalarının bir GSI'ye gerçek zamanlı yayıldığını izlemek ve
anahtarlarını nihai tutarlılık penceresi seni asla ısırmayacak şekilde tasarlamak için
DynoTable'ı dene.