Shard key bisa dipakai apa adanya nilainya, atau lewat hash-nya. Dua strategi itu — ranged dan hashed — menghasilkan distribusi fisik data yang sangat berbeda: satu menjaga kedekatan nilai, satu lagi mengacaknya demi kemerataan. Keduanya sah, dan kabar baiknya: mekanisme hashing-nya bisa kita selami langsung di replica set biasa lewat hashed index dan fungsi convertShardKeyToHashed(), tanpa sharded cluster sungguhan.
Ranged: Menjaga Kedekatan Nilai #
Ranged sharding membagi data ke dalam chunk berupa rentang nilai shard key yang berurutan — document dengan nilai berdekatan cenderung berada di chunk, bahkan shard, yang sama. Inilah strategi bawaan saat kita mendeklarasikan { field: 1 }.
Kekuatan ranged terlihat pada range query: query find({ userId: { $gte: 100, $lt: 200 } }) mengetahui rentangnya, sehingga mongos hanya mengirimnya ke shard-shard pemilik rentang terkait — bukan ke seluruh cluster. Kelemahannya muncul di sisi tulis: bila nilai shard key selalu bertambah (nomor faktur, timestamp), seluruh insert menumpuk pada satu chunk di ujung rentang, dan shard pemiliknya menjadi sibuk sementara yang lain menganggur.
Hashed: Menyebarkan lewat Hash #
Hashed sharding memakai hash 64-bit dari nilai shard key sebagai dasar pembagian. Distribusinya mendekati acak dan cenderung merata — nilai yang berurutan tidak lagi bertetangga. Karena itulah hashed adalah obat klasik untuk shard key monotonic seperti ObjectId atau timestamp.
Konsekuensinya simetris dengan ranged: query equality ({ userId: 42 }) tetap bisa diarahkan ke satu shard karena hash nilai bisa dihitung, tetapi range query kehilangan makna — rentang nilai asli tidak lagi berpeta ke rentang hash, sehingga query berbentuk $gte/$lt harus disiarkan ke semua shard (scatter-gather).
Mesin hash-nya sendiri bisa kita lihat. mongosh menyediakan convertShardKeyToHashed() yang memakai fungsi hashing yang sama dengan hashed index:
convertShardKeyToHashed(1)
convertShardKeyToHashed(2)Output yang Dihasilkan:
Long('5902408780260971510')
Long('848411777775835583')Dua nilai yang berurutan menghasilkan hash yang sama sekali tidak berurutan — inilah sumber kemerataan distribusi hashed.
Ada satu batasan yang jarang disadari: hashed index memotong angka pecahan menjadi bilangan bulat 64-bit sebelum di-hash. Kita buktikan sendiri:
convertShardKeyToHashed(2)
convertShardKeyToHashed(2.3)
convertShardKeyToHashed(2.2)
convertShardKeyToHashed(2.9)Output yang Dihasilkan:
Long('848411777775835583')
Long('848411777775835583')
Long('848411777775835583')
Long('848411777775835583')Keempat nilai menghasilkan hash identik karena 2.3, 2.2, dan 2.9 dipotong menjadi 2 sebelum di-hash. Dokumentasi resmi karenanya melarang hashed index untuk angka pecahan yang tidak dapat dikonversi andal ke bilangan bulat 64-bit, dan mendukung nilai pecahan maksimal 2 pangkat 53. Untuk field userId bertipe bilangan bulat seperti contoh kita, batasan ini tidak berlaku.
Hash dalam Aksi di Server Latihan #
Kita bangun collection kecil ber-index hashed, lalu periksa bagaimana query equality memanfaatkannya:
db.pengguna.insertMany([
{ userId: 1, nama: "Budi" },
{ userId: 2, nama: "Siti" },
{ userId: 3, nama: "Dewi" }
])
db.pengguna.createIndex({ userId: "hashed" })
db.pengguna.find({ userId: 2 }).explain().queryPlanner.winningPlanOutput yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3d8e239368a0b6748dab3'),
'1': ObjectId('6ad3d8e239368a0b6748dab4'),
'2': ObjectId('6ad3d8e239368a0b6748dab5')
}
}
userId_hashed
{
isCached: false,
stage: 'FETCH',
filter: { userId: { '$eq': 2 } },
nss: 'mdtest_mongodb_sharding_ranged_vs_hashed_sharding.pengguna',
inputStage: {
stage: 'IXSCAN',
nss: 'mdtest_mongodb_sharding_ranged_vs_hashed_sharding.pengguna',
keyPattern: { userId: 'hashed' },
indexName: 'userId_hashed',
isMultiKey: false,
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: { userId: [ '[848411777775835583, 848411777775835583]' ] }
}
}Perhatikan indexBounds: query userId: 2 diterjemahkan menjadi pencarian hash 848411777775835583 — persis nilai convertShardKeyToHashed(2) di atas. MongoDB menghitung hash secara otomatis; aplikasi tidak pernah menghitung sendiri. Pada sharded cluster sungguhan, mongos memakai logika yang sama untuk mengarahkan query equality ke satu shard.
Sekarang kebalikannya — range query pada index hashed:
db.pengguna.find({ userId: { $gte: 2 } }).explain().queryPlanner.winningPlanOutput yang Dihasilkan:
{
isCached: false,
stage: 'COLLSCAN',
filter: { userId: { '$gte': 2 } },
nss: 'mdtest_mongodb_sharding_ranged_vs_hashed_sharding.pengguna',
direction: 'forward'
}Index hashed yang sama, query yang berbeda nasib: padahal collection punya index userId_hashed, query $gte dijawab dengan COLLSCAN — collection scan penuh. Index hashed memang tidak bisa melayani rentang nilai asli, karena urutan nilai tidak lagi berpeta ke urutan hash. Di sharded cluster, pola yang sama memaksa mongos menyiarkan query ke semua shard.
Perbandingan Ringkas #
| Aspek | Ranged | Hashed |
|---|---|---|
| Distribusi data | Mengikuti urutan nilai | Mendekati acak, cenderung merata |
| Query equality pada shard key | Targeted ke satu shard | Targeted ke satu shard |
| Range query pada shard key | Targeted ke shard pemilik rentang | Scatter-gather ke semua shard |
| Insert dengan key monotonic | Hotspot pada satu shard | Tersebar aman |
| Constraint unique pada shard key | Bisa | Tidak bisa |
| Batasan tipe data | Umumnya fleksibel | Angka pecahan dipotong ke 64-bit integer |
Contoh berikut memperlihatkan kontras keduanya pada data yang sama (ilustrasi, bukan hasil eksekusi):
Ranged pada noFaktur yang naik terus:
Shard 1: [1, 30000) -> noFaktur 1, 2, 3
Shard 2: [30000, MaxKey) -> noFaktur 100001, 100002
Insert berikutnya selalu ke Shard 2 (hotspot)
Hashed pada noFaktur yang sama:
Shard 1: hash(noFaktur 1), hash(noFaktur 3), hash(noFaktur 100002)
Shard 2: hash(noFaktur 2), hash(noFaktur 100001)
Insert tersebar; urutan nilai tidak lagi berpeta ke lokasiRangkuman #
- Ranged menjaga kedekatan nilai: range query efisien, tetapi shard key monotonic memicu hotspot tulis.
- Hashed menyebarkan data lewat hash 64-bit: insert merata untuk key monotonic, tetapi range query menjadi scatter-gather.
- MongoDB menghitung hash secara otomatis — terbukti dari
indexBoundsexplain yang memuat nilai hash, bukan nilai asli. - Angka pecahan dipotong ke bilangan bulat 64-bit sebelum di-hash (
2.3dan2.9menghasilkan hash sama) — hindari hashed untuk pecahan.
Kedua strategi baru sebagian cerita: kualitas shard key sendiri — cardinality, frequency, dan monotonicity — adalah bahan Chapter berikutnya.