L
Microservices • Literally Web ID

Desain Sistem Terdistribusi Skala Tinggi Menggunakan Pola CQRS & Event Sourcing

⏱️ 15 menit baca • 📅 14 Oct 2026 • 👁️ 1 views
Desain Sistem Terdistribusi Skala Tinggi Menggunakan Pola CQRS & Event Sourcing

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap arsitektur sistem terdistribusi modern, aplikasi dengan skala perusahaan (enterprise-scale) sering kali dihadapkan pada tantangan beban operasional yang asimetris. Operasi Write (perintah untuk mengubah state data) memiliki karakteristik latensi, kebutuhan konsistensi transaksi (ACID), serta perlindungan integritas bisnis yang ketat. Sebaliknya, operasi Read (kueri untuk mengambil data) menuntut throughput tinggi, latensi sub-milidetik, kemampuan agregasi kompleks, serta skalabilitas horisontal yang masif.

Bottleneck Performa pada Arsitektur Monolitik Tradisional

Pendekatan tradisional menggunakan CRUD Architecture di mana satu skema database relasional (seperti MySQL atau PostgreSQL) melayani operasi baca dan tulis secara bersamaan, menciptakan bottleneck fatal pada level:

  1. Locking & Contention: Transaksi tulis (INSERT, UPDATE, DELETE) memicu row-level atau table-level locking. Hal ini mencekik query baca yang kompleks, menyebabkan connection pool exhaustion di sisi aplikasi web backend (Node.js/Go/PHP-FPM).
  2. Object-Relational Impedance Mismatch: Model data yang dioptimalkan untuk normalisasi penulisan (3NF) sangat tidak efisien ketika dieksekusi untuk kebutuhan antarmuka frontend (SPA/SSR) yang membutuhkan data terdenormalisasi (nested JSON).
  3. Scaling Asymmetry: Anda tidak dapat menskalakan infrastruktur database secara independen. Ketika trafik pembacaan melonjak akibat kampanye pemasaran, Anda terpaksa menaikkan spesifikasi server database tulis secara vertikal yang berbiaya sangat tinggi.

Perbandingan Kondisi: Sebelum vs Sesudah CQRS & Event Sourcing

Parameter Sebelum (Traditional CRUD + Shared DB) Sesudah (CQRS + Event Sourcing + Kafka)
Skalabilitas Terikat pada kapasitas maksimum satu instance database master. Skala tulis dan baca terpisah total; read model dapat direplikasi secara tak terbatas.
Auditing & History Rentan kehilangan jejak audit; UPDATE menimpa data lama (destruktif). Imutabilitas mutlak; setiap perubahan disimpan sebagai event kronologis (append-only log).
Kompleksitas Rendah pada fase awal, namun melambat secara eksponensial seiring pertumbuhan fitur. Tinggi pada fase awal setup, namun sangat modular dan mudah dikembangkan untuk jangka panjang.
Konsistensi Konsistensi kuat (Strong Consistency) secara default, namun mengorbankan ketersediaan (Availability).
Ketersediaan tinggi (High Availability) dengan konsistensi eventual (Eventual Consistency).

[!WARNING] Jangan menerapkan CQRS dan Event Sourcing pada aplikasi skala kecil atau domain sederhana (CRUD murni). Pola ini memperkenalkan kompleksitas operasional yang masif. Gunakan hanya jika domain bisnis Anda kompleks, membutuhkan audit trail permanen, atau mengalami ketimpangan ekstrem antara beban baca dan tulis.


2. Konsep Inti & Deep Dive Arsitektur

CQRS (Command Query Responsibility Segregation) memisahkan model yang memperbarui data (Command) dari model yang membaca data (Query). Ketika dikombinasikan dengan Event Sourcing, perubahan state tidak disimpan sebagai snapshot data mentah, melainkan sebagai urutan kejadian (immutable events) yang terjadi di masa lalu.

Alur Kerja di Balik Layar (Under the Hood)

  1. Command Phase: Klien mengirimkan HTTP POST/PUT ke API Gateway. Backend memvalidasi perintah berdasarkan aturan bisnis (Domain Invariants) menggunakan state saat ini yang direkonstruksi dari Event Store.
  2. Event Emission: Jika valid, domain menghasilkan event baru (misalnya, OrderPlaced). Event ini disimpan secara append-only ke dalam Apache Kafka.
  3. Event Broker: Kafka mendistribusikan event tersebut ke berbagai Consumer Group.
  4. Projection Phase: Layanan pemroses asinkron membaca event dari Kafka, memperbarui Read Model (disimpan di Redis/Elasticsearch/MySQL terpisah) yang dioptimalkan khusus untuk kueri UI.
  5. Query Phase: Klien membaca data langsung dari Read Model dengan latensi minimal.
[Client / Frontend]
       │
       ├──────────────► (POST /commands) ──────────────┐
       │                                             ▼
       │                                    [Command Service]
       │                                             │
       │                                             ▼
       │                                    [Event Store / Kafka]
       │                                             │
       │                     ┌───────────────────────┴───────────────────────┐
       │                     ▼                                               ▼
       │             [Async Projection Worker 1]                     [Async Projection Worker 2]
       │                     │                                               │
       │                     ▼                                               ▼
       │             [Read Model DB (Redis)]                        [Read Model DB (Elasticsearch)]
       │                     │                                               │
       └────── (GET /queries) ───────────────────────────────────────────────┘

3. Perbandingan Komparatif / Analisis Pendekatan

Dalam membangun infrastruktur penyimpanan event dan read model, arsitek sering kali harus memilih antara beberapa teknologi utama. Berikut adalah matriks perbandingan mendalam:

Teknologi Event Store Kelebihan Kekurangan Konsumsi Sumber Daya (CPU/RAM/IO)
Apache Kafka Throughput masif, partisioning handal, log retention jangka panjang, ekosistem luas. Kompleksitas operasional Zookeeper/Kraft, kurva belajar curam. Tinggi pada RAM dan Disk IOPS; membutuhkan minimal 3 node cluster untuk produksi.
EventStoreDB Dibangun khusus untuk Event Sourcing, mendukung Projections native, API kuat. Kurang populer untuk kasus penggunaan umum di luar ES, komunitas lebih kecil. Sedang pada CPU; sangat bergantung pada kecepatan disk (NVMe).
RabbitMQ Mudah di-setup, routing fleksibel (AMQP), bagus untuk task queue tradisional. Bukan event store sejati; pesan dihapus setelah dikonsumsi (butuh konfigurasi stream). Sedang pada RAM, konsumsi CPU moderat.

4. Prasyarat Sistem & Persiapan Lingkungan

Untuk menjalankan arsitektur ini di lingkungan produksi berbasis Linux (Ubuntu 22.04 LTS / Debian 12), pastikan spesifikasi minimal server terpenuhi:

  • CPU: Minimal 4 vCore
  • RAM: Minimal 16 GB (Kafka dan JVM membutuhkan alokasi memori yang stabil)
  • Storage: SSD NVMe minimal 100 GB (untuk mengimbangi I/O log append)
  • Software Stack: Docker & Docker Compose, Go 1.22+, Apache Kafka, Redis, Nginx sebagai Reverse Proxy.

Konfigurasi Kernel Linux untuk High Concurrency (/etc/sysctl.conf)

Terapkan penyesuaian sistem operasi berikut untuk menangani ribuan koneksi jaringan dan buffer I/O:

# /etc/sysctl.conf - Optimasi Sistem untuk High Throughput Kafka & Backend
fs.file-max = 2097152
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 1024 65535
vm.swappiness = 10
vm.max_map_count = 524288

Jalankan perintah berikut untuk menerapkan perubahan:

sysctl -p

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Setup Infrastruktur dengan Docker Compose

Buat file docker-compose.yml untuk menjalankan Zookeeper, Kafka, Redis, dan Aplikasi Go backend:

version: '3.8'

services:
  zookeeper:
  image: confluentinc/cp-zookeeper:7.5.0
  container-name: zookeeper
  environment:
    ZOOKEEPER_CLIENT_PORT: 2181
    ZOOKEEPER_TICK_TIME: 2000
  networks:
    - cqrs-net

  kafka:
  image: confluentinc/cp-kafka:7.5.0
  container-name: kafka
  depends_on:
    - zookeeper
  ports:
    - "9092:9092"
  environment:
    KAFKA_BROKER_ID: 1
    KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
    KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://kafka:29092
    KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT
    KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT_INTERNAL
    KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
    KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
  networks:
    - cqrs-net

  redis:
  image: redis:7.2-alpine
  container-name: redis_read_model
  ports:
    - "6379:6379"
  networks:
    - cqrs-net

networks:
  cqrs-net:
  driver: bridge

Langkah 2: Implementasi Command & Event Store Publisher (Go)

Buat file main.go yang bertindak sebagai Command Handler dan mempublikasikan event ke Kafka:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/IBM/sarama"
	"github.com/google/uuid"
)

type CreateOrderCommand struct {
	CustomerID string  `json:"customer_id"`
	Amount     float64 `json:"amount"`
}

type OrderCreatedEvent struct {
	EventID     string    `json:"event_id"`
	AggregateID string    `json:"aggregate_id"`
	CustomerID  string    `json:"customer_id"`
	Amount      float64   `json:"amount"`
	CreatedAt   time.Time `json:"created_at"`
}

var producer sarama.SyncProducer

func initKafka() {
	config := sarama.NewConfig()
	config.Producer.Return.Successes = true
	config.Producer.RequiredAcks = sarama.WaitForAll
	config.Producer.Retry.Max = 5

	var err error
	producer, err = sarama.NewSyncProducer([]string{"localhost:9092\
🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server