Mengapa GSI DynamoDB Bersifat Eventually Consistent
Anda menulis sebuah Item, langsung mem-query Global Secondary Index untuknya, dan
mendapat nihil — meski penulisan berhasil dan GetItem tabel dasar
mengembalikan Item itu baik-baik.
Tidak ada yang rusak. Anda menabrak properti GSI yang paling mengejutkan: setiap pembacaan GSI bersifat eventually consistent. Ada jendela singkat setelah penulisan di mana index belum menyusul.
Apakah GSI DynamoDB bersifat eventually consistent?
Ya — setiap pembacaan Global Secondary Index bersifat eventually consistent, tanpa
cara untuk menonaktifkannya. Penulisan Anda commit ke tabel dasar dulu, lalu
berpropagasi secara asinkron ke index, jadi sebuah query yang dikeluarkan tepat
setelah penulisan bisa mengembalikan baris yang basi atau hilang. DynamoDB tidak
menyediakan flag ConsistentRead untuk GSI.
- Sebuah GSI adalah tabel terpisah yang direplikasi secara asinkron — penulisan Anda commit ke tabel dasar dulu, lalu berpropagasi ke index.
- Tidak ada flag
ConsistentReaduntuk GSI. Tak seperti tabel dasar, Anda tak bisa memaksa strong read untuk menutup celahnya. - Read-your-own-writes dari tabel dasar, bukan GSI. Anda sudah memegang primary key tepat setelah penulisan.
- Tegakkan keunikan dengan conditional write, bukan query GSI. Celah propagasi mengubah pengecekan "apakah ini sudah dipakai?" menjadi sebuah race.
Gejalanya: sebuah sign-up yang "tak bisa menemukan dirinya sendiri"
Ambil tabel Members untuk layanan akun-user. Tabel dasar berkey id internal,
tetapi user login dengan email, jadi ada GSI lookup-email:
| PK | SK | displayName | |
|---|---|---|---|
| ACC#a1f9c | PROFILE | ada@northwind.test | Ada L. |
| GSI1PK | GSI1SK |
|---|---|
| ada@northwind.test | ACC#a1f9c |
Alur sign-up melakukan dua hal berturut-turut: PutItem member baru, lalu
Query EmailIndex WHERE GSI1PK = "ada@northwind.test" untuk memeriksa tak ada orang
lain yang mengklaim alamat itu dan memuat profilnya.
Jalankan kedua panggilan itu berselang beberapa milidetik dan Query-nya bisa
mengembalikan nol Item. Lakukan lagi sedetik kemudian dan barisnya ada di sana.
Penulisan tidak gagal — index-nya saja yang belum diperbarui.
Mengapa ini terjadi: GSI direplikasi secara asinkron
Sebuah GSI adalah tabel terpisah yang dikelola secara internal dengan partition dan key schema-nya sendiri. Ia tidak dipelihara di dalam transaksi yang sama dengan penulisan tabel-dasar Anda.
Saat Anda PutItem, DynamoDB secara tahan lama commit ke tabel dasar, mengakui
penulisan Anda, lalu kemudian berpropagasi secara asinkron ke setiap GSI.
Dokumentasi GSI AWS
menyatakannya jelas: GSI hanya mendukung pembacaan eventually consistent.
Delay propagasi antara penulisan tabel-dasar dan pembaruan index biasanya sepersekian detik — tetapi ia tidak dijamin dan tidak terbatas di bawah beban. Mendesain seolah-olah ia terbatas adalah jebakannya.
Ini bukan bug; ini adalah trade-off desain Dynamo asli. Makalah Amazon Dynamo 2007 memilih ketersediaan dan toleransi partisi di atas strong consistency.
GSI mewarisi garis keturunan itu. Coupling longgar itulah yang membuat index menskala dan tetap dapat ditulis secara independen dari tabel dasar.
Celah antara 200 OK dan "replikasi perubahan" adalah jendela di mana pembacaan
index Anda basi. Tidak ada flag consistent-read yang menutupnya.
Tak seperti tabel dasar — di mana Anda melewatkan ConsistentRead = true untuk
memaksa GetItem/Query yang strongly consistent — GSI menolak mentah-mentah opsi itu.
Sebuah LSI bisa dibaca secara kuat karena ia berbagi partition tabel dasar; lihat GSI vs LSI untuk alasan perbedaan itu ada.
Jebakan yang lebih halus: nilai lama yang basi, bukan hanya yang baru hilang
Kasus baris hilang adalah yang jelas. Bug yang lebih senyap adalah membaca nilai sebelumnya yang basi.
Misalkan Ada mengubah emailnya dari ada@northwind.test ke ada.l@northwind.test.
Tabel dasar memperbarui secara atomik, tetapi sesaat GSI masih bisa mengembalikan
entri index lama.
Lookup terhadap nilai baru meleset, sementara nilai yang ditinggalkan masih teresolusi.
Lebih buruk: jika Anda mem-query GSI dan menulis balik berdasarkan apa yang Anda baca, Anda bisa bertindak atas nilai yang tak lagi ada. Perlakukan setiap pembacaan GSI sebagai snapshot yang mungkin tertinggal dari realita.
Desain di sekitarnya — jangan melawannya
Jendela propagasi itu nyata, jadi solusinya arsitektural, bukan tombol retry yang Anda putar. Empat pola, kurang-lebih dalam urutan preferensi:
Read-your-own-writes dari tabel dasar. Tepat setelah penulisan Anda sudah memegang primary key (
ACC#a1f9c), jadi lakukanGetItemyang strongly consistent pada tabel dasar alih-alih mem-query GSI.GSI adalah untuk pola akses lain — "saya punya email, temukan akunnya" — bukan untuk mengonfirmasi penulisan yang baru Anda lakukan.
Tegakkan keunikan dengan Item guard, bukan GSI. Jangan pernah memercayai query GSI untuk membuktikan sebuah email belum diklaim — celah propagasi menjadikannya race yang bisa kalah oleh dua sign-up simultan.
Sebagai gantinya, tulis Item keunikan khusus berkey pada email itu sendiri (
PK = "EMAIL#ada@northwind.test") di dalamTransactWriteItemsdenganConditionExpressionberupaattribute_not_exists(PK).Kondisi tabel-dasar yang strongly consistent, diterapkan secara atomik, itulah yang sebenarnya menegakkan keunikan.
TransactWriteItems: - Put member item (PK = ACC#a1f9c, SK = PROFILE) - Put uniqueness item (PK = EMAIL#ada@northwind.test) ConditionExpression: attribute_not_exists(PK)Jika sign-up kedua berlomba untuk alamat yang sama, kondisinya gagal dan seluruh transaksi ditolak — tanpa GSI, tanpa delay propagasi, tanpa double-claim.
Bangun dan pratinjau kondisi
attribute_not_existsitu dengan DynamoDB Expression Builder sebelum Anda merangkainya ke dalam kode.Toleransi lag-nya di UX. Saat pembacaan GSI memang alat yang tepat (login dengan email untuk user yang sudah ada), jendelanya sub-detik dan tak berbahaya — akun mapan sudah berpropagasi lama.
Cadangkan jalur tabel-dasar yang strongly consistent hanya untuk momen read-after-write.
Query-ulang, jangan berasumsi. Jika sebuah alur kerja harus mengamati Item yang betul-betul baru melalui GSI, perlakukan hasil kosong sebagai "belum terlihat", bukan "tidak ada", dan query-ulang setelah backoff singkat.
Tetapi utamakan pola 1 dan 2, yang menghilangkan tebakan sepenuhnya.
Lihat sendiri celah propagasinya
Cara tercepat membangun intuisi adalah menyaksikannya terjadi. Di DynoTable Anda mem-put sebuah Item ke tabel dasar dan langsung mem-query GSI di tab kedua.
Pada tabel yang terbebani Anda sesekali akan menangkap index tertinggal dari data dasar, lalu menyaksikannya konvergen pada refresh berikutnya.
Melihat lag itu dengan data Anda sendiri membuat aturan "read-your-own-writes dari tabel dasar" melekat jauh lebih baik daripada diagram mana pun.
Jebakan dan langkah berikutnya
- Jangan gerbangi logika pada read-after-write GSI. Pengecekan keunikan, konfirmasi "apakah penulisan saya mendarat", dan loop read-modify-write berada di tabel dasar yang strongly consistent.
- Jangan raih
ConsistentReadpada GSI — ia tak diizinkan dan akan error. - Jangan modelkan pola akses sebagai GSI saat key dasar sudah menjawabnya. Layani pembacaan dari primary key dan Anda melewati jendela propagasi sepenuhnya.
Memilih bentuk key yang tepat adalah seluruh permainan dalam
single-table design; mengetahui kapan Query
mengalahkan Scan menjauhkan Anda dari index sejak awal
(Query vs Scan).
Bangun dan uji ConditionExpression keunikan Anda di
DynamoDB Expression Builder. Lalu
coba DynoTable untuk menyaksikan penulisan tabel-dasar berpropagasi ke
GSI secara real time, dan rancang key Anda agar jendela eventual-consistency tak
pernah menggigit.