Menengah7 menit baca

Hubungan many-to-many di DynamoDB

Seorang siswa mendaftar di banyak kursus; suatu kursus menampung banyak siswa. Di SQL Anda menjangkau tabel join dan JOIN tiga tabel.

DynamoDB tidak punya join, jadi hubungan harus hidup di key — dan tipunya adalah menyimpan setiap edge pendaftaran dalam bentuk yang kedua belah pihak bisa Query langsung.

Panduan ini menelusuri masalah siswa ↔ kursus dari ujung ke ujung: access pattern, pattern yang menyelesaikannya, skema key asli yang bisa Anda salin, dan cara membaca kedua arah kembali tanpa pernah men-Scan tabel.

Bagaimana Anda memodelkan hubungan many-to-many di DynamoDB?

DynamoDB tidak punya join, jadi Anda memodelkan hubungan many-to-many dengan pattern : simpan setiap tautan sebagai item edge sendiri yang di-key by satu sisi, lalu tambah GSI terbalik yang menukar key. Satu edge, ditulis sekali, lalu menjawab query dari kedua arah dengan murah.

  • Simpan setiap pendaftaran sebagai item edge sendiri, bukan atribut list di salah satu sisi.
  • Key edge by siswa (PK = STU#…, SK = ENROLL#CRS#…) agar satu Query mengembalikan seluruh daftar kursus siswa.
  • Tambah terbalik yang menukar peran (GSI1PK = CRS#…) agar edge yang sama juga menjawab "siapa di kursus ini?".
  • Satu edge, ditulis sekali, dibaca murah kedua arah — itulah seluruh permainannya.

Bingkai access pattern dulu

Pemodelan DynamoDB adalah access-pattern-first: Anda memutuskan baca sebelum memilih satu nama atribut. Hubungan many-to-many hampir selalu punya dua baca simetris plus lookup entitas:

  • Ambil profil siswa, dan daftar setiap kursus yang diikuti siswa itu.
  • Ambil metadata kursus, dan daftar setiap siswa yang terdaftar di kursus itu.
  • Lookup satu edge pendaftaran — untuk meng-update nilai atau drop kursus.

Dua baca daftar menunjuk arah berlawanan lintas himpunan edge yang sama. Desain naif melayani satu dengan murah dan memaksa Scan untuk yang lain — jebakan tepat yang dibahas di Query vs Scan.

Tugasnya membuat kedua arah menjadi satu Query.

Pakai pattern adjacency-list

Panduan AWS sendiri untuk hubungan adalah adjacency list: model setiap hubungan sebagai item yang partition key-nya satu endpoint dan sort key-nya yang lain.

AWS mendokumentasikan ini di halaman Best Practices for Managing Many-to-Many Relationships DynamoDB Developer Guide.

Mengapa key dan bukan tabel kedua? Karena primitif yang DynamoDB berikan adalah Query terhadap satu partisi.

Query membaca rentang kontigu nilai sort key di bawah satu partition key dalam satu operasi yang ditagih — itu satu-satunya "join" yang ditawarkan engine.

Untuk mendapatkan hubungan yang dibaca murah dari kedua sisi, Anda menduplikasi edge: tulis sekali di-key by siswa, lalu pakai secondary index untuk memproyeksikan edge yang sama di-key by kursus.

Ini pemikiran overloaded-key dari Single-Table Design, diterapkan ke hubungan alih-alih hierarki parent-child.

Bentuknya dua tampilan bertumpuk dari edge yang sama — tabel dasar di-key by siswa, GSI terbalik di-key by kursus:

edge sama, key ditukaredge sama, key ditukarGSI1 terbalik di-key by kursusGSI1PK CRS#math204GSI1SK STU#a91GSI1PK CRS#cs101GSI1SK STU#a91Tabel dasar di-key by siswaPK STU#a91SK ENROLL#CRS#math204PK STU#a91SK ENROLL#CRS#cs101

Setiap edge ditulis sekali di tabel dasar dan diproyeksikan ke GSI dengan key ditukar, sehingga Query terhadap partisi mana pun membaca hubungan dengan murah.

Garis keturunannya kembali ke paper Dynamo Amazon 2007: partition key adalah unit distribusi, dan akses single-key adalah jalur cepat.

Hubungan di DynamoDB adalah latihan membengkokkan baca many-to-many ke jalur cepat itu.

Kerjakan contoh: siswa ↔ kursus

Pakai satu tabel dengan key generik, PK dan SK, dan encode tipe entitas di nilainya. Edge pendaftaran adalah jantungnya:

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

Satu Query PK = "STU#a91" mengembalikan profil siswa dan setiap pendaftaran dalam satu baca. Persempit dengan SK begins_with "ENROLL#" untuk hanya mendapat edge kursus. Itu menyelesaikan "daftar kursus siswa".

Tetapi "daftar siswa kursus" menunjuk arah lain — dan tabel dasar tidak bisa menjawabnya, karena id siswa ada di partition key, bukan sort key.

Tambah global secondary index terbalik yang menukar peran. Beri item edge pasangan GSI1PK/GSI1SK generik yang menampung kursus di sisi partisi dan siswa di sisi sort:

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

Sekarang Query GSI1 WHERE GSI1PK = "CRS#math204" mendaftar setiap siswa di kursus itu — baca yang tabel dasar tidak bisa layani. Satu item edge, ditulis sekali, menjawab kedua arah.

Harus GSI, bukan LSI: partisi kursus sepenuhnya berbeda dari partisi siswa, dan LSI berbagi partition key tabel dasar.

Indeks mencakup banyak partisi, jadi harus global — lihat GSI vs LSI.

GSI di DynamoDB diisi secara asinkron. Pendaftaran brand-new bisa butuh sejenak untuk muncul di arah CRS#….

Perlakukan baca roster kursus sebagai — yang Developer Guide sebutkan secara eksplisit untuk global secondary index.

Tulis dan baca di DynoTable

Menulis pendaftaran berarti menyetel empat atribut key plus data edge sendiri. Kondisi yang menghentikan siswa mendaftar dua kali di kursus yang sama adalah penjaga attribute_not_exists(PK) pada composite key.

Itu tepat jenis kondisi yang bisa Anda susun secara visual dengan DynamoDB Expression Builder alih-alih menulis tangan ExpressionAttributeNames dan nilai placeholder.

Di DynoTable Anda arahkan Query ke GSI1, set GSI1PK = "CRS#math204", dan roster kembali sebagai tabel yang bisa Anda baca, urut, dan edit in place — kedua arah hubungan bisa dijelajahi dari satu skema.

Query GSI terbalik di DynoTable untuk mendaftar setiap siswa yang terdaftar di suatu kursus.
Query GSI terbalik di DynoTable untuk mendaftar setiap siswa yang terdaftar di suatu kursus.

Jebakan dan langkah selanjutnya

  • Jangan simpan satu sisi sebagai atribut list. Array courseIds pada item siswa terasa rapi sampai kursus butuh rosternya, array menabrak langit-langit item 400 KB, atau dua pendaftaran race dan saling clobber. Item edge diskrit berskala dan meng-update secara independen.
  • Jaga data edge di edge. grade dan enrolledOn pendaftaran milik item edge, bukan diduplikasi ke siswa atau kursus — ada tepat satu baris per pasangan (siswa, kursus) untuk di-update.
  • Perhatikan propagasi GSI. Arah inverted-index eventually consistent, jadi baca segera setelah pendaftaran mungkin tertinggal sepecahan detik.
  • Project hanya yang dibutuhkan roster. Proyeksi KEYS_ONLY atau sempit menjaga GSI kecil saat tampilan roster hanya butuh id.

Untuk lebih dalam pada pattern di sekitarnya, baca Single-Table Design untuk overloaded key dan GSI vs LSI untuk kapan inverted index harus global. Dan untuk mulai dari hubungan Anda sendiri, alat gratis Single-Table Design mengubah daftar access pattern seperti "daftar kursus siswa / daftar siswa kursus" menjadi rencana PK/SK/GSI dengan contoh item.

Lalu unduh DynoTable untuk memodelkan skema siswa ↔ kursus secara nyata — tulis edge, bangun kondisi dengan Expression Builder, dan Query kedua arah hubungan tanpa satu pun scan. Dan saat Anda ingin tampilan JOIN tiga tabel klasik tetap, SQL Workbench DynoTable menjalankannya atas tabel live Anda.

Diperbarui

Coba desain ini secara interaktif

Sketsakan entitas dan pola akses Anda di tool DynamoDB Single-Table Design gratis — ia menyarankan template key PK/SK, mempratinjau item collection, dan menunjukkan pola mana yang membutuhkan GSI.

Buka tool Single-Table Design