Aplikasi tidak perlu tahu datanya tersebar — mongos merangkum semuanya sebagai satu collection biasa. Tetapi di balik kemudahan itu, kualitas sebuah query di sharded cluster ditentukan satu hal: apakah filternya menyertakan shard key. Query yang menyertakannya meluncur ke shard yang tepat; query yang tidak, disiarkan ke semua shard dan menunggu yang terlama. Chapter ini membedah kedua jalur itu, perilaku sort/limit/skip, dan cara membacanya lewat explain.
Targeted vs Scatter-Gather #
Targeted query menyertakan shard key (atau prefiksnya bila compound) di filter. mongos menghitung chunk mana yang memuat rentang nilai tersebut — lewat cache metadata-nya — lalu mengirim query hanya ke shard pemilik chunk. Scatter-gather query tidak menyertakan shard key, sehingga mongos terpaksa menyiarkannya ke semua shard, menunggu seluruh respons, lalu menggabungkan hasilnya. Biaya scatter-gather tumbuh seiring jumlah shard: cluster sepuluh shard membayar sepuluh kali kerja untuk satu query.
Tabel ringkas perilakunya:
Pola query (shard key userId) | Perilaku |
|---|---|
find({ userId: 42 }) | Targeted ke satu shard |
find({ userId: { $in: [1, 5, 9] } }) | Targeted ke shard pemilik tiap nilai |
find({ userId: { $gte: 10, $lt: 20 } }) (ranged) | Targeted ke shard pemilik rentang |
find({ userId: { $gte: 10 } }) (hashed) | Scatter-gather — rentang tidak berpeta ke hash |
find({ email: "[email protected]" }) | Scatter-gather ke semua shard |
find({ _id: ObjectId(...) }) saat _id bukan shard key | Scatter-gather |
insertOne(...) | Selalu targeted ke satu shard |
updateMany/deleteMany tanpa shard key | Broadcast ke semua shard |
Aturan tegasnya menyertainya: updateOne(), replaceOne(), dan deleteOne() wajib menyertakan shard key lengkap atau _id di filternya — tanpa keduanya MongoDB menolak operasi karena tidak bisa menentukan shard mana yang dituju.
Sort, Limit, dan Skip di Tangan mongos #
mongos tidak sekadar meneruskan; ia juga merakit hasil:
sort()dieksekusi di tiap shard terlebih dahulu, lalu mongos melakukan merge sort atas hasil yang sudah terurut.limit(n)diteruskan ke setiap shard (tiap shard mengembalikan maksimal n document), lalu mongos menerapkan limit sekali lagi di hasil gabungan.skip(n)tidak bisa diteruskan ke shard — mongos mengambil hasil tanpa lompatan lalu melewati n document saat merakit. Dipadukan denganlimit, mongos meneruskanlimit + skipke shard agar tetap efisien.
Membedah Query dengan explain #
Di server latihan, kita bisa melihat fondasi logikanya: query dengan field ter-index (analogi shard key) memanfaatkan rute langsung, sedangkan query tanpanya memindai semua data (analogi semua shard):
db.pesan.insertMany([
{ userId: 1, teks: "halo" },
{ userId: 2, teks: "hai" },
{ userId: 3, teks: "hei" }
])
db.pesan.createIndex({ userId: 1 })
db.pesan.find({ userId: 2 }).explain().queryPlanner.winningPlanOutput yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3d8e0bcf662a3b9ce7172'),
'1': ObjectId('6ad3d8e0bcf662a3b9ce7173'),
'2': ObjectId('6ad3d8e0bcf662a3b9ce7174')
}
}
userId_1
{
isCached: false,
stage: 'FETCH',
nss: 'mdtest_mongodb_sharding_queries_on_sharded_collections.pesan',
inputStage: {
stage: 'IXSCAN',
nss: 'mdtest_mongodb_sharding_queries_on_sharded_collections.pesan',
keyPattern: { userId: 1 },
indexName: 'userId_1',
isMultiKey: false,
multiKeyPaths: { userId: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { userId: [ '[2, 2]' ] }
}
}Sekarang query pada field di luar index — padanannya query tanpa shard key:
db.pesan.find({ teks: "hai" }).explain().queryPlanner.winningPlanOutput yang Dihasilkan:
{
isCached: false,
stage: 'COLLSCAN',
filter: { teks: { '$eq': 'hai' } },
nss: 'mdtest_mongodb_sharding_queries_on_sharded_collections.pesan',
direction: 'forward'
}COLLSCAN pada satu shard menjadi scatter-gather pada cluster: semua shard memindai datanya masing-masing. Pada sharded cluster sungguhan, output explain menambahkan lapisan ini — mongos menampilkan tahap SHARD_MERGE serta field shards yang memuat rencana eksekusi tiap shard. Bentuk besarnya seperti ini (ilustratif, bukan hasil eksekusi):
winningPlan: {
stage: 'SHARD_MERGE',
shards: [
{ shardName: 'shardA', ...-winningPlan lokal IXSCAN... },
{ shardName: 'shardB', ...-winningPlan lokal IXSCAN... }
]
}Untuk aggregation pipeline, explain mengungkap pembagian kerja lebih jauh: field splitPipeline menampilkan stage yang dijalankan di masing-masing shard, dan mergeType menunjukkan lokasi penggabungan — router (mongos), anyShard, atau specificShard.
Rangkuman #
- Query dengan shard key (atau prefiksnya) menjadi targeted; tanpa shard key menjadi scatter-gather yang mahal seiring bertambahnya shard.
updateOne,replaceOne, dandeleteOnewajib menyertakan shard key atau_id;insertOneselalu targeted.sortdi-merge oleh mongos,limitditeruskan lalu diterapkan ulang,skipbaru diterapkan saat perakitan hasil.explainpada cluster memperlihatkanSHARD_MERGEdan rencana per shard;splitPipelinedanmergeTypeuntuk aggregation.
Perutean berdasar nilai shard key masih bisa dipertajam satu level lagi: mengikat rentang data ke shard tertentu lewat zones.