PostgreSQL dapat menangani jutaan baris dan ribuan koneksi bersamaan pada satu node — sebagian besar aplikasi tidak pernah membutuhkan sharding. Tetapi ketika Anda memang melampaui ambang batas itu, keputusan sharding membentuk seluruh arsitektur Anda selama bertahun-tahun. Saya telah membangun sistem berbasis PostgreSQL untuk kasus penggunaan ERP di mana satu node database menangani semua data Commsult Indonesia dengan nyaman. Panduan ini mencakup spektrum dari partisi tabel (yang dapat dan harus Anda lakukan lebih awal) hingga sharding terdistribusi penuh dengan Citus.
Partisi dan sharding saling berkaitan tetapi berbeda. Partisi membagi tabel besar menjadi tabel fisik yang lebih kecil (partisi) pada node database yang sama. Ini meningkatkan kinerja kueri dengan pemangkasan — kueri dengan klausa WHERE pada kunci partisi hanya memindai partisi yang relevan. Sharding mendistribusikan partisi tersebut ke beberapa node database, memungkinkan scale-out horizontal.
PostgreSQL telah mendukung partisi tabel deklaratif sejak versi 10. Dua strategi yang paling berguna adalah partisi RANGE (berdasarkan tanggal — sangat baik untuk data time-series seperti log audit, event, dan invoice) dan partisi HASH (berdasarkan hash ID tenant atau ID pengguna). Untuk tabel audit_logs sistem ERP dengan jutaan baris per tahun, partisi berdasarkan bulan mengurangi waktu kueri dari detik menjadi milidetik.
PostgreSQL Scaling Decision Tree
Start Here: Is your primary DB > 70% CPU/IO?
│
No ──► Do you have read replicas?
│ No ──► Add read replicas (PRIMARY + 2 REPLICAS)
│ Yes ──► Add more read replicas or optimize queries
│
Yes
│
▼
Are your largest tables partitioned?
│
No ──► Add table partitioning (RANGE by date, HASH by tenant_id)
│ This often solves the problem without sharding
│
Yes
│
▼
Do you have a multi-tenant architecture?
│
Yes ──► Citus Schema-based sharding (1 schema per tenant)
No ──► Citus Row-based sharding (distribute by entity key)
─────────────────────────────────────────────────────────
Citus Worker Architecture:
┌─────────────────┐
│ Coordinator │ ← Application connects here
│ (PostgreSQL) │ ← Routes queries, parallelizes joins
└────────┬────────┘
│
┌──────┴──────┐
▼ ▼
Worker 1 Worker 2
Shards 0-3 Shards 4-7
(tenant A-M) (tenant N-Z)Dari membangun sistem audit trail untuk ERP Commsult: partisi tabel bervolume tinggi Anda berdasarkan created_at bulan dari hari pertama. Jalur migrasi dari tabel yang tidak dipartisi ke yang dipartisi itu menyakitkan — Anda perlu mengganti nama tabel lama, membuat induk yang dipartisi baru, menyalin data, dan melakukan swap. Melakukannya di awal ketika tabel kosong hampir tidak memerlukan biaya dan menghemat jendela pemeliharaan darurat ketika tabel mencapai 50 juta baris.
Citus memperluas PostgreSQL dengan eksekusi kueri terdistribusi di seluruh node koordinator dan node pekerja. Tabel didistribusikan melalui kolom distribusi — Citus hash nilai kolom dan menugaskan setiap shard (subset baris) ke node pekerja. Citus 12 memperkenalkan sharding berbasis skema, yang ideal untuk SaaS multi-tenant — setiap tenant mendapatkan skema mereka sendiri, dan Citus memperlakukan skema sebagai batas shard.
-- PostgreSQL Range Partitioning (by month)
CREATE TABLE audit_logs (
id UUID DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
action VARCHAR(50) NOT NULL,
resource VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
) PARTITION BY RANGE (created_at);
-- Create monthly partitions
CREATE TABLE audit_logs_2025_01
PARTITION OF audit_logs
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE audit_logs_2025_02
PARTITION OF audit_logs
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
-- Index each partition (auto-inherited from parent)
CREATE INDEX ON audit_logs (created_at, user_id);
-- Citus: Distribute a table across shards
SELECT create_distributed_table('orders', 'tenant_id');
SELECT create_distributed_table('invoices', 'tenant_id');
-- Reference tables (replicated to all shards for JOINs)
SELECT create_reference_table('products');
SELECT create_reference_table('currencies');Pilihan kolom distribusi menentukan kinerja kueri. Aturan emas: kueri yang memfilter pada kolom distribusi cepat; kueri yang tidak adalah scatter-gather (menyentuh semua shard). Untuk ERP multi-tenant, tenant_id adalah kolom distribusi yang jelas — semua kueri dari sesi tenant memfilter pada tenant_id mereka, sehingga lokalitas data dimaksimalkan.
Dalam pengaturan PostgreSQL yang di-shard, foreign key dan join lintas shard tidak tersedia atau sangat mahal. Anda kehilangan jaminan integritas referensial standar — Anda harus menerapkannya di lapisan aplikasi. Transaksi yang mencakup beberapa shard memerlukan protokol transaksi terdistribusi. Migrasi skema harus diterapkan ke semua shard secara bersamaan. Jangan lakukan sharding sampai Anda benar-benar menghabiskan penskalaan vertikal.
Sinyal yang menunjukkan Anda membutuhkan sharding: CPU atau I/O node database utama Anda secara konsisten di atas 70% meskipun ada optimasi kueri dan pengindeksan; tabel terbesar Anda dalam ratusan gigabyte dan tumbuh lebih cepat dari yang dapat Anda tambahkan penyimpanan; Anda memiliki arsitektur multi-tenant di mana isolasi data tenant adalah persyaratan keras.
Sebelum mempertimbangkan sharding, tambahkan read replica PostgreSQL. Sebagian besar aplikasi berat dalam membaca — 80-90% kueri adalah bacaan. Primary + 2 read replica melipattigakan kapasitas baca Anda tanpa perubahan skema. Arahkan kueri analitik, pembuatan laporan, dan endpoint API hanya-baca ke replica; arahkan penulisan ke primary.
Migrasi dari node PostgreSQL tunggal ke kluster Citus yang di-shard adalah non-trivial. Jalur yang direkomendasikan: pertama tambahkan partisi ke tabel terbesar Anda di node tunggal. Kemudian provisikan koordinator Citus + pekerja. Migrasikan tabel yang dipartisi ke tabel terdistribusi menggunakan create_distributed_table() Citus. Perbarui string koneksi aplikasi Anda untuk menunjuk ke koordinator Citus. Rencanakan 2-4 minggu pekerjaan migrasi untuk database besar.