Langsung ke konten utama
DevOpsMar 28, 2026

Sharding vs Partitioning — Arsitektur untuk Tabel Masif

OS
Open Soft Team

Engineering Team

Partitioning vs Sharding: Apa Bedanya?

Partitioning membagi tabel menjadi beberapa bagian dalam satu server database. PostgreSQL mengelolanya secara transparan.

Sharding mendistribusikan data ke beberapa server database yang berbeda. Aplikasi (atau middleware) bertanggung jawab routing query ke shard yang tepat.

AspekPartitioningSharding
ServerSatuBanyak
TransparansiOtomatisManual/middleware
SkalabilitasVertikalHorizontal
KompleksitasRendahTinggi
Cross-partition queryMudahSulit/lambat
BatasHardware satu serverTeoritis tak terbatas

Kapan Partitioning Cukup

Partitioning cukup ketika:

  • Data muat di satu server (< 1TB aktif)
  • Beban write < 10.000 TPS
  • Perlu cross-partition query yang sering
  • Tim kecil tanpa expertise distributed systems

Kapan Harus Sharding

Pertimbangkan sharding ketika:

  • Data melebihi kapasitas satu server
  • Beban write > 10.000 TPS
  • Memerlukan isolasi multi-region
  • Ketersediaan 99.99%+ diperlukan

Strategi Shard Key

Pilihan shard key menentukan distribusi data dan pola query:

1. Hash-Based Sharding

fn get_shard(user_id: Uuid, num_shards: u32) -> u32 {
    let hash = xxhash(&user_id.as_bytes());
    hash % num_shards
}

Distribusi merata, tetapi range query harus menghubungi semua shard.

2. Range-Based Sharding

fn get_shard(created_at: DateTime) -> &str {
    match created_at.year() {
        2024 => "shard_2024",
        2025 => "shard_2025",
        2026 => "shard_2026",
        _ => "shard_default",
    }
}

Cocok untuk data time-series. Shard lama bisa di-archive.

3. Directory-Based Sharding

async fn get_shard(tenant_id: Uuid, directory: &PgPool) -> String {
    sqlx::query_scalar("SELECT shard_name FROM shard_directory WHERE tenant_id = $1")
        .bind(tenant_id)
        .fetch_one(directory)
        .await
        .unwrap()
}

Fleksibel — bisa memindahkan tenant antar shard. Tetapi directory adalah single point of failure.

Implementasi Query Router di Rust

struct ShardRouter {
    shards: HashMap<u32, PgPool>,
    num_shards: u32,
}

impl ShardRouter {
    fn get_pool(&self, shard_key: &Uuid) -> &PgPool {
        let shard_id = xxhash(shard_key.as_bytes()) % self.num_shards;
        &self.shards[&shard_id]
    }
    
    async fn query_all<T: FromRow>(
        &self,
        query: &str,
    ) -> Result<Vec<T>, DbError> {
        let futures: Vec<_> = self.shards.values()
            .map(|pool| {
                sqlx::query_as::<_, T>(query)
                    .fetch_all(pool)
            })
            .collect();
        
        let results = futures::future::try_join_all(futures).await?;
        Ok(results.into_iter().flatten().collect())
    }
}

Migrasi ke Sharding

  1. Mulai dengan partitioning — Lebih sederhana, selesaikan dulu
  2. Identifikasi shard key — Kolom yang paling sering di-filter
  3. Dual-write — Tulis ke database lama dan baru secara bersamaan
  4. Verifikasi — Bandingkan hasil query antara kedua sistem
  5. Cutover — Arahkan pembacaan ke sistem baru
  6. Cleanup — Hapus dual-write dan database lama

Tantangan Sharding

  1. Cross-shard join — Tidak bisa JOIN antar shard secara native
  2. Transaksi terdistribusi — 2-phase commit lambat dan kompleks
  3. Rebalancing — Menambah shard memerlukan redistribusi data
  4. Operasional — Monitoring, backup, dan upgrade lebih kompleks

Kesimpulan

Mulai dengan partitioning — ini menyelesaikan 90% masalah skala database. Sharding adalah langkah selanjutnya ketika satu server tidak lagi cukup. Pilih shard key berdasarkan pola query, implementasikan router query, dan rencanakan migrasi dengan hati-hati.