High Availability & Multi-Region Database Sharding dengan SQLite di Edge
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap arsitektur web modern, paradigma Serverless dan Edge Computing telah mengubah cara kita mendistribusikan aplikasi. Cloudflare Workers, AWS Lambda@Edge, dan Deno Deploy memungkinkan eksekusi logika aplikasi milidetik dari pengguna akhir. Namun, rintangan terbesar dari arsitektur terdistribusi global ini bukanlah pada komputasi, melainkan pada lapisan penyimpanan data (persistence layer).
Secara tradisional, arsitektur database mengikuti pola Client-Server sentralistik, di mana instance MySQL, PostgreSQL, atau MariaDB diletakkan di satu region cloud utama (misalnya us-east-1 atau ap-southeast-1). Ketika pengguna di Frankfurt atau Tokyo mengakses aplikasi edge, permintaan komputasi ditangani secara instan di edge terdekat, tetapi kueri database harus melintasi jaringan global kembali ke server pusat. Hal ini menimbulkan latensi jaringan yang masif (bisa mencapai 200ms–400ms), yang secara langsung merusak metrik Time to First Byte (TTFB) dan User Experience.
[Client di Tokyo] ---> (Edge Worker: 5ms) ---> (Database Pusat di US-East: 250ms)
Total Latency: ~255ms (Bottleneck pada Network Round Trip ke Database Sentral)
Di sisi lain, penerapan database terdistribusi konvensional seperti CockroachDB atau Spanner sering kali menghadapi kendala kompleksitas operasional, biaya infrastruktur yang sangat tinggi, dan batasan konsistensi lintas benua yang diatur oleh hukum fisika (speed of light dalam replikasi Paxos/Raft).
Di sinilah SQLite di edge hadir sebagai solusi revolusioner. Dengan membawa file database SQLite langsung ke edge node di dekat pengguna dan memanfaatkan teknologi replikasi berbasis Conflict-free Replicated Data Types (CRDTs) atau Write-Ahead Log (WAL) streaming, kita dapat mencapai operasi baca (read) dengan latensi mendekati 0ms dan penulisan (write) yang aman di seluruh dunia.
Kondisi Sebelum vs Sesudah Penerapan
| Metrik / Parameter | Sebelum: Database Terpusat Tradisional | Sesudah: SQLite Sharding di Edge | Dampak Bisnis |
|---|---|---|---|
| Latensi Baca (P99) | 180ms - 350ms (Lintas Benua) | 2ms - 10ms (Lokal di Edge Node) | Peningkatan drastis pada Core Web Vitals (LCP & INP) |
| Latensi Tulis (P99) | 150ms - 250ms | 50ms - 150ms (dengan optimasi routing) | Pengalaman pengguna lebih responsif secara global |
| Ketersediaan (Availability) | Rentan terhadap Single Point of Failure pusat | High Availability mutlak, otonom per region | Zero downtime meski satu region cloud mengalami gangguan total |
| Biaya Infrastruktur | Tinggi (Managed DB instance per region mahal) | Sangat rendah (Penyimpanan lokal SSD/NVMe edge) | Penghematan cost cloud hingga 60% - 80% |
[!WARNING] SQLite bukanlah pengganti mutlak untuk semua skenario database relasional skala raksasa. Penggunaan SQLite di edge memerlukan pemahaman mendalam tentang model konsistensi (Eventual vs Strong Consistency) dan strategi penanganan konflik penulisan.
2. Konsep Inti & Deep Dive Arsitektur
Arsitektur SQLite di edge mengandalkan prinsip Embedded Database Replication. Berbeda dengan client-server, SQLite berjalan di dalam proses yang sama dengan runtime aplikasi (seperti V8 isolate di Cloudflare Workers atau proses Node.js/Go di server edge). File database .sqlite berada langsung pada local disk atau memori edge node.
Alur Eksekusi & Replikasi Data
+-------------------------------------------------------------------+
| GLOBAL EDGE NETWORK |
| |
| +--------------------+ +-----------------------------+ |
| | Region: Frankfurt | | Region: Singapore | |
| | | | | |
| | [Client] | | [Client] | |
| | | | | | | |
| | v | | v | |
| | [Edge Worker] | | [Edge Worker] | |
| | | (Read/Write) | | | (Read/Write) | |
| | v | | v | |
| | [Local SQLite File]| | [Local SQLite File] | |
| +---------+----------+ +--------------+--------------+ |
| | | |
| +------------------+----------------+ |
| | |
| v |
| [Global Replication Mesh / WAL] |
| (Litestream / LiteFS / rqlite) |
+-------------------------------------------------------------------+
- Local Reads (Latensi Ultra-Rendah): Ketika aplikasi edge menerima permintaan pembacaan (
SELECT), kueri dieksekusi langsung terhadap file SQLite lokal tanpa ada panggilan jaringan keluar. Kecepatan baca dibatasi oleh kecepatan I/O disk lokal (NVMe). - Mutasi Data / Writes: Karena SQLite adalah file tunggal yang dikunci saat penulisan, operasi tulis (
INSERT,UPDATE,DELETE) diarahkan ke primary node yang ditentukan atau ditangani melalui lapisan replikasi terdistribusi (seperti LiteFS atau Cloudflare D1). - Replikasi WAL (Write-Ahead Log): Perubahan yang terjadi pada database dikemas dalam bentuk log transaksi WAL. Log ini disiarkan secara asinkron atau sinkron ke seluruh edge node lainnya untuk memperbarui salinan database lokal mereka.
3. Perbandingan Komparatif / Analisis Pendekatan
Untuk membangun ekosistem database edge berbasis SQLite, terdapat beberapa pendekatan arsitektural yang umum digunakan di industri:
| Pendekatan / Teknologi | LiteFS (Fly.io) | Cloudflare D1 | rqlite (Distributed SQLite) | PostgreSQL Terpusat |
|---|---|---|---|---|
| Model Penyimpanan | FUSE Filesystem + WAL | Serverless Edge Storage | Raft Consensus Cluster | Managed Cloud Instance |
| Konsistensi Data | Eventual (dengan Read-After-Write Consistency) | Eventual Consistency | Strong Consistency (Raft) | Strong Consistency |
| Kompleksitas Setup | Menengah (Butuh konfigurasi volume) | Sangat Rendah (Fully Managed) | Tinggi (Butuh cluster manager) | Rendah |
| Konsumsi RAM & CPU | Sangat Efisien | Dikelola oleh Cloudflare | Menengah (Go runtime + Raft) | Tinggi |
| Portabilitas | Tinggi (Berjalan di Docker/K8s) | Terikat ekosistem Cloudflare | Tinggi (Standalone Go binary) | Terikat Provider Cloud |
- LiteFS sangat ideal bagi arsitektur berbasis wadah (container) seperti Fly.io di mana setiap VM memiliki disk lokal FUSE yang disinkronkan melalui penyimpanan objek S3 atau langsung antar-node.
- Cloudflare D1 adalah pilihan terbaik jika Anda murni menggunakan Cloudflare Workers tanpa ingin memikirkan manajemen infrastruktur disk sama sekali.
- rqlite menawarkan konsistensi kuat berbasis protokol Raft, namun mengorbankan sebagian kecepatan penulisan karena kuorum jaringan yang diperlukan.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum melakukan implementasi, pastikan lingkungan infrastruktur Anda memenuhi spesifikasi teknis berikut:
- Sistem Operasi: Linux Ubuntu 22.04 LTS / Debian 12 (untuk node edge mandiri) atau container environment.
- Runtime & Bahasa: Go 1.22+ (untuk utilitas manajemen kustom atau binari rqlite/LiteFS), Node.js 20+ / Bun.
- Dependency Sistem:
sqlite3,fuse3(jika menggunakan LiteFS),curl,jq,htop. - Izin & Keamanan: Akses
sudountuk konfigurasi filesystem mount, port terbuka untuk komunikasi antar-edge (biasanya port TCP 20202 untuk LiteFS / 4001 untuk rqlite).
Verifikasi Awal Sistem
Jalankan perintah berikut di terminal untuk memastikan pustaka SQLite dan FUSE tersedia:
# Verifikasi instalasi SQLite
sqlite3 --version
# Verifikasi pustaka FUSE untuk filesystem virtual
sudo apt-get update && sudo apt-get install -y sqlite3 libsqlite3-dev fuse3 libfuse3-dev
# Pastikan kernel mendukung FUSE
lsmod | grep fuse
5. Panduan Implementasi Langkah demi Langkah
Kita akan membangun implementasi nyata menggunakan LiteFS di atas infrastruktur server berbasis Linux untuk mendemonstrasikan replikasi SQLite multi-region.
Langkah 1: Struktur Direktori & Konfigurasi LiteFS
Buat direktori kerja di /etc/litefs dan buat file konfigurasi litefs.yml.
# /etc/litefs.yml
fusedir: "/litefs"
data-dir: "/var/lib/litefs"
# Pengaturan lease untuk menentukan Primary Node
lease:
type: "consul"
candidate: true
advertise-url: "http://192.168.1.10:20202"
consul:
url: "http://127.0.0.1:8500"
key: "litefs/my-global-app/lock"
# Pengaturan database yang akan direplikasi
databases:
- name: "production.db"
path: "/data/production.db"
backup:
url: "s3://my-edge-db-backups-bucket/production"
Langkah 2: Skema Database Inisialisasi (SQL)
Buat file inisialisasi skema SQL di /app/schema.sql.
-- /app/schema.sql
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
region TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
expires_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_sessions_expiry ON sessions(expires_at);
Langkah 3: Backend Aplikasi Node.js / Go Penghubung Database
Berikut adalah implementasi backend menggunakan Go yang terhubung ke SQLite lokal melalui LiteFS, dilengkapi routing otomatis untuk write ke primary dan read dari replica lokal.
// main.go
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"os"
_ "github.com/mattn/go-sqlite3"
)
var db *sql.DB
func main()
{
var err error
// Membuka database SQLite yang dikelola oleh LiteFS
dbPath := "/litefs/production.db"
db, err = sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
if err != nil {
log.Fatalf("Gagal membuka database: %v", err)
}
defer db.Close()
http.HandleFunc("/users", handleUsers)
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
log.Println("Server Edge berjalan pada port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
type User struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Region string `json:"region"`
}
func handleUsers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
rows, err := db.Query("SELECT id, email, name, region FROM users LIMIT 100")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var users []User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email, &u.Name, &u.Region); err != nil {
continue
}
users = append(users, u)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
case http.MethodPost:
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_, err := db.Exec("INSERT INTO users (id, email, name, region) VALUES (?, ?, ?, ?)", u.ID, u.email, u.name, u.region)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"status":"success\