Relasi One-to-Many di DynamoDB
Control plane SaaS hampir selalu punya hierarki berbasis kepemilikan: satu
workspace memiliki banyak proyek. Di SQL Anda akan menaruh foreign key
workspace_id di tabel proyek lalu melakukan JOIN.
DynamoDB tidak punya join dan tidak punya foreign key, jadi relasinya harus hidup di
dalam key schema itu sendiri. Dilakukan dengan benar, "muat sebuah workspace dan
setiap proyek di dalamnya" menjadi satu Query saja, bukan satu pembacaan plus scan
lanjutan.
Bagaimana cara memodelkan relasi one-to-many di DynamoDB?
Berikan induk dan semua anaknya yang sama agar mereka berbagi satu , lalu bedakan mereka dengan sort key. DynamoDB tidak punya join atau foreign key, jadi relasinya hidup di dalam key schema itu sendiri. Memuat sebuah induk plus setiap anaknya kemudian menjadi satu Query saja, bukan sebuah join.
- Modelkan pembacaannya, bukan entitasnya. Relasi one-to-many hanya ada untuk melayani "daftarkan proyek-proyek sebuah workspace" — bentuk key-nya di sekitar query itu.
- Encode induk ke dalam anaknya. Berikan workspace dan semua proyeknya nilai partition-key yang sama agar mereka jatuh di satu .
- Lalu pembacaan daftar itu adalah satu
Query. Induk plus anak-anaknya kembali bersama-sama — tanpa join, tanpa round trip kedua (sebuahQuerymengembalikan hingga 1 MB per halaman, memaginasi lewatLastEvaluatedKeydi luar itu). - Waspadai . Satu tenant raksasa memusatkan semua trafiknya di satu partisi; sebuah workspace besar mungkin butuh key ter-shard dan pembacaan fan-out.
Pola akses, lebih dulu
Pemodelan DynamoDB adalah access-pattern-first, bukan entity-first — disiplin yang sama di balik single-table design. Sebelum memilih key apa pun, tuliskan pembacaan yang benar-benar dikeluarkan aplikasi:
- Ambil setelan satu workspace.
- Daftarkan setiap proyek di sebuah workspace, terbaru dulu.
- Ambil satu proyek spesifik berdasarkan id.
Relasi "satu workspace, banyak proyek" hanya penting karena pembacaan #2. Jika Anda tak pernah perlu mendaftarkan proyek-proyek sebuah workspace bersama-sama, Anda tak akan memodelkan relasinya sama sekali — Anda akan menyimpan proyek secara independen.
Jadi pertanyaannya tak pernah "bagaimana saya merepresentasikan one-to-many?" secara abstrak. Melainkan "query mana yang harus dilayani relasi ini?" Jawab itu, lalu bentuk key-nya di sekitarnya.
Kenapa foreign key tak akan membantu di sini
Di DynamoDB setiap GetItem dan Query menargetkan sebuah partition key, dan
layanan itu meng-hash key tersebut untuk menemukan partisi yang menampung Item-nya.
AWS menyatakannya langsung di dokumen Core Components: nilai partition-key adalah input untuk fungsi hash internal yang memutuskan di mana data berada.
Penempatan berbasis-hash itu adalah warisan dari makalah Dynamo: Amazon's Highly Available Key-value Store tahun 2007 yang asli, tempat consistent hashing mendistribusikan key ke seluruh node.
Sebuah atribut workspace_id telanjang pada Item proyek tak terlihat oleh mesin
itu — DynamoDB tak bisa "mengikutinya".
Untuk mengambil Item terkait dalam satu request, identitas induk harus di-encode ke
dalam partition key proyek, sehingga semua Item sebuah workspace ter-hash ke
partisi yang sama dan satu Query bisa menyapunya.
Contoh terpandu: workspace dan proyek
Gunakan key schema generik yang di-overload. Sebut partition key EntityRef dan sort
key Detail. Identitas workspace masuk ke EntityRef untuk baik Item workspace
maupun setiap proyek di bawahnya:
| 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 |
Workspace dan semua proyeknya berbagi EntityRef = "WS#acme", jadi mereka membentuk
satu koleksi Item yang hidup bersama di satu partisi.
Sort key Detail memisahkan mereka: META adalah rekaman workspace, dan setiap
proyek membawa prefiks PROJ# dengan id yang time-ordered dan zero-padded sehingga
proyek terurut secara alami.
Secara visual, induk dan anak-anaknya bertumpuk di dalam satu partisi, terurut berdasarkan sort key:
Satu Query pada EntityRef = "WS#acme" menyapu seluruh tumpukan — induk plus setiap
anak — dalam satu pembacaan.
Kini ketiga pola akses masing-masing runtuh menjadi satu panggilan:
- Setelan workspace —
GetItem(EntityRef="WS#acme", Detail="META"). - Daftarkan proyek terbaru-dulu —
Query(EntityRef="WS#acme")denganDetail begins_with "PROJ#", dijalankan dalam urutan menurun (ScanIndexForward = false). - Satu proyek —
GetItem(EntityRef="WS#acme", Detail="PROJ#2026-0042").
Yang kedua adalah inti keseluruhannya: induk dan anak-anaknya kembali dari satu
Query, tanpa join dan tanpa round trip kedua — DynamoDB mengembalikan hingga 1 MB
per halaman dan memberi Anda LastEvaluatedKey untuk mengambil sisanya. Itulah gerakan
yang tak bisa Anda lakukan dengan atribut foreign-key dan sebuah Scan.
Menulis kondisi begins_with itu dengan tangan merepotkan — sintaks key-condition
dan projection-expression menggigit.
DynamoDB Expression Builder menghasilkan
KeyConditionExpression, peta placeholder #name/:value, dan cuplikan SDK yang siap
jalan agar Anda tak berkelahi dengan tata bahasanya:
KeyConditionExpression "#er = :er AND begins_with(#d, :p)"
ExpressionAttributeNames { "#er": "EntityRef", "#d": "Detail" }
ExpressionAttributeValues { ":er": "WS#acme", ":p": "PROJ#" }
Inspeksi koleksi Item di DynoTable
Hasil tata letak ini bersifat visual: setiap baris yang berbagi sebuah EntityRef
adalah workspace plus anak-anaknya, duduk bersebelahan.
DynoTable mengelompokkan mereka sehingga Anda melihat relasi one-to-many sebagai satu blok yang bersambung alih-alih menebak-nebaknya di seberang tabel-tabel terpisah.

Jebakan dan bentuk alternatifnya
Beberapa hal yang perlu diwaspadai:
- Hot partition. Setiap Item untuk satu workspace hidup di satu partisi, jadi satu
tenant yang sangat besar atau sangat sibuk memusatkan trafik. Perilaku
adaptive capacity
yang dijelaskan AWS menyerap kemiringan sedang, tetapi sebuah workspace dengan
jutaan proyek mungkin butuh key ter-shard (mis.
WS#acme#01 … #10) dan pembacaan fan-out. - Ukuran koleksi Item. Dengan sebuah local secondary index, koleksi Item satu partisi dibatasi hingga 10 GB; tanpa LSI tak ada batas seperti itu. Jika Anda sedang menimbang tipe indeks di sini, lihat GSI vs LSI.
- Raih
Query, jangan pernahScan. Seluruh desain ini ada agar Anda bisaQuerysatu partisi. Jatuh kembali keScanberfilter untuk "menemukan proyek-proyek sebuah workspace" membuang modelnya dan membaca seluruh tabel — jebakan yang dibahas di Query vs Scan.
Jika Anda benar-benar perlu mendaftarkan proyek lintas workspace (misalnya, semua
proyek status = ACTIVE secara global), tabel dasar tak bisa menjawab itu — partition
key-nya ter-scope ke workspace.
Itu tugas untuk sebuah secondary index yang me-repartisi proyek pada atribut berbeda, bukan untuk membentuk ulang relasi ini.
Langkah selanjutnya
Modelkan pola aksesnya, encode induk ke dalam partition key anaknya, dan pembacaan
one-to-many menjadi satu Query. Bangun dan validasi kondisi key dengan
DynamoDB Expression Builder — dan jika Anda
lebih suka memulai dari pola aksesnya sendiri, alat Single-Table
Design gratis menyusun draf rencana
PK/SK/GSI dengan contoh item.
Lalu unduh DynoTable untuk memuat skema ini, menjelajahi koleksi Item
workspace→proyek secara langsung, dan memastikan setiap query melakukan tepat satu
pembacaan. Jika Anda lebih suka melihat workspace dan proyek sebagai tampilan
relasional yang tergabung, SQL Workbench DynoTable
menjalankan JOIN itu juga.


