DynamoDB JOIN: Cara Menggabungkan Tabel (dan Mengapa Biasanya Tidak Bisa)
Tidak ada JOIN di DynamoDB. API tidak punya operator join, model data tidak punya
foreign key, dan — bagian yang mengejutkan kebanyakan orang — PartiQL, lapisan query
berasa-SQL, juga tidak menambahkannya. Sebuah PartiQL SELECT membaca tepat satu
tabel.
Jika Anda datang dari database relasional, ini adalah dinding pertama yang Anda hantam. Panduan ini membahas mengapa dinding itu ada, empat hal yang dilakukan developer sebagai gantinya, satu kasus di mana Anda benar-benar membutuhkan join sungguhan — dan cara menjalankannya.
Bisakah DynamoDB melakukan join?
Tidak. DynamoDB tidak dapat menggabungkan tabel — tidak melalui API tingkat-rendah (GetItem / Query / Scan / BatchGetItem), tidak melalui PartiQL, dan tidak melalui query planner bawaan apa pun, karena tidak ada query planner. Setiap baca memetakan ke satu tabel atau salah satu indeksnya; menggabungkan dua tabel pada key yang cocok adalah sesuatu yang Anda lakukan di aplikasi Anda setelah DynamoDB mengembalikan item, tidak pernah di dalamnya.
- DynamoDB tidak punya operator
JOIN. Tidak pernah punya. SELECTPartiQL adalah tabel-tunggal saja — grammar-nya secara harfiahSELECT … FROM {{table}}[.{{index}}], dan mengarahkannya ke dua tabel mengembalikanValidationException: Only Select from a Single Table or index supported.- Perbaikan yang direkomendasikan AWS adalah tidak membutuhkan join: denormalisasi, atau gunakan single-table design agar item terkait hidup di satu partisi yang Anda ambil dalam satu permintaan.
- Untuk kasus lintas-tabel / ad-hoc yang sejati, Anda join di luar DynamoDB — di aplikasi Anda, atau dengan alat yang melakukannya untuk Anda.
Mengapa DynamoDB tidak punya join
Sebuah SQL JOIN meminta database membaca beberapa tabel dan merakitnya pada waktu
query. Panduan AWS sendiri untuk memodelkan data relasional
menjabarkan biayanya: sebuah query seperti
SELECT * FROM Orders
INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
ORDER BY Quantity_on_Hand DESCfleksibel, tetapi "setiap join dalam query meningkatkan kompleksitas runtime query karena data untuk setiap tabel harus disusun lalu dirakit." Pekerjaan itu tak terbatas — biayanya tergantung pada data, bukan pada query — yang justru merupakan properti yang ditolak DynamoDB.
Jadi AWS merancang batasannya. DynamoDB, dalam kata-kata mereka, "dibangun untuk
meminimalkan kedua batasan [CPU dan jaringan] dengan menghilangkan JOIN (dan
mendorong denormalisasi data) serta mengoptimalkan arsitektur database untuk sepenuhnya
menjawab query aplikasi dengan satu permintaan ke sebuah item." Itulah kualitas yang
membeli latensi satu-digit-milidetik pada skala apa pun: biaya runtime baca DynamoDB
konstan terlepas dari ukuran tabel. Tidak ada engine join dan tidak ada konsep
foreign-key untuk direncanakan — secara desain.
"Tapi PartiQL itu SQL, pasti bisa join?"
Tidak. PartiQL memberi Anda sintaksis SELECT / INSERT / UPDATE / DELETE atas
DynamoDB, tetapi ia kompatibel-SQL, bukan SQL.
Grammar SELECT resmi
adalah:
SELECT {{expression}} [, ...]
FROM {{table}}[.{{index}}]
[ WHERE {{condition}} ]
[ ORDER BY {{key}} [DESC|ASC], ... ]FROM mengambil satu tabel (opsional salah satu indeksnya). Tidak ada tabel FROM
kedua, tidak ada JOIN, tidak ada subquery, tidak ada CTE. Arahkan PartiQL ke dua tabel
dan DynamoDB menolaknya
(dilaporkan di AWS re:Post):
ValidationException: Only Select from a Single Table or index supportedJika Anda ingin alasan lengkap mengapa PartiQL terlihat seperti SQL tetapi tidak dapat berperilaku seperti itu, lihat PartiQL vs SQL.
4 workaround yang sebenarnya dipakai developer
1. Denormalisasi (salin datanya masuk)
Simpan field yang seharusnya Anda join langsung ke item. Sebuah Order membawa snapshot
dari customerName dan shippingAddress alih-alih customerId yang akan Anda resolusi
nanti. Satu baca, tanpa join.
Biayanya adalah fan-out waktu-tulis: saat sumber berubah Anda memperbarui setiap salinan (biasanya via handler DynamoDB Streams). Anda menukar kompleksitas baca dengan kompleksitas tulis — biasanya pertukaran bagus untuk aplikasi berat-baca.
2. Single-table design (pra-join di partisi)
Letakkan entitas terkait di satu tabel di bawah partition key bersama sehingga sebuah
koleksi item adalah hasil tergabung. Seorang pelanggan dan semua order-nya berbagi
PK = "CUSTOMER#42"; satu Query mengembalikan item pelanggan plus setiap item order —
"join" sudah terjadi pada waktu tulis.
Query PK = "CUSTOMER#42"
→ CUSTOMER#42 / PROFILE (pelanggannya)
→ CUSTOMER#42 / ORDER#1001 (sebuah order)
→ CUSTOMER#42 / ORDER#1002 (sebuah order)
Ini jawaban DynamoDB kanonik untuk relasi satu-ke-banyak. Panduan lengkap di single-table design.
3. Join sisi-aplikasi (dua baca, jahit di kode)
Baca dari tabel A, ambil key yang Anda dapat kembali, baca dari tabel B, dan gabungkan kedua set hasil di aplikasi Anda. Ini logika join relasional — hanya berjalan di kode Anda alih-alih database:
// "Ambil setiap order dengan nama pelanggannya" — join manual.
const {Items: orders} = await ddb.query({TableName: 'Orders' /* … */});
const customers = await Promise.all(
orders.map((o) => ddb.getItem({TableName: 'Customers', Key: {id: o.customerId}}))
);
const joined = orders.map((o, i) => ({
...o,
customerName: customers[i].Item?.name
}));Cukup untuk fan-out kecil. Dengan banyak order ia menjadi masalah N+1 — satu baca
untuk mendaftar order, lalu satu baca per order — yang lambat dan membakar kapasitas
baca. BatchGetItem (berikutnya) menyatukan gelombang kedua itu menjadi satu
round-trip.
4. BatchGetItem (satu round-trip, banyak tabel)
BatchGetItem
adalah yang terdekat dari API ke "menyentuh dua tabel sekaligus": satu permintaan
mengembalikan "atribut dari satu atau lebih item dari satu atau lebih tabel," hingga
100 item atau 16 MB per panggilan, mana pun yang lebih dulu tercapai. Ia memangkas
round-trip dari join sisi-aplikasi — tetapi ia bukan join. Anda "mengidentifikasi
item yang diminta berdasarkan primary key"; tidak ada kondisi ON dan tidak ada
pencocokan relasional. Anda tetap harus tahu key di muka dan menjahit respons sendiri.
Kapan JOIN sungguhan tak terhindarkan
Keempat workaround mencakup jalur baca produksi dengan baik. Di mana mereka gagal adalah query ad-hoc, eksploratif, analitis — yang tidak Anda modelkan:
- "Pelanggan mana di EU yang menempatkan order di atas $500 bulan lalu?" lintas tabel
Ordersdan tabelCustomers. - Pemeriksaan kualitas-data sekali-pakai yang menggabungkan dua tipe entitas.
- Reporting dan agregat (
GROUP BY,SUM,COUNT) — yang DynamoDB sama sekali tidak punya operatornya.
Ini justru query yang tidak dapat Anda pra-panggang ke dalam partisi, karena menurut
definisi Anda tidak tahu Anda akan menanyakannya. Insting relasional — menulis sebuah
JOIN — adalah yang tepat di sini. DynamoDB hanya tidak dapat melayaninya secara
native, dan PartiQL juga tidak.
Jawaban berat yang biasa adalah mengekspor ke S3 dan query dengan Athena, atau menyalurkan ke warehouse. Itu benar untuk analitik sejati pada skala, tetapi itu banyak pemipaan untuk pertanyaan yang ingin Anda jawab sekarang, terhadap tabel live Anda.
Menjalankan JOIN sungguhan dengan SQL Workbench DynoTable
DynoTable adalah klien DynamoDB desktop yang SQL Workbench-nya
menjalankan SQL aktual — termasuk JOIN, GROUP BY, dan fungsi agregat — atas tabel
DynamoDB Anda. Ia membaca item melalui API DynamoDB normal, lalu mengeksekusi bagian
relasional dari query di klien. Jadi Anda dapat menulis:
SELECT c.name, SUM(o.total) AS spend
FROM Customers c
JOIN Orders o ON o.customerId = c.id
WHERE c.region = 'EU'
GROUP BY c.name
HAVING SUM(o.total) > 500— dan mendapatkan set hasil, terhadap tabel yang tidak punya relasi terdefinisi dan
engine query yang tidak punya keyword JOIN.
Caveat jujur — "dalam aturan pola-akses DynamoDB": Workbench tetap membaca melalui
DynamoDB, jadi join tak-terbatas adalah baca tak-terbatas. Query tercepat adalah yang di
mana klausa WHERE (atau atribut ON dari join) menghantam partition key atau sebuah
GSI di setidaknya satu sisi, sehingga DynamoDB menjalankan Query
alih-alih scan tabel penuh sebelum join dieksekusi. Workbench
tidak mencabut batasan dalam panduan ini — ia hanya membuat Anda dapat menanyakan
pertanyaan SQL alih-alih menulis jahitan dengan tangan sendiri, dan memberi tahu Anda
apa yang dilakukannya di bawah.
Ini satu-satunya "ya, Anda bisa join" yang benar-benar nyata: PartiQL dan
NoSQL Workbench
AWS sendiri — yang operation builder-nya terbatas pada operasi data-plane tabel-tunggal
(Query / Scan / GetItem) — keduanya berhenti di dinding tabel-tunggal, seperti
juga kebanyakan klien GUI lain. Lihat bagaimana DynoTable dibandingkan sebagai
GUI DynamoDB.
FAQ
Apakah PartiQL mendukung JOIN?
Tidak. SELECT PartiQL membaca satu tabel (atau salah satu indeksnya). Query
multi-tabel mengembalikan ValidationException: Only Select from a Single Table or index supported. Dinding yang sama dengan sisa API.
Bisakah Anda menggabungkan dua tabel DynamoDB dalam satu query?
Tidak secara native. API DynamoDB tidak punya pernyataan yang membaca dua tabel dan
mencocokkannya pada sebuah key. BatchGetItem dapat membaca item dari banyak tabel
dalam satu permintaan, tetapi tidak punya kondisi ON — ia mengembalikan item yang
Anda sebut berdasarkan primary key dan menyerahkan pencocokan kepada Anda. Sebuah
JOIN … ON … sungguhan hanya terjadi di luar DynamoDB: di aplikasi Anda, atau di SQL
Workbench DynoTable.
Bisakah Anda menggabungkan tabel ke GSI-nya?
Tidak — sebuah Global Secondary Index bukan tabel terpisah yang
Anda gabungkan; ia tampilan key alternatif dari item yang sama. Anda Query entah
tabelnya atau indeksnya dalam sebuah SELECT, bukan keduanya digabung. Sebuah GSI
membuat Anda dapat menjangkau item dengan key berbeda, yang sering menghilangkan
kebutuhan join sejak awal.
Bisakah Anda menggabungkan lintas dua akun AWS (atau dua tabel di akun berbeda)?
Tidak secara native, dan tidak dengan BatchGetItem juga — satu permintaan tidak dapat
menjangkau kredensial, dan tidak ada primitif join lintas-akun. Anda akan membaca setiap
tabel dengan kredensial akunnya sendiri dan menggabungkan hasilnya di aplikasi Anda atau
di alat seperti Workbench DynoTable.
Apakah denormalisasi benar-benar lebih baik daripada join? Untuk beban kerja target DynamoDB — baca yang dapat diprediksi, bervolume tinggi — ya. Anda memindahkan biaya ke waktu tulis (dan menerima beberapa duplikasi data) sebagai ganti baca satu-permintaan yang berskala datar. Panduan single-table design membahas trade-off-nya.
Membangun key dan kondisi untuk baca ini dengan tangan itu merepotkan —
expression builder menghasilkan sintaksis
KeyConditionExpression / FilterExpression untuk Anda, dan
DynoTable menjalankan SQL sungguhan saat workaround tidak cukup.