Database

CockroachDB: Distributed SQL untuk Aplikasi Skala Global

Tutorial komprehensif CockroachDB dari arsitektur distributed, serializability, ranges, locality-based configuration, changefeeds, hingga multi-region deployment untuk aplikasi skala global

1. Pengenalan CockroachDB

CockroachDB (sering disingkat CRDB) adalah distributed SQL database yang dirancang untuk menyediakan skalabilitas horizontal, konsistensi kuat (strong consistency), dan toleransi terhadap kegagalan (survivability). CockroachDB dibangun di atas konsep yang mirip dengan Google Spanner — menggabungkan kekuatan SQL relasional dengan arsitektur distributed yang resilien.

Nama "Cockroach" diinspirasi dari kecoa yang dikenal sangat sulit dibunuh — filosofi yang sama diterapkan pada database ini. CockroachDB bisa bertahan dari kegagalan node, bahkan kegagalan seluruh data center, tanpa kehilangan data atau ketersediaan.

Arsitektur CockroachDB Cluster
🖥️
SQL Layer
Parsing SQL, query planning,
execution engine
📦
Transaction Layer
MVCC, serializable
isolation, timestamps
💿
Distribution Layer
Ranges, Raft consensus,
leaseholder routing
💾
Storage Layer
Pebble (LSM engine),
on-disk format

1.1 Keunggulan Utama CockroachDB

1.2 Kapan Menggunakan CockroachDB?

Gunakan CockroachDBPertimbangkan Alternatif
Aplikasi multi-region globalSingle region, skala kecil
Butuh strong consistencyCukup eventual consistency
High availability kritisBisa toleransi downtime singkat
Transaksi ACID kompleksData model key-value sederhana
Skalabilitas horizontal otomatisVertical scaling cukup

2. Instalasi & Setup Cluster

CockroachDB bisa dijalankan dalam mode single-node untuk development atau multi-node cluster untuk production. Setiap node berjalan sebagai satu binary yang sama — tidak ada perbedaan antara master dan worker.

2.1 Instalasi CockroachDB

Linux — Instalasi CockroachDB
# Download CockroachDB binary terbaru
wget -qO- https://binaries.cockroachdb.com/cockroach-v24.1.0.linux-amd64.tgz | tar xvz

# Pindahkan ke PATH
sudo cp -i cockroach-v24.1.0.linux-amd64/cockroach /usr/local/bin/

# Verifikasi instalasi
cockroach version

# Instal client library (opsional, untuk bahasa tertentu)
# Python:
pip install psycopg2-binary sqlalchemy

# Node.js:
npm install pg

2.2 Memulai Single-Node Cluster

Terminal — Single-Node Development Cluster
# Buat direktori untuk data
mkdir -p ~/cockroach-data

# Mulai single-node cluster (insecure mode untuk development)
cockroach start-single-node \
  --insecure \
  --store=~/cockroach-data \
  --listen-addr=localhost:26257 \
  --http-addr=localhost:8080 \
  --background

# Buka SQL shell
cockroach sql --insecure --host=localhost:26257

# Di dalam SQL shell, buat database dan tabel:
CREATE DATABASE mydb;
USE mydb;

CREATE TABLE users (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    name STRING NOT NULL,
    email STRING UNIQUE NOT NULL,
    region STRING DEFAULT 'us-east1',
    created_at TIMESTAMPTZ DEFAULT now()
);

INSERT INTO users (name, email, region) VALUES
    ('Budi', 'budi@example.com', 'ap-southeast-1'),
    ('Andi', 'andi@example.com', 'eu-west-1'),
    ('Sari', 'sari@example.com', 'us-east1');

SELECT * FROM users;

2.3 Multi-Node Cluster Lokal

Terminal — 3-Node Cluster untuk Development
# Node 1
cockroach start \
  --insecure \
  --store=node1 \
  --listen-addr=localhost:26257 \
  --http-addr=localhost:8080 \
  --join=localhost:26257,localhost:26258,localhost:26259 \
  --background

# Node 2
cockroach start \
  --insecure \
  --store=node2 \
  --listen-addr=localhost:26258 \
  --http-addr=localhost:8081 \
  --join=localhost:26257,localhost:26258,localhost:26259 \
  --background

# Node 3
cockroach start \
  --insecure \
  --store=node3 \
  --listen-addr=localhost:26259 \
  --http-addr=localhost:8082 \
  --join=localhost:26257,localhost:26258,localhost:26259 \
  --background

# Initialize cluster (hanya sekali)
cockroach init --insecure --host=localhost:26257

# Cek status cluster
cockroach node status --insecure --host=localhost:26257
💡 CockroachDB Cloud (Serverless)

CockroachDB juga menyediakan layanan cloud di cockroachlabs.cloud dengan tier serverless yang gratis hingga 10 GiB storage dan 50M Request Units/bulan. Ini sangat cocok untuk development dan prototyping tanpa perlu setup cluster sendiri.

3. Serializability & Konsistensi

CockroachDB menjamin serializable isolation — level isolasi transaksi terturut yang memastikan hasil eksekusi transaksi concurrent sama dengan eksekusi sekuensial tertentu. Ini artinya tidak ada dirty reads, non-repeatable reads, maupun phantom reads.

3.1 Bagaimana Serializability Bekerja

CockroachDB menggunakan kombinasi dari:

SQL — Demonstrasi Serializability
-- CockroachDB SELALU dalam serializable isolation
-- Tidak perlu SET TRANSACTION ISOLATION LEVEL

-- Contoh: Transfer saldo dengan jaminan konsistensi
-- Sesi 1:
BEGIN;
  SELECT balance FROM accounts WHERE id = 'acc-1'; -- 1000
  SELECT balance FROM accounts WHERE id = 'acc-2'; -- 500
  UPDATE accounts SET balance = balance - 100 WHERE id = 'acc-1';
  UPDATE accounts SET balance = balance + 100 WHERE id = 'acc-2';
COMMIT;

-- Jika ada Sesi 2 yang mencoba update data yang sama
-- di saat bersamaan, salah satu akan mengalami
-- serialization conflict dan otomatis di-retry

-- Mengecek isolation level (selalu SERIALIZABLE):
SHOW TRANSACTION ISOLATION LEVEL;

-- Retry logic di aplikasi (penting!)
-- CockroachDB mengembalikan error 40001 (serialization failure)
-- ketika transaksi perlu di-retry

3.2 Transaction Contention & Retry

Karena serializability, transaksi yang berkompetisi untuk data yang sama bisa mengalami contention. CockroachDB menyediakan mekanisme retry otomatis di beberapa driver, tetapi umumnya developer perlu mengimplementasikan retry logic sendiri.

Python — Transaction Retry Logic
import psycopg2
from psycopg2 import errors
import time

def execute_with_retry(conn, func, max_retries=5):
    """Execute function dalam transaction dengan retry logic."""
    for attempt in range(max_retries):
        try:
            with conn:
                with conn.cursor() as cur:
                    result = func(cur)
            return result
        except errors.SerializationFailure:
            conn.rollback()
            wait = 0.1 * (2 ** attempt)  # exponential backoff
            print(f"Retry attempt {attempt + 1}, waiting {wait}s...")
            time.sleep(wait)
    raise Exception("Transaction failed after max retries")

# Contoh penggunaan
conn = psycopg2.connect("postgresql://root@localhost:26257/mydb")

def transfer_balance(cur):
    cur.execute("""
        UPDATE accounts SET balance = balance - 100
        WHERE id = 'acc-1'
    """)
    cur.execute("""
        UPDATE accounts SET balance = balance + 100
        WHERE id = 'acc-2'
    """)
    return True

execute_with_retry(conn, transfer_balance)
⚠️ Mengurangi Contention

Untuk mengurangi contention, hindari transaksi yang mengupdate baris yang sama secara bersamaan. Gunakan teknik seperti: mengurutkan akses ke tabel secara konsisten, menggunakan SELECT FOR UPDATE, membagi hot rows, atau mengubah data model untuk menghindari bottleneck.

4. Ranges & Data Distribution

CockroachDB membagi semua data menjadi ranges — potongan-potongan data dari key space yang berurutan. Setiap range berukuran default 512 MiB dan direplikasi (default 3 replika) menggunakan Raft consensus untuk memastikan konsistensi.

4.1 Bagaimana Ranges Bekerja

Distribusi Data dalam Ranges
📊
Range 1
Key A-F
Replika di Node 1, 2, 3
📊
Range 2
Key G-M
Replika di Node 2, 3, 1
📊
Range 3
Key N-Z
Replika di Node 3, 1, 2

4.2 Mengelola Ranges

SQL — Mengelola dan Memantau Ranges
-- Melihat informasi ranges
SHOW RANGES FROM TABLE users;

-- Melihat distribusi ranges di seluruh node
SELECT
    range_id,
    start_key,
    end_key,
    lease_holder,
    replicas
FROM crdb_internal.ranges
ORDER BY range_id;

-- Melihat statistik ranges
SELECT
    range_id,
    key_bytes,
    val_bytes,
    live_bytes,
    lease_holder
FROM crdb_internal.range_stats
LIMIT 20;

-- Mengubah ukuran range (untuk testing/scenario tertentu)
ALTER TABLE users CONFIGURE ZONE USING
    range_min_bytes = 134217728,  -- 128 MiB
    range_max_bytes = 536870912;  -- 512 MiB

-- Manual split (biasanya tidak perlu karena otomatis)
ALTER TABLE users SPLIT AT VALUES ('M');

-- Manual merge (menggabungkan range kecil)
ALTER TABLE users UNSPLIT AT VALUES ('M');

-- Melihat hot ranges
SELECT
    range_id,
    lease_holder,
    reads_per_second,
    writes_per_second
FROM crdb_internal.ranges_no_leases r
JOIN crdb_internal.node_metrics m ON r.lease_holder = m.node_id
ORDER BY reads_per_second DESC
LIMIT 10;

4.3 Index & Range Interaction

Setiap tabel dan index di CockroachDB dipecah menjadi range terpisah. Pemilihan primary key sangat mempengaruhi distribusi data:

SQL — Primary Key untuk Distribusi Optimal
-- HINDARI: Auto-increment ID bisa menyebabkan hot spot
-- Semua write masuk ke range terakhir
CREATE TABLE bad_example (
    id SERIAL PRIMARY KEY,
    data STRING
);

-- LEBIH BAIK: Hash-sharded primary key
CREATE TABLE orders (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    customer_id INT NOT NULL,
    total DECIMAL(10,2),
    created_at TIMESTAMPTZ DEFAULT now()
);

-- ATAU: Gunakan hash-sharded index untuk kolom sequential
CREATE INDEX idx_orders_created ON orders (created_at)
    USING HASH;

-- Untuk tabel besar, pertimbangkan composite key dengan locality
CREATE TABLE events (
    region STRING,
    created_at TIMESTAMPTZ,
    event_id UUID DEFAULT gen_random_uuid(),
    payload JSONB,
    PRIMARY KEY (region, created_at, event_id)
);

5. Locality & Zone Configuration

Locality memungkinkan Anda memberi tahu CockroachDB tentang topologi fisik cluster — region mana setiap node berada. Informasi ini digunakan untuk menempatkan replika data secara strategis, mengoptimalkan latensi baca, dan memenuhi persyaratan kepatuhan data (data residency).

5.1 Menentukan Locality Saat Startup

Terminal — Memulai Node dengan Locality
# Node di US East
cockroach start \
  --insecure \
  --store=node-us-east \
  --listen-addr=us-east.cockroach.internal:26257 \
  --join=node-us-east:26257,node-eu-west:26257,node-ap-south:26257 \
  --locality=region=us-east,zone=us-east-1a \
  --background

# Node di EU West
cockroach start \
  --insecure \
  --store=node-eu-west \
  --listen-addr=eu-west.cockroach.internal:26257 \
  --join=node-us-east:26257,node-eu-west:26257,node-ap-south:26257 \
  --locality=region=eu-west,zone=eu-west-1a \
  --background

# Node di AP South
cockroach start \
  --insecure \
  --store=node-ap-south \
  --listen-addr=ap-south.cockroach.internal:26257 \
  --join=node-us-east:26257,node-eu-west:26257,node-ap-south:26257 \
  --locality=region=ap-south,zone=ap-south-1a \
  --background

# Initialize cluster
cockroach init --insecure --host=node-us-east:26257

5.2 Zone Configuration

SQL — Zone Configuration untuk Locality
-- Konfigurasi zona untuk tabel: replika di setiap region
ALTER TABLE users CONFIGURE ZONE USING
    num_replicas = 3,
    constraints = '{+region=us-east: 1, +region=eu-west: 1, +region=ap-south: 1}',
    lease_preferences = '[[+region=us-east]]';

-- Konfigurasi database-level
ALTER DATABASE mydb CONFIGURE ZONE USING
    num_replicas = 3,
    constraints = '{+region=us-east: 1, +region=eu-west: 1}';

-- Melihat zone configuration
SHOW ZONE CONFIGURATION FOR TABLE users;
SHOW ZONE CONFIGURATION FOR DATABASE mydb;

-- Partitioning berdasarkan region
ALTER TABLE users PARTITION BY LIST (region) (
    PARTITION us_east VALUES IN ('us-east'),
    PARTITION eu_west VALUES IN ('eu-west'),
    PARTITION ap_south VALUES IN ('ap-south')
);

-- Setelah partition, atur placement setiap partisi
ALTER PARTITION us_east OF TABLE users CONFIGURE ZONE USING
    constraints = '{+region=us-east: 2}',
    lease_preferences = '[[+region=us-east]]';

ALTER PARTITION eu_west OF TABLE users CONFIGURE ZONE USING
    constraints = '{+region=eu-west: 2}',
    lease_preferences = '[[+region=eu-west]]';

ALTER PARTITION ap_south OF TABLE users CONFIGURE ZONE USING
    constraints = '{+region=ap-south: 2}',
    lease_preferences = '[[+region=ap-south]]';
💡 Locality-Optimized Search

CockroachDB bisa menggunakan locality-optimized search untuk mengurangi latensi cross-region reads. Dengan mengatur ALTER DATABASE ... SET LOCALITY REGIONAL BY ROW, read untuk baris tertentu akan di-route ke node terdekat terlebih dahulu.

6. Changefeeds (Change Data Capture)

Changefeeds memungkinkan Anda memantau dan mengirimkan perubahan data secara real-time ke sistem eksternal seperti Kafka, cloud storage, atau webhook. Ini sangat berguna untuk event sourcing, data replication, cache invalidation, dan analytics pipeline.

6.1 Membuat Changefeed

SQL — Changefeed ke Kafka dan Cloud Storage
-- Aktifkan rangefeed di cluster
SET CLUSTER SETTING kv.rangefeed.enabled = true;

-- Changefeed ke Kafka
CREATE CHANGEFEED FOR TABLE users, orders
INTO 'kafka://kafka-broker:9092?topic_prefix=cdc_'
WITH updated, resolved, envelope = 'wrapped';

-- Changefeed ke cloud storage (S3, GCS, Azure)
CREATE CHANGEFEED FOR TABLE users
INTO 's3://my-bucket/cdc/users?AWS_ACCESS_KEY_ID={key}&AWS_SECRET_ACCESS_KEY={secret}'
WITH format = 'parquet', compression = 'gzip';

-- Changefeed ke webhook
CREATE CHANGEFEED FOR TABLE orders
INTO 'webhook-https://my-api.com/cdc-webhook?insecure_tls_skip_verify=true'
WITH webhook_auth_header = 'Bearer my-secret-token',
     webhook_client_timeout = '5s',
     envelope = 'wrapped';

-- Changefeed dengan filter dan transformasi
CREATE CHANGEFEED FOR TABLE orders
INTO 'kafka://kafka-broker:9092'
WITH updated, envelope = 'wrapped',
     format = 'json',
     key_in_value;

-- Changefeed hanya untuk kolom tertentu (cdc_prev berguna untuk audit)
CREATE CHANGEFEED FOR TABLE orders (id, status, total)
INTO 'kafka://kafka-broker:9022?topic_prefix=order_changes'
WITH updated, resolved;

6.2 Memantau & Mengelola Changefeeds

SQL — Mengelola Changefeed Jobs
-- Melihat semua changefeed jobs
SHOW JOBS WHEN COMPLETE SELECT * FROM [SHOW CHANGEFEED JOBS];

-- Melihat status changefeed
SELECT job_id, status, created, description
FROM [SHOW CHANGEFEED JOBS];

-- Pause changefeed
PAUSE JOB 12345;

-- Resume changefeed
RESUME JOB 12345;

-- Cancel changefeed
CANCEL JOB 12345;

-- Melihat changefeed yang sedang aktif
SELECT
    job_id,
    job_type,
    status,
    fraction_completed,
    high_water_timestamp
FROM crdb_internal.jobs
WHERE job_type = 'CHANGEFEED';

-- Mengubah sink URI changefeed
ALTER CHANGEFEED 12345 SET sink_uri = 'kafka://new-broker:9092';

-- Menambah tabel ke changefeed
ALTER CHANGEFEED 12345 ADD TABLE new_table;

6.3 Format Output Changefeed

FormatDeskripsiCocok Untuk
jsonJSON standar, mudah dibacaWebhook, debugging
avroBinary format dengan schema registryKafka, analytics
csvComma-separated valuesExport, reporting
parquetColumnar formatData warehouse, S3

7. Multi-Region Deployment

CockroachDB menyediakan fitur multi-region yang powerful melalui survival goals dan table localities. Ini memungkinkan Anda menentukan bagaimana data direplikasi antar region dan seberapa resilien terhadap kegagalan region.

7.1 Mengatur Database Multi-Region

SQL — Multi-Region Database Setup
-- Pastikan node sudah diberi locality saat startup
-- Set database sebagai multi-region
ALTER DATABASE mydb SET PRIMARY REGION "us-east1";
ALTER DATABASE mydb ADD REGION "eu-west1";
ALTER DATABASE mydb ADD REGION "ap-southeast-1";

-- Set survival goal: survive region failure
ALTER DATABASE mydb SURVIVE REGION FAILURE;

-- Melihat region yang terdaftar
SHOW REGIONS FROM DATABASE mydb;

-- Tabel global: dibaca cepat dari semua region, ditulis dari region utama
CREATE TABLE products (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    name STRING NOT NULL,
    price DECIMAL(10,2)
) LOCALITY GLOBAL;

-- Tabel regional by row: setiap baris dikaitkan dengan region tertentu
CREATE TABLE user_accounts (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    email STRING NOT NULL,
    crdb_region crdb_internal_region NOT NULL DEFAULT gateway_region(),
    name STRING NOT NULL
) LOCALITY REGIONAL BY ROW;

-- Baris akan otomatis disimpan di region yang sesuai
INSERT INTO user_accounts (email, name) VALUES ('budi@id.com', 'Budi');
-- Data ini akan disimpan di region AP (jika koneksi dari sana)

-- Membaca dari region spesifik
SELECT * FROM user_accounts
WHERE crdb_region = 'ap-southeast-1';

-- Tabel regional by table: semua data di satu region
CREATE TABLE local_configs (
    key STRING PRIMARY KEY,
    value STRING
) LOCALITY REGIONAL BY TABLE IN REGION "us-east1";

7.2 Memahami Survival Goals

Survival GoalReplika MinimumToleransiWrite Latency
ZONE FAILURE (default)31 zone matiRendah
REGION FAILURE5 (across 3+ regions)1 region matiLebih tinggi

7.3 Multi-Region Best Practices

SQL — Optimasi Multi-Region Queries
-- Gunakan gateway_region() untuk otomatis mengaitkan baris
-- dengan region dari koneksi
INSERT INTO user_accounts (email, name, crdb_region)
VALUES ('user@local.com', 'Local User', gateway_region());

-- Cross-region join tetap bisa dilakukan
-- tapi coba hindari untuk latensi rendah
SELECT
    u.name,
    u.crdb_region,
    o.total
FROM user_accounts u
JOIN orders o ON u.id = o.user_id
WHERE u.crdb_region = gateway_region();

-- Gunakan GLOBAL table untuk data yang jarang berubah
-- dan dibutuhkan di semua region (lookup table)
CREATE TABLE countries (
    code STRING PRIMARY KEY,
    name STRING NOT NULL
) LOCALITY GLOBAL;

-- Statistik untuk monitoring cross-region
SELECT
    u.crdb_region,
    COUNT(*) as user_count,
    AVG(o.total) as avg_order
FROM user_accounts u
JOIN orders o ON u.id = o.user_id
GROUP BY u.crdb_region;

8. Optimasi & Best Practices

8.1 Query Performance

SQL — EXPLAIN dan Query Optimasi
-- Analisis query plan
EXPLAIN SELECT * FROM users WHERE region = 'us-east';

-- EXPLAIN dengan detail
EXPLAIN (VERBOSE, TYPES)
    SELECT u.name, COUNT(o.id)
    FROM users u JOIN orders o ON u.id = o.user_id
    GROUP BY u.name;

-- EXPLAIN ANALYZE (mengeksekusi query dan menampilkan statistik)
EXPLAIN ANALYZE
    SELECT * FROM orders WHERE total > 100 ORDER BY created_at DESC;

-- Melihat statistik tabel
SHOW STATISTICS FOR TABLE users;

-- Mengumpulkan statistik manual
ANALYZE users;

-- Melihat index usage
SELECT
    ti.index_name,
    ti.index_type,
    ts.total_reads,
    ts.last_read
FROM crdb_internal.table_indexes ti
JOIN crdb_internal.index_usage_statistics ts
    ON ti.index_id = ts.index_id AND ti.table_id = ts.table_id
WHERE ti.descriptor_name = 'users';

8.2 Cluster Monitoring

SQL — Monitoring Cluster Health
-- Node liveness
SELECT node_id, address, is_live
FROM crdb_internal.gossip_nodes;

-- Replication status
SELECT
    store_id,
    node_id,
    range_count,
    lease_count,
    total_bytes
FROM crdb_internal.kv_store_status;

-- Cluster settings yang berguna
SET CLUSTER SETTING server.time_until_store_dead = '10m';
SET CLUSTER SETTING kv.snapshot_rebalance.max_rate = '64 MiB';
SET CLUSTER SETTING kv.rangefeed.enabled = true;

-- SQL activity monitoring
SELECT
    query,
    count,
    service_lat_avg,
    service_lat_p99
FROM crdb_internal.node_statement_statistics
ORDER BY count DESC
LIMIT 20;

8.3 Checklist Production

AspekRekomendasi
Cluster SizeMinimum 3 node, ideal 5+ untuk production
Replication3 replika default, 5 untuk critical data
BackupsScheduled backup ke cloud storage setiap jam
MonitoringPrometheus + Grafana, atau CockroachDB Cloud Console
SecuritySSL/TLS, RBAC, audit logging
Connection PoolGunakan PgBouncer atau HikariCP
VersionUpgrade secara berkala, baca release notes

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang CockroachDB:

Pertanyaan 1: Apa isolation level default di CockroachDB?

a) Read Committed
b) Repeatable Read
c) Serializable
d) Snapshot

Pertanyaan 2: Apa yang terjadi ketika transaksi mengalami serialization conflict di CockroachDB?

a) Data menjadi corrupt
b) Transaksi di-commit dengan versi terbaru
c) Transaksi mengembalikan error 40001 dan perlu di-retry
d) CockroachDB menurunkan isolation level otomatis

Pertanyaan 3: Apa fungsi dari ranges di CockroachDB?

a) Mengelola user authentication
b) Membagi data menjadi potongan dan mendistribusikannya ke node
c) Mengompresi data untuk menghemat storage
d) Mencatat log perubahan data

Pertanyaan 4: Apa fungsi dari changefeeds di CockroachDB?

a) Mengubah schema database secara otomatis
b) Menyediakan cache untuk query yang sering dijalankan
c) Mengirimkan perubahan data secara real-time ke sistem eksternal
d) Mengenkripsi data sebelum disimpan ke disk

Pertanyaan 5: Table locality apa yang cocok untuk data lookup (seperti daftar negara) yang dibaca dari semua region?

a) REGIONAL BY ROW
b) REGIONAL BY TABLE
c) GLOBAL
d) UNDEFINED
← Sebelumnya Supabase Realtime Features Selanjutnya → ClickHouse untuk Real-time Analytics
🔍 Zoom
100%
🎨 Tema