Sharding #
Sharding adalah salah satu keputusan teknis yang paling berdampak dan paling sulit dibalik dalam lifecycle sebuah sistem database. Ketika dilakukan pada waktu yang tepat dan dengan shard key yang tepat, ia memungkinkan sistem melayani miliaran baris data dan jutaan write per hari — sesuatu yang tidak mungkin dilakukan oleh satu database server secanggih apapun. Ketika dilakukan terlalu dini atau dengan shard key yang salah, ia menciptakan kompleksitas yang memperlambat tim selama bertahun-tahun, membuat debugging menjadi mimpi buruk, dan membuat setiap perubahan schema terasa seperti operasi bedah jantung.
Sharding bukan langkah pertama dalam skalabilitas database — ia adalah langkah terakhir yang diambil setelah semua teknik yang lebih sederhana sudah dioptimalkan dan tidak lagi cukup. Memahami sharding secara benar berarti memahami bukan hanya cara kerjanya, tapi juga semua trade-off yang datang bersamanya dan semua masalah yang ia ciptakan sambil menyelesaikan masalah lain.
Sharding vs Partitioning: Perbedaan yang Fundamental #
Dua istilah ini sering digunakan secara bergantian padahal merujuk pada konsep yang berbeda di level yang berbeda. Memahami perbedaannya penting sebelum memutuskan pendekatan mana yang dibutuhkan.
Partitioning — bekerja di dalam satu database instance:
┌─────────────────────────────────────────────┐
│ Database Server (satu) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │orders_ │ │orders_ │ │orders_ │ │
│ │2025_01 │ │2025_02 │ │2025_03 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Satu database, beberapa partition │
└─────────────────────────────────────────────┘
→ Satu server, satu proses database
→ CPU, RAM, disk masih berbagi
→ Index dan query planner masih satu
→ Foreign key masih bisa digunakan
Sharding — bekerja di level infrastruktur, lintas server:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Shard A │ │ Shard B │ │ Shard C │
│ (server 1) │ │ (server 2) │ │ (server 3) │
│ │ │ │ │ │
│ user_id 1–1M │ │ user_id 1M–2M│ │ user_id 2M–3M│
└───────────────┘ └───────────────┘ └───────────────┘
→ Tiga server terpisah, tiga proses database
→ CPU, RAM, disk masing-masing
→ Tidak ada foreign key lintas shard
→ Query lintas shard butuh koordinasi di application layer
Dalam praktik, kedua teknik sering dikombinasikan: data di-shard ke beberapa server, dan di dalam setiap shard, tabel besar di-partisi per waktu. Ini memberikan manfaat keduanya — distribusi beban horizontal dari sharding dan pruning query dari partitioning.
Mengapa Sharding Dibutuhkan: Batas yang Tidak Bisa Dihindari #
Setiap database server memiliki batas fisik yang tidak bisa ditembus dengan optimasi apapun. Ketika sistem sudah mendekati batas ini, sharding menjadi satu-satunya jalan keluar yang realistis.
Batas fisik yang memaksa sharding:
Batas CPU:
→ Satu server memiliki jumlah CPU yang terbatas
→ Semakin banyak concurrent query, semakin banyak CPU dibutuhkan
→ Di luar titik tertentu, menambah CPU tidak linear meningkatkan throughput
Batas write throughput:
→ Single primary database hanya bisa melayani N write per detik
→ Write yang melebihi kapasitas ini menyebabkan antrian, lock contention, timeout
→ Read replica tidak membantu write — write tetap ke primary
Batas storage:
→ Dataset 50TB di satu server: backup butuh hari, restore butuh hari
→ Index pada tabel 10 miliar baris bisa lebih besar dari RAM server
→ VACUUM/OPTIMIZE pada tabel raksasa memblokir operasi lain
Batas operasional:
→ ALTER TABLE pada 10 miliar baris: bisa memakan waktu jam atau hari
→ Schema migration menjadi operasi yang sangat berisiko
Semua batas ini tidak bisa diatasi dengan vertical scaling setelah titik tertentu —
server terbesar yang tersedia di cloud masih memiliki ceiling-nya sendiri.
Sharding menyelesaikan masalah ini dengan mendistribusikan data ke beberapa server independen. Setiap shard memiliki resource sendiri, sehingga beban terdistribusi dan tidak ada satu titik pun yang menjadi bottleneck.
Tiga Strategi Sharding #
Range-Based Sharding #
Range-based sharding membagi data berdasarkan rentang nilai shard key. Ini adalah strategi yang paling mudah dipahami dan paling mudah diimplementasi.
Range-based sharding berdasarkan user_id:
Shard 1 │ Shard 2 │ Shard 3 │ Shard 4
───────────┼─────────────┼─────────────┼───────────
id 1–25M │ id 25M–50M │ id 50M–75M │ id 75M–100M
Routing logic di application:
def get_shard(user_id):
if user_id <= 25_000_000: return shard_1
elif user_id <= 50_000_000: return shard_2
elif user_id <= 75_000_000: return shard_3
else: return shard_4
# Implementasi shard router sederhana untuk range-based sharding
class RangeShardRouter:
def __init__(self):
self.shard_ranges = [
(0, 25_000_000, "shard_1"),
(25_000_000, 50_000_000, "shard_2"),
(50_000_000, 75_000_000, "shard_3"),
(75_000_000, float('inf'), "shard_4"),
]
def get_shard(self, user_id: int) -> str:
for start, end, shard_name in self.shard_ranges:
if start <= user_id < end:
return shard_name
raise ValueError(f"No shard found for user_id: {user_id}")
def get_connection(self, user_id: int):
shard_name = self.get_shard(user_id)
return self.connections[shard_name]
router = RangeShardRouter()
# Query otomatis diarahkan ke shard yang tepat
def get_user_orders(user_id: int):
conn = router.get_connection(user_id)
return conn.query(
"SELECT * FROM orders WHERE user_id = ?", user_id
)
Masalah utama range-based sharding: hot shard. Data baru hampir selalu memiliki ID yang besar — artinya shard terakhir selalu menerima lebih banyak write dari shard yang lebih awal. Ini menciptakan ketidakseimbangan beban yang semakin parah seiring waktu.
Hot shard problem pada range-based sharding:
Waktu T1: Waktu T2 (6 bulan kemudian):
Shard 1 [████████] Shard 1 [████████] ← tidak berubah, data lama
Shard 2 [██████] Shard 2 [██████] ← hampir tidak berubah
Shard 3 [████] Shard 3 [████]
Shard 4 [██] Shard 4 [████████████████] ← HOT! semua user baru masuk sini
Shard 4 menerima 90% write dan 60% read → beban tidak merata
Solusi: resharding secara berkala, atau pindah ke hash-based sharding
Hash-Based Sharding #
Hash-based sharding menentukan shard berdasarkan nilai hash dari shard key. Distribusinya jauh lebih merata dibanding range-based, karena nilai hash terdistribusi secara acak.
# Hash-based shard router
import hashlib
class HashShardRouter:
def __init__(self, num_shards: int):
self.num_shards = num_shards
self.connections = {
f"shard_{i}": create_connection(f"shard-{i}.db.internal")
for i in range(num_shards)
}
def get_shard_index(self, shard_key) -> int:
# Hash yang konsisten dan deterministic
key_bytes = str(shard_key).encode('utf-8')
hash_value = int(hashlib.md5(key_bytes).hexdigest(), 16)
return hash_value % self.num_shards
def get_connection(self, shard_key):
index = self.get_shard_index(shard_key)
return self.connections[f"shard_{index}"]
router = HashShardRouter(num_shards=8)
def get_user(user_id: int):
conn = router.get_connection(user_id)
return conn.query("SELECT * FROM users WHERE id = ?", user_id)
Distribusi hash-based sharding (8 shard):
user_id=1 → hash → shard_3 [████████]
user_id=2 → hash → shard_7 [████████]
user_id=3 → hash → shard_1 [████████]
user_id=4 → hash → shard_5 [████████]
...semua shard mendapat beban yang kurang lebih sama
Tidak ada hot shard — distribusi merata terlepas dari pola insert
Kelemahan besar hash-based sharding: resharding sangat mahal. Ketika kamu menambah shard baru dari 8 menjadi 16, hampir semua data harus dipindahkan karena hash(key) % 8 dan hash(key) % 16 memberikan hasil yang berbeda untuk hampir semua key.
Solusi untuk masalah ini adalah consistent hashing — teknik yang meminimalkan jumlah data yang harus dipindahkan saat shard ditambah atau dihapus.
Consistent hashing — resharding yang lebih efisien:
Ring hash (0–100):
Shard A: rentang 0–24
Shard B: rentang 25–49
Shard C: rentang 50–74
Shard D: rentang 75–100
Menambah shard E di rentang 50–62:
Shard C: rentang 63–74 (diperkecil, sebagian data pindah ke E)
Shard E: rentang 50–62 (baru, mengambil sebagian data dari C)
Shard A, B, D: tidak berubah
Hanya ~1/N data yang harus dipindahkan (N = jumlah shard)
dibanding simple hash yang harus memindahkan hampir semua data.
Directory-Based Sharding #
Directory-based sharding menggunakan tabel lookup (shard directory) yang menyimpan mapping antara shard key dan shard yang menyimpan data tersebut. Ini adalah pendekatan paling fleksibel — kamu bisa memindahkan data antar shard kapan saja hanya dengan mengupdate entry di directory.
# Directory-based shard router
class DirectoryShardRouter:
def __init__(self, directory_db_connection):
self.directory_db = directory_db_connection
self.local_cache = {} # cache lokal untuk menghindari lookup setiap request
def get_shard(self, tenant_id: int) -> str:
# Cek cache lokal dulu
if tenant_id in self.local_cache:
return self.local_cache[tenant_id]
# Lookup ke shard directory
result = self.directory_db.query(
"SELECT shard_name FROM shard_directory WHERE tenant_id = ?",
tenant_id
)
if not result:
raise ValueError(f"No shard found for tenant_id: {tenant_id}")
shard_name = result[0]['shard_name']
self.local_cache[tenant_id] = shard_name # simpan ke cache
return shard_name
def assign_shard(self, tenant_id: int, shard_name: str):
"""Assign tenant ke shard tertentu — biasanya saat onboarding tenant baru"""
self.directory_db.execute(
"INSERT INTO shard_directory (tenant_id, shard_name) VALUES (?, ?)",
tenant_id, shard_name
)
self.local_cache[tenant_id] = shard_name
def migrate_tenant(self, tenant_id: int, new_shard: str):
"""Pindahkan tenant dari shard lama ke shard baru tanpa downtime"""
# 1. Copy data ke shard baru (background process)
# 2. Update directory (atomic)
# 3. Invalidate cache
self.directory_db.execute(
"UPDATE shard_directory SET shard_name = ? WHERE tenant_id = ?",
new_shard, tenant_id
)
self.local_cache.pop(tenant_id, None)
Tabel shard_directory:
tenant_id │ shard_name │ migrated_at
──────────┼─────────────┼─────────────
101 │ shard_1 │ 2024-01-15
102 │ shard_1 │ 2024-01-16
103 │ shard_2 │ 2024-02-01 ← tenant besar dipindah ke shard tersendiri
104 │ shard_1 │ 2024-02-03
105 │ shard_3 │ 2024-03-10 ← tenant enterprise dapat shard dedicated
Directory-based sharding memberikan fleksibilitas tertinggi: kamu bisa memindahkan satu tenant dari shard yang penuh ke shard baru hanya dengan mengupdate satu baris di directory, tanpa mengubah shard key atau merombak arsitektur. Ini sangat berguna untuk multi-tenant SaaS di mana beban per tenant sangat bervariasi.
Kelemahannya: shard directory adalah single point of failure baru. Jika directory down, seluruh sistem tidak bisa menentukan shard mana yang harus diakses. Pastikan directory menggunakan replikasi dan caching agresif.
Masalah Nyata yang Dibawa Sharding #
Sharding menyelesaikan masalah skalabilitas, tapi ia menciptakan masalah baru yang tidak ada di sistem single-database. Memahami masalah-masalah ini sebelum mengimplementasi sharding adalah hal yang kritis.
Cross-Shard Query: Operasi yang Paling Mahal #
Query yang perlu mengambil data dari lebih dari satu shard adalah operasi yang sangat mahal di sistem sharded. Tidak ada yang namanya JOIN lintas shard di level database — semuanya harus dilakukan di application layer, yang berarti multiple round-trip ke database dan agregasi di memory.
# Cross-shard query — sangat mahal dan sulit di-scale
def get_all_users_with_orders(date_range):
results = []
# Harus query semua shard satu per satu (atau parallel)
for shard in [shard_1, shard_2, shard_3, shard_4]:
partial_results = shard.query("""
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.created_at BETWEEN ? AND ?
GROUP BY u.id
""", date_range.start, date_range.end)
results.extend(partial_results)
# Gabungkan dan sort di application layer
return sorted(results, key=lambda x: x['order_count'], reverse=True)
# Masalah:
# → Latency = max(latency semua shard) — tidak bisa lebih cepat dari shard paling lambat
# → Memory: semua hasil dari semua shard harus masuk ke memory aplikasi
# → Tidak bisa limit di database level — harus ambil semua lalu filter di app
# → Sulit di-paginate dengan benar
Implikasi paling penting dari masalah ini: shard key harus dipilih berdasarkan pola query yang paling sering. Jika 95% query bisa diselesaikan dalam satu shard, sistem masih sangat efisien. Jika 30% query perlu lintas shard, sistem akan sangat lambat dan sulit di-scale.
Distributed Transaction: Konsistensi yang Hilang #
Di single database, transaction menjamin atomicity — semua atau tidak sama sekali. Di sistem sharded, transaction yang melibatkan lebih dari satu shard membutuhkan distributed transaction, yang jauh lebih kompleks.
Skenario distributed transaction yang bermasalah:
Transfer saldo antara user A (shard 1) dan user B (shard 3):
Step 1: Kurangi saldo user A di shard_1
Step 2: Tambah saldo user B di shard_3
Yang bisa salah:
→ Step 1 berhasil, tapi Step 2 gagal:
user A kehilangan uang, user B tidak menerima → uang hilang
→ Step 1 berhasil, koneksi ke shard_3 putus:
sama — uang hilang tanpa trace yang jelas
→ Kedua shard slow → timeout di koordinator → tidak jelas mana yang berhasil
Solusi untuk distributed transaction:
Opsi 1: Two-Phase Commit (2PC)
→ Coordinator bertanya ke semua shard: "siap commit?"
→ Jika semua siap, coordinator perintahkan commit
→ Jika ada yang tidak siap, coordinator perintahkan rollback semua
→ Masalah: coordinator bisa fail di antara prepare dan commit
(menyisakan shard dalam state yang tidak pasti)
→ Sangat lambat dan tidak skalabel untuk high-traffic
Opsi 2: Saga Pattern
→ Setiap step dilakukan secara terpisah
→ Jika step gagal, jalankan compensating transaction
→ Contoh: jika Step 2 gagal, kembalikan saldo user A
→ Lebih kompleks tapi lebih resilient dari 2PC
→ Eventual consistency — ada jendela waktu di mana state tidak konsisten
Opsi 3: Desain ulang agar transaction tetap dalam satu shard
→ Ini adalah solusi terbaik
→ Misalnya: simpan saldo di shard yang ditentukan oleh user_id pengirim
→ Transfer dicatat sebagai "debit dari A" di shard A dan "credit ke B" di shard B
→ Dua record terpisah, tidak perlu atomic cross-shard transaction
Jangan pernah mengimplementasi distributed transaction tanpa memahami sepenuhnya konsekuensinya terhadap konsistensi data. Partial failure di distributed transaction lebih berbahaya dari gagal total — karena sebagian data berubah dan sebagian tidak, dan tidak ada rollback otomatis. Desain shard key yang meminimalkan kebutuhan cross-shard transaction jauh lebih baik daripada mengandalkan distributed transaction.
Resharding: Operasi yang Paling Ditakuti #
Suatu hari, jumlah shard yang kamu miliki tidak lagi cukup. Satu atau beberapa shard mulai penuh. Kamu perlu menambah shard baru dan mendistribusikan data ke dalamnya. Ini disebut resharding — dan ini adalah salah satu operasi paling kompleks dan berisiko dalam sistem sharded.
Tantangan resharding:
Sebelum: 4 shard → Sesudah: 8 shard
Simple hash (hash % 4 → hash % 8):
→ Hampir semua data di posisi shard yang berbeda
→ Harus copy ~75% dari total data
→ Downtime atau dual-write period yang panjang
→ Risiko inkonsistensi selama proses migrasi
Consistent hashing:
→ Hanya ~12.5% data yang harus dipindahkan (1/8 dari total)
→ Jauh lebih cepat dan lebih aman
→ Tapi implementasinya lebih kompleks
Strategi resharding yang relatif aman:
Step 1: Buat shard baru tapi belum aktif
Step 2: Mulai dual-write — tulis ke shard lama DAN shard baru
Step 3: Backfill — copy data lama ke shard baru di background
Step 4: Verifikasi konsistensi antara shard lama dan baru
Step 5: Switch traffic ke shard baru
Step 6: Hapus data dari shard lama setelah periode stabilisasi
Resharding yang tidak direncanakan dengan baik bisa menyebabkan downtime berjam-jam atau bahkan kehilangan data. Ini sebabnya strategi resharding harus dirancang sejak implementasi sharding pertama — bukan ketika sudah terdesak.
Memilih Shard Key yang Tepat #
Pemilihan shard key adalah keputusan yang paling penting dalam implementasi sharding — dan paling sulit diubah setelah sistem berjalan. Shard key yang salah bisa membuat semua manfaat sharding hilang dan menambahkan kompleksitas tanpa nilai.
Kriteria shard key yang baik:
✓ Cardinality tinggi
→ Banyak nilai berbeda memastikan distribusi merata
→ user_id, order_id, transaction_id → baik
→ status, country_code → terlalu sedikit nilai, distribusi buruk
✓ Distribusi yang merata
→ Nilai terdistribusi merata di semua shard
→ user_id dengan auto-increment + hash → merata
→ created_at tanpa hash → tidak merata (hot shard di nilai terbaru)
✓ Selalu ada di query yang paling sering
→ Jika 90% query memfilter berdasarkan user_id, gunakan user_id
→ Jika shard key tidak ada di WHERE clause → cross-shard query
✓ Immutable — tidak pernah berubah
→ Jika shard key berubah, data harus dipindahkan antar shard
→ user_id tidak berubah → aman
→ email bisa berubah → berbahaya sebagai shard key
✓ Granularitas cukup untuk distribusi masa depan
→ Jika ada 10 shard dan shard key hanya punya 10 nilai unik,
tidak bisa menambah shard ke-11
Kolom yang baik sebagai shard key:
user_id, customer_id, tenant_id, account_id
Kolom yang buruk sebagai shard key:
created_at (hot shard), status (cardinality rendah),
email (bisa berubah), country_code (hanya beberapa nilai)
Arsitektur Sharding yang Lengkap #
Di produksi, sharding jarang berdiri sendiri. Ia dikombinasikan dengan replikasi di dalam setiap shard untuk high availability, partitioning untuk manajemen data dalam shard, dan connection pooler untuk efisiensi koneksi.
Arsitektur sharding lengkap di produksi:
Application Layer
┌──────────────────────────────────────────────────────────┐
│ Shard Router (menentukan shard berdasarkan shard key) │
└─────────────────────────┬────────────────────────────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌───────────▼─┐ ┌───────▼─────┐ ┌──▼────────────┐
│ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ │ │ │ │ │
│ Primary │ │ Primary │ │ Primary │
│ ↓ replikasi│ │ ↓ replikasi│ │ ↓ replikasi │
│ Replica 1 │ │ Replica 1 │ │ Replica 1 │
│ Replica 2 │ │ Replica 2 │ │ Replica 2 │
│ │ │ │ │ │
│ [partisi │ │ [partisi │ │ [partisi │
│ per bulan]│ │ per bulan]│ │ per bulan] │
└─────────────┘ └─────────────┘ └───────────────┘
Setiap shard:
→ Memiliki primary + replica sendiri (high availability)
→ Tabel besar di-partisi per waktu (query efficiency)
→ Connection pooler di depan (efisiensi koneksi)
Anti-Pattern yang Harus Dihindari #
✗ Anti-pattern 1: sharding sebelum waktunya
Sistem dengan 50 juta baris dan 1.000 user aktif tidak butuh sharding.
Optimasi query, index, partitioning, dan read replica jauh lebih tepat.
Sharding yang prematur menambah kompleksitas tanpa manfaat nyata.
✓ Exhaust semua teknik yang lebih sederhana sebelum mempertimbangkan sharding.
────────────────────────────────────────────────────────────────────────────────
✗ Anti-pattern 2: shard key yang tidak ada di query
Jika 50% query tidak menyertakan shard key di WHERE clause,
50% query tersebut akan selalu menjadi cross-shard query.
Sistem yang sangat sharded bisa lebih lambat dari single database.
✓ Analisis query pattern secara mendalam sebelum memilih shard key.
Pastikan shard key ada di mayoritas query yang paling sering dijalankan.
────────────────────────────────────────────────────────────────────────────────
✗ Anti-pattern 3: mengandalkan cross-shard JOIN
JOIN lintas shard tidak ada di level database — harus dilakukan di application.
Ini lambat, mengkonsumsi banyak memory, dan tidak bisa di-paginate dengan benar.
✓ Desain data model agar query yang penting bisa diselesaikan dalam satu shard.
Denormalisasi jika perlu untuk menghindari cross-shard join.
────────────────────────────────────────────────────────────────────────────────
✗ Anti-pattern 4: tidak menyiapkan strategy resharding
"Kita mulai dengan 4 shard, nanti kalau penuh kita tambah."
Tanpa rencana resharding yang jelas, menambah shard menjadi operasi
yang sangat berisiko dan bisa menyebabkan downtime panjang.
✓ Desain resharding strategy sejak awal. Pertimbangkan consistent hashing
atau directory-based sharding untuk fleksibilitas resharding.
────────────────────────────────────────────────────────────────────────────────
✗ Anti-pattern 5: hardcode jumlah shard di kode aplikasi
if user_id % 4 == 0: use shard_1 # hardcoded 4 shard!
Ketika menambah shard ke-5, harus mengubah semua kode yang mengandung angka 4.
✓ Abstraksi shard routing ke satu komponen yang terpusat.
Jumlah shard harus bisa berubah tanpa mengubah logika bisnis.
────────────────────────────────────────────────────────────────────────────────
✗ Anti-pattern 6: mengabaikan hot shard
Range-based sharding pada ID sequential → shard terakhir selalu paling panas.
Tidak ada monitoring yang mendeteksi ini → satu shard overload sementara yang
lain idle — sama sekali mengalahkan tujuan sharding.
✓ Monitor distribusi beban per shard. Jika tidak merata, pertimbangkan
pindah ke hash-based atau tambahkan shard baru dengan resharding.
Checklist Review Sharding #
KEPUTUSAN SHARDING:
□ Semua teknik yang lebih sederhana sudah dioptimalkan dan tidak cukup
(query optimization, index, partitioning, read replica, caching)
□ Volume data atau write throughput sudah di luar kapasitas single server
□ Tim memiliki kapabilitas untuk mengelola sistem yang jauh lebih kompleks
□ Ada rencana untuk menangani cross-shard query dan distributed transaction
PEMILIHAN SHARD KEY:
□ Shard key dipilih berdasarkan analisis pola query, bukan intuisi
□ Shard key ada di mayoritas query yang paling sering dijalankan
□ Shard key memiliki cardinality tinggi dan distribusi yang merata
□ Shard key immutable — nilainya tidak pernah berubah setelah insert
□ Shard key memberikan granularitas yang cukup untuk penambahan shard di masa depan
IMPLEMENTASI:
□ Shard routing diabstraksikan ke satu komponen terpusat
□ Jumlah shard tidak hardcoded di logika bisnis
□ Cross-shard query sudah diidentifikasi dan ada rencana penanganannya
□ Distributed transaction diminimalkan atau diganti dengan Saga pattern
□ Setiap shard memiliki replikasi untuk high availability
RESHARDING:
□ Strategy resharding sudah dirancang sejak awal
□ Proses resharding sudah didokumentasikan dan pernah diuji di staging
□ Consistent hashing atau directory-based sharding dipertimbangkan
untuk memudahkan resharding di masa depan
OBSERVABILITY:
□ Metrik per shard tersedia: QPS, latency, disk usage, connection count
□ Distribusi data per shard dimonitor — alert jika tidak merata (hot shard)
□ Cross-shard query diidentifikasi dan dimonitor
□ Error rate per shard dimonitor — alert jika satu shard lebih banyak error
Ringkasan #
- Sharding adalah langkah terakhir, bukan langkah pertama — ia menyelesaikan masalah yang tidak bisa diselesaikan cara lain, tapi dengan biaya kompleksitas yang sangat tinggi. Exhausted semua teknik yang lebih sederhana sebelum mempertimbangkan sharding.
- Sharding berbeda dari partitioning — partitioning bekerja di dalam satu database instance (satu server), sharding bekerja di level infrastruktur (beberapa server independen). Keduanya sering dikombinasikan.
- Shard key adalah keputusan paling penting — dan paling sulit diubah. Ia harus memiliki cardinality tinggi, distribusi merata, selalu ada di query paling sering, dan immutable.
- Range-based sharding mudah dipahami tapi rentan hot shard — shard yang menyimpan data terbaru selalu lebih panas. Pertimbangkan hash-based atau consistent hashing untuk distribusi yang lebih merata.
- Hash-based sharding distribusinya merata tapi resharding mahal — menambah shard baru memaksa pemindahan sebagian besar data. Consistent hashing meminimalkan data yang harus dipindahkan.
- Directory-based sharding paling fleksibel — tenant bisa dipindahkan antar shard kapan saja, tapi shard directory adalah single point of failure yang harus dijaga ketersediaannya.
- Cross-shard query harus dihindari semaksimal mungkin — ia tidak bisa dilakukan di level database, harus dilakukan di application layer dengan multiple round-trip, agregasi di memory, dan kesulitan pagination.
- Distributed transaction jauh lebih berbahaya dari yang terlihat — partial failure bisa meninggalkan data dalam state yang tidak konsisten tanpa rollback otomatis. Desain agar transaction tetap dalam satu shard sebisa mungkin.
- Resharding harus dirancang sejak awal — operasi yang paling ditakuti di sistem sharded. Tanpa rencana yang jelas, menambah shard baru bisa menjadi downtime yang sangat panjang.
- Observability per shard adalah wajib — tanpa monitoring distribusi beban, hot shard yang membunuh satu node tidak akan terdeteksi sampai terjadi insiden.
← Sebelumnya: Scalability