Skalabilitas database relasional horizontal memerlukan partisi data ke beberapa node database independen (shards). Masalah rekayasa utamanya terletak pada titik penanganan logika perutean kueri: apakah kueri dirutekan langsung di dalam kode monolit menggunakan multiple database connection pool, atau dienkapsulasi menggunakan routing proxy layer independen seperti arsitektur Vitess (PlanetScale) dan Citus.
Arsitektur Sharding: Client-Side Routing vs Database Proxy
Dua pendekatan utama untuk mendistribusikan kueri ke shard cluster:
- Client-Side Routing (Monolit): Aplikasi mengelola koneksi langsung ke setiap database shard. Kode aplikasi mengevaluasi shard key (misalnya
user_id), menghitung fungsi hash, lalu memilih connection pool target sebelum menjalankan kueri. - Routing Proxy Layer (Vitess Model): Aplikasi menganggap database sebagai satu endpoint logis tunggal. Proxy layer (misalnya
VTGatepada Vitess) menerima kueri SQL mentah, mem-parse Abstract Syntax Tree (AST), mengekstrak predicate yang relevan dengan shard key, membuka koneksi ke shard target (misalnyaVTTablet), lalu menggabungkan hasil kueri jika diperlukan (scatter-gather).
Trade-Off Teknis: Proxy Layer vs Application-Level Routing
1. Query Planner dan Parsing AST
Pada monolit, developer mengontrol kueri secara eksplisit melalui kode. Jika operasi lintas shard (cross-shard) diperlukan, developer menulis kode agregasi kustom di memori aplikasi. Pada proxy routing, proxy wajib mengimplementasikan query planner SQL penuh. Proxy harus mendeteksi kueri yang aman diarahkan ke satu shard versus kueri kompleks (seperti JOIN lintas shard atau agregasi non-partisi) yang membutuhkan eksekusi terdistribusi. Ini meningkatkan beban CPU proxy dan membatasi sintaks SQL yang didukung.
2. Latensi Jaringan Tambahan (Network Hop)
Proxy memperkenalkan network hop tambahan (biasanya 0.5–2 ms dalam jaringan internal/VPC yang sama). Untuk kueri transaksional frekuensi tinggi (OLTP), penambahan latensi ini dapat berdampak signifikan pada throughput p99 aplikasi jika proxy tidak ditempatkan berdampingan (co-located) atau dioptimalkan dengan zero-copy networking.
3. Connection Pool Exhaustion
Jika ada 50 instans monolit dan 100 node shard, koneksi langsung dari aplikasi menghasilkan minimum 50 x 100 x pool_size koneksi simultan ke database cluster. Skema ini cepat membuat database kehabisan thread koneksi. Proxy layer berfungsi sebagai connection multiplexer terpusat: puluhan ribu koneksi aplikasi dapat di-multiplexing ke sejumlah kecil koneksi persisten ke database backend, mengeliminasi bottleneck memori per koneksi di engine database (MySQL/PostgreSQL).
Implementasi Sederhana: Hash-Based Connection Router di Go
Berikut implementasi connection router berbasis Rendezvous Hash / Murmur3 yang memetakan kueri berdasarkan shard key ke pool target tanpa dependensi framework eksternal:
package main
import (
"context"
"database/sql"
"fmt"
"hash/fnv"
"sync"
)
type ShardRouter struct {
mu sync.RWMutex
shards []*sql.DB
}
func NewShardRouter(dsns []string) (*ShardRouter, error) {
shards := make([]*sql.DB, len(dsns))
for i, dsn := range dsns {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("failed connecting shard %d: %w", i, err)
}
// Batasi pool per shard
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
shards[i] = db
}
return &ShardRouter{shards: shards}, nil
}
// GetShard memilih shard target menggunakan modulo hashing
func (r *ShardRouter) GetShard(shardKey string) *sql.DB {
h := fnv.New32a()
h.Write([]byte(shardKey))
idx := h.Sum32() % uint32(len(r.shards))
return r.shards[idx]
}
func (r *ShardRouter) QueryRow(ctx context.Context, shardKey string, query string, args ...any) *sql.Row {
db := r.GetShard(shardKey)
return db.QueryRowContext(ctx, query, args...)
}
Kapan Transisi ke Dedicated Proxy Cluster Layak?
Mengadopsi proxy layer khusus (seperti kluster Vitess mandiri) menambah overhead pemeliharaan infrastruktur stateful. Transisi ini layak secara rasio biaya komputasi dan kompleksitas sistem ketika memenuhi kriteria berikut:
- Jumlah Shard > 16 Node: Saat jumlah backend shard bertambah, overhead connection pool pada monolit menyebabkan utilisasi RAM berlebih di database hanya untuk alokasi thread koneksi (misal 8–10 MB per koneksi di Postgres).
- Monolit Berpindah ke Polyglot Microservices: Jika sistem terdiri dari Go, Node.js, dan Python, menulis ulang logika hash routing, resharding logic, dan connection pooling di setiap service secara terpisah menghasilkan duplikasi kode dan risiko inkonsistensi data hash.
- Dibutuhkan Online Resharding Tanpa Downtime: Proxy menyembunyikan perubahan topologi database. Ketika sebuah shard dipecah menjadi dua, proxy dapat membagi lalu lintas baca/tulis secara dinamis (shadow writing, cutover) tanpa perlunya deploy ulang kode aplikasi.
Aturan Praktis: Tetap gunakan application-level routing di dalam monolit jika jumlah shard masih di bawah 8 node dan basis kode bersifat seragam. Alihkan ke dedicated proxy layer jika overhead koneksi database menekan resource memori atau aplikasi mulai dipecah menjadi multiple services yang mengakses shard yang sama.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!