☁️ Sesi 12 dari 16 — QA Cerdas

Keandalan Microservices & Cloud
SRE, Chaos Engineering & SLO

Sistem modern berjalan di ratusan microservices yang terdistribusi di cloud. Bagaimana memastikan keandalan ketika satu komponen gagal bisa merembet ke mana-mana?

💥 Chaos Engineering & Fault Injection
📍 SRE, SLI/SLO/SLA
🔗 Circuit Breaker & Resilience Patterns
3 × 50 menit
🏦 Bagian 1 — Microservices vs Monolith: Tantangan Keandalan

Ketika Aplikasi Pecah Menjadi Ratusan Layanan Kecil

💡 ANALOGI — Warung vs Mal Besar

Monolith = Warung Nasi: Satu tempat, semua ada — masak, bayar, meja di satu lokasi. Mudah dikelola, tapi kalau dapur kebakaran, seluruh warung tutup.

Microservices = Mal Besar: Ratusan tenant terpisah — restoran, bank, apotek, masing-masing independen. Kalau satu toko tutup, mal tetap buka. Tapi koordinasi antar tenant (parkir, listrik, keamanan) jauh lebih kompleks.

Tantangan keandalan microservices: kegagalan satu service bisa memicu cascade failure ke service lain yang bergantung padanya — seperti listrik mal mati total hanya karena satu tenant lupa bayar tagihan.

🪟 Monolith
  • Satu proses, satu deployment unit
  • Single point of failure — satu crash = semua down
  • Mudah di-test end-to-end secara lokal
  • Scaling harus keseluruhan, boros resource
  • Cocok untuk tim kecil dan awal project
Contoh: SIAKAD versi awal dengan satu file PHP besar yang handle semua fitur.
🆕 Microservices
  • Puluhan hingga ratusan service kecil yang independen
  • Fault isolation — satu service gagal, lainnya tetap jalan
  • Setiap service bisa di-scale secara independen
  • Testing lebih kompleks: integration, contract testing
  • Cocok untuk tim besar, sistem skala enterprise
Contoh: Gojek dengan service terpisah untuk driver, customer, payment, tracking, promo.

Tantangan Keandalan Khas Microservices

Cascade Failure
Service A memanggil B, B memanggil C. C timeout → B menunggu → A menunggu → semua request stuck.
Network Unreliability
Network antar container/pod bisa latency tinggi, packet loss, atau partisi. Tidak ada di monolith.
Distributed Tracing
Request melintasi 15 service — saat error, sulit tahu di service mana masalahnya tanpa tracing.
💥 Bagian 2 — Chaos Engineering

"Sengaja Merusak Sistem untuk Membuktikan Keandalannya"

💡 ANALOGI — Latihan Gempa di Gedung

Arsitektur gedung tahan gempa tidak cukup hanya di atas kertas. Insinyur melakukan simulasi gempa buatan untuk membuktikan gedung benar-benar tahan. Chaos Engineering adalah simulasi gempa untuk sistem software: kita sengaja mematikan server, memotong koneksi jaringan, atau menambahkan latency buatan — untuk membuktikan bahwa sistem tetap berfungsi dan memulihkan diri secara otomatis.

1

Define Steady State (Kondisi Normal)

Tentukan metrik "sistem berjalan normal": error rate <0.1%, latency p99 <500ms, availability >99.9%. Ini adalah baseline yang harus tetap terjaga selama eksperimen.

2

Hypothesis — "Sistem Akan Bertahan Jika..."

Buat hipotesis: "Jika payment service mati, checkout service akan fallback ke antrian dan transaksi tidak hilang." Hypothesis harus spesifik dan bisa diukur.

3

Inject Failure di Production (atau Staging)

Gunakan Chaos Monkey, Gremlin, atau LitmusChaos untuk inject failure: kill pod, tambah latency 500ms, simulasi disk full, matikan availability zone. Mulai dari scope kecil!

4

Observe & Compare dengan Steady State

Monitor apakah metrik menyimpang dari steady state. Apakah error rate naik? Apakah auto-recovery berjalan? Berapa lama sistem pulih (RTO)?

5

Fix Weaknesses & Iterate

Jika sistem gagal: perbaiki kelemahan (tambah retry, circuit breaker, fallback). Jika hipotesis terbukti: tingkatkan scope eksperimen. Dokumentasikan semua temuan sebagai runbook.

💥 Tools Chaos Engineering

  • Chaos Monkey (Netflix) — mematikan VM production secara acak, pioneer chaos engineering
  • Gremlin — platform enterprise, berbagai jenis attack: CPU, memory, network, disk
  • LitmusChaos — open source untuk Kubernetes, ChaosHub dengan ratusan eksperimen siap pakai
  • AWS Fault Injection Simulator — chaos eksperimen native di AWS, terintegrasi CloudWatch
  • Chaos Toolkit — open source Python-based, platform agnostic

🎯 Jenis Failure yang Diuji

  • Infrastructure chaos: kill VM/pod, shutdown AZ, disk failure
  • Network chaos: add latency, packet loss %, network partition
  • Application chaos: inject exception, memory leak, CPU hog
  • Dependency chaos: matikan database, third-party API timeout
  • State chaos: corrupt data, fill cache, exhaust connection pool

⚠️ Aturan emas Chaos Engineering: Selalu mulai dari staging, bukan production. Saat pertama kali di production, lakukan di jam traffic rendah dengan tim on-call siaga. Chaos Engineering bukan "sengaja merusak" tapi "mengontrol kondisi failure untuk belajar".

📍 Bagian 3 — Site Reliability Engineering (SRE) & SLO

Dari "Jangan Down" ke "Berapa Toleransi Down yang Kita Punya?"

💡 ANALOGI — Kontrak Penerbangan

Maskapai penerbangan tidak menjanjikan "tidak pernah terlambat" — itu mustahil. Mereka menjanjikan "on-time departure 85% dari penerbangan". Ini adalah SLO (Service Level Objective). SLA adalah kontrak hukumnya dengan konsekuensi jika dilanggar. SLI adalah ukuran aktualnya: berapa persen penerbangan benar-benar on-time bulan ini.

Filosofi Google SRE: 100% reliability is wrong target — sistem yang berusaha 100% uptime akan terlalu lambat berinovasi. Error budget memberikan tim kebebasan untuk deploy fitur baru selama masih dalam batas toleransi.

KonsepDefinisiContoh Nyata (SIAKAD ISTN)Siapa yang Ukur
SLI
Service Level Indicator
Metrik yang mengukur perilaku sistem secara kuantitatif% request SIAKAD yang direspons dalam 2 detik, diukur per 5 menitEngineering / Monitoring sistem otomatis
SLO
Service Level Objective
Target internal yang ingin dicapai berdasarkan SLI99% request SIAKAD harus respons <2 detik selama 30 hari rollingEngineering team — self-commitment
SLA
Service Level Agreement
Kontrak formal dengan konsekuensi jika SLO dilanggarISTN berkomitmen ke mahasiswa: SIAKAD tersedia 99.5% saat UTS/UAS berlangsungManagement / Legal — kontraktual
Error Budget
Anggaran Error
Sisa "jatah down" yang masih boleh terjadi dalam periode SLOSLO 99%: boleh down max 7.2 jam/bulan. Jika budget habis → freeze deploy baru.Engineering & Product team bersama

Error Budget — Filosofi Kunci SRE

SLO 99%
7.2 jam/bulan error budget
SLO 99.9%
43.8 menit/bulan error budget
SLO 99.99%
4.4 menit/bulan error budget
SLO 99.999%
26 detik/bulan error budget

Aturan Error Budget: Jika budget habis sebelum akhir bulan → tim harus menghentikan deploy fitur baru dan fokus pada reliability improvement. Budget tersisa = tim bebas berinovasi.

🔗 Bagian 4 — Resilience Patterns untuk Microservices

Pola Arsitektur untuk Sistem yang Tidak Mudah Roboh

💡 ANALOGI — MCB Listrik di Rumah

Rumah modern punya MCB (Miniatur Circuit Breaker) per ruangan — kalau ada korsleting di kamar tidur, MCB-nya trip dan hanya kamar itu yang mati, bukan seluruh rumah. Circuit Breaker pattern dalam microservices bekerja sama: kalau payment service gagal terus, circuit breaker "trip" dan request tidak lagi dikirim ke sana — mencegah cascade failure ke checkout service yang memanggil payment.

Circuit Breaker — 3 State

CLOSED
Request berjalan normal ke service downstream. Error rate dipantau terus.
OPEN
Error rate melebihi threshold. Semua request langsung dikembalikan (fail-fast). Tidak ada beban ke service yang sudah sakit.
🕐
HALF-OPEN
Setelah timeout, coba kirim sebagian request. Jika berhasil → kembali CLOSED. Jika gagal → kembali OPEN.
🔄
Retry with Backoff
Coba ulang request yang gagal dengan jeda waktu yang semakin lama (exponential backoff). Hindari retry storm yang memperparah server yang sudah kelebihan beban.
🎲
Bulkhead
Isolasi resource pool per service. Seperti sekat kapal: bocor di satu kompartemen tidak menenggelamkan keseluruhan. Batasi thread pool per dependency.
🗑️
Fallback
Saat service gagal, kembalikan nilai default, cache lama, atau respons degraded. Lebih baik data lama daripada error 500 ke pengguna.
📋
Timeout
Setiap network call harus punya batas waktu. Jangan biarkan request hang selamanya. Tanpa timeout, satu service lambat bisa menghabiskan semua thread.
😁
Health Check
Setiap service expose endpoint /health. Load balancer secara berkala memeriksa — jika tidak sehat, traffic tidak dirouting ke sana.
🏭
Rate Limiting
Batasi jumlah request per klien per waktu. Mencegah satu klien "memakan" semua kapasitas sistem dan menyebabkan degradasi untuk semua pengguna.
🔭 Bagian 5 — Observability: Tiga Pilar

Sistem yang Bisa Menjelaskan Kondisinya Sendiri

💡 ANALOGI — Pilot vs Supir Becak

Supir becak tahu kondisi kendaraannya dari feeling — kurang lebih. Pilot pesawat modern punya ratusan instrumen yang memberikan data real-time: ketinggian, kecepatan, bahan bakar, suhu mesin, setiap sensor aktif. Observability adalah mengubah sistem software dari "supir becak" menjadi "pilot" — kita bisa tahu persis apa yang sedang terjadi di dalam sistem kapan saja.

📈
Metrics
Data numerik yang dikumpulkan secara periodik: CPU, memory, request rate, error rate, latency. Efisien untuk alerting dan dashboard real-time.
Tools: Prometheus, Datadog, CloudWatch
📋
Logs
Catatan terstruktur/tidak terstruktur tentang event yang terjadi. Kaya konteks, cocok untuk debugging — tapi mahal di storage dan processing.
Tools: ELK Stack, Loki, Splunk, Fluentd
📊
Traces
Rekaman perjalanan satu request melintas semua service (distributed tracing). Sangat penting untuk debug performance di microservices.
Tools: Jaeger, Zipkin, OpenTelemetry, AWS X-Ray

📌 OpenTelemetry adalah standar terbuka untuk instrumentasi observability. Dengan satu SDK, data Metrics + Logs + Traces bisa dikirim ke berbagai backend (Jaeger, Prometheus, Datadog) tanpa vendor lock-in. Ini standar de-facto industri saat ini.

🐍 Bagian 6 — Praktik: Simulasi Circuit Breaker & SLO Tracker

Implementasi Resilience Pattern & Error Budget

Circuit Breaker + SLO/Error Budget Tracker Simulation Python · dataclasses · statistics
# ================================================================
# S11409 - Sesi 12: Circuit Breaker & SLO Error Budget
# Dosen: Riadi Marta Dinata, S.Ti., M.Kom. | ISTN Jakarta
# ================================================================

import time, random, statistics
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Callable

# ── CIRCUIT BREAKER IMPLEMENTATION ───────────────────────────
class CBState(Enum):
    CLOSED   = "CLOSED"    # Normal: request berjalan
    OPEN     = "OPEN"      # Gagal: request langsung ditolak
    HALF_OPEN = "HALF_OPEN" # Percobaan: tes apakah service pulih

@dataclass
class CircuitBreaker:
    name: str
    failure_threshold: int = 5      # Gagal berturut → OPEN
    recovery_timeout:  int = 10     # Detik sebelum coba HALF_OPEN
    success_threshold: int = 2      # Sukses berturut di HALF_OPEN → CLOSED

    _state: CBState = field(default=CBState.CLOSED, init=False)
    _failure_count: int = field(default=0, init=False)
    _success_count: int = field(default=0, init=False)
    _last_failure_time: float = field(default=0.0, init=False)
    _call_count: int = field(default=0, init=False)

    @property
    def state(self): return self._state

    def call(self, func: Callable, *args, **kwargs):
        self._call_count += 1

        if self._state == CBState.OPEN:
            elapsed = time.time() - self._last_failure_time
            if elapsed < self.recovery_timeout:
                return None, "CIRCUIT_OPEN — Request ditolak (fail-fast)"
            else:
                self._state = CBState.HALF_OPEN
                self._success_count = 0
                print(f"  [CB:{self.name}] OPEN → HALF_OPEN (mencoba recovery)")

        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result, "OK"
        except Exception as e:
            self._on_failure()
            return None, f"FAILED: {e}"

    def _on_success(self):
        self._failure_count = 0
        if self._state == CBState.HALF_OPEN:
            self._success_count += 1
            if self._success_count >= self.success_threshold:
                self._state = CBState.CLOSED
                print(f"  [CB:{self.name}] HALF_OPEN → CLOSED (service pulih!)")

    def _on_failure(self):
        self._failure_count += 1
        self._last_failure_time = time.time()
        if self._failure_count >= self.failure_threshold:
            if self._state != CBState.OPEN:
                self._state = CBState.OPEN
                print(f"  [CB:{self.name}] CLOSED → OPEN (terlalu banyak error!)")

# ── SIMULASI PAYMENT SERVICE ──────────────────────────────────
def payment_service(amount, service_healthy=True):
    """Simulasi: service sehat vs sakit"""
    if not service_healthy:
        raise ConnectionError("Payment gateway timeout!")
    if amount <= 0:
        raise ValueError("Amount must be positive")
    return {"status": "SUCCESS", "amount": amount,
            "tx_id": f"TX-{random.randint(10000,99999)}"}

print("=" * 60)
print("    CIRCUIT BREAKER SIMULATION")
print("    S11409 ISTN Jakarta")
print("=" * 60)

cb = CircuitBreaker("payment-svc", failure_threshold=3, recovery_timeout=2)

# Fase 1: Service normal
print("\n--- Fase 1: Service Normal (5 request) ---")
for i in range(5):
    result, status = cb.call(payment_service, 100_000, service_healthy=True)
    print(f"  Request {i+1}: {status} | CB State: {cb.state.value}")

# Fase 2: Service mulai gagal
print("\n--- Fase 2: Service Gagal (5 request) ---")
for i in range(5):
    result, status = cb.call(payment_service, 100_000, service_healthy=False)
    print(f"  Request {i+1}: {status} | CB State: {cb.state.value}")

# Fase 3: Tunggu recovery timeout
print("\n--- Fase 3: Menunggu recovery timeout (2 detik)... ---")
time.sleep(2.1)

# Fase 4: Coba kembali (HALF_OPEN)
print("\n--- Fase 4: Coba kembali setelah timeout ---")
for i in range(4):
    result, status = cb.call(payment_service, 100_000, service_healthy=True)
    print(f"  Request {i+1}: {status} | CB State: {cb.state.value}")


# ── SLO ERROR BUDGET TRACKER ──────────────────────────────────
@dataclass
class SLOTracker:
    service_name: str
    slo_target: float = 0.999   # 99.9%
    window_days: int = 30

    _requests: List[dict] = field(default_factory=list, init=False)

    def record(self, success: bool, latency_ms: float):
        self._requests.append({
            "success": success, "latency": latency_ms,
            "timestamp": time.time()
        })

    @property
    def sli(self):
        if not self._requests: return 1.0
        return sum(1 for r in self._requests if r["success"]) / len(self._requests)

    @property
    def error_budget_total(self):
        """Total detik error budget dalam window"""
        return (1 - self.slo_target) * self.window_days * 24 * 3600

    @property
    def error_budget_used(self):
        """Perkiraan detik yang sudah terpakai"""
        error_rate = 1 - self.sli
        return error_rate * self.window_days * 24 * 3600

    @property
    def error_budget_remaining_pct(self):
        total = self.error_budget_total
        if total == 0: return 0
        used = min(self.error_budget_used, total)
        return max(0, (total - used) / total * 100)

    def report(self):
        latencies = [r["latency"] for r in self._requests]
        print(f"\n  Service  : {self.service_name}")
        print(f"  Total req: {len(self._requests)}")
        print(f"  SLI      : {self.sli:.4%}")
        print(f"  SLO Tgt  : {self.slo_target:.4%}")
        status = "OK" if self.sli >= self.slo_target else "VIOLATED"
        print(f"  SLO Status: {status}")
        print(f"  Error Budget Total    : {self.error_budget_total/60:.1f} menit/bulan")
        print(f"  Error Budget Used     : {self.error_budget_used/60:.1f} menit/bulan")
        print(f"  Error Budget Remaining: {self.error_budget_remaining_pct:.1f}%")
        if latencies:
            p50 = statistics.median(latencies)
            p99 = sorted(latencies)[int(len(latencies)*0.99)]
            print(f"  Latency p50: {p50:.0f}ms | p99: {p99:.0f}ms")
        if self.error_budget_remaining_pct < 20:
            print("  !! WARNING: Error budget hampir habis — tahan deploy baru!")

print("\n\n" + "=" * 60)
print("    SLO ERROR BUDGET TRACKER — SIAKAD ISTN")
print("=" * 60)

tracker = SLOTracker("siakad-api", slo_target=0.999)
random.seed(42)

# Simulasi 1000 request (99.2% success rate — di bawah SLO!)
for _ in range(1000):
    success  = random.random() > 0.008   # 0.8% error rate
    latency  = random.gauss(120, 40) if success else random.gauss(4500, 500)
    tracker.record(success, max(10, latency))

tracker.report()
print("\nDone! Gunakan sebagai baseline monitoring SIAKAD.")
KUIS SESI 12

Uji Pemahaman Anda

Soal 1 — Cascade Failure

SIAKAD ISTN memiliki 4 service: Auth, KRS, Nilai, dan Payment. KRS memanggil Auth untuk validasi token, Payment juga memanggil Auth. Jika Auth service down 100%: (a) Gambarlah alur cascade failure, (b) Resilience pattern apa yang bisa mencegahnya? (c) Bagaimana fallback yang tepat untuk masing-masing service?

Soal 2 — Chaos Engineering

Anda menjadi SRE di startup fintech. Buat rencana Chaos Engineering untuk menguji ketahanan payment service: (a) Definisikan steady state yang terukur, (b) Buat 3 hipotesis chaos experiment, (c) Jenis failure apa yang akan di-inject, dan (d) Apa kriteria "eksperimen berhasil" vs "gagal"?

Soal 3 — SLO dan Error Budget

Aplikasi e-learning ISTN memiliki SLO: 99.5% availability per bulan. Dalam bulan berjalan terjadi 2 insiden: (a) down 45 menit karena maintenance, (b) down 2 jam karena bug deployment. (a) Hitung total error budget yang terpakai dalam menit, (b) Hitung sisa error budget dan persentasenya, (c) Apakah tim masih boleh deploy fitur baru minggu ini?

Soal 4 — Circuit Breaker State

Circuit Breaker payment service dikonfigurasi: failure_threshold=5, recovery_timeout=30s, success_threshold=2. Jelaskan secara detail state transitions yang terjadi jika: request 1-3 sukses, request 4-8 gagal semua, lalu diam 35 detik, lalu request 9-12 berhasil semua. Di state apa CB akhirnya?

Soal 5 — Python

Modifikasi SLOTracker di atas: (a) tambahkan SLI latency — "99% request harus selesai dalam 500ms", (b) implementasikan burndown alert — jika error budget akan habis dalam 7 hari berdasarkan laju saat ini, kirim peringatan, (c) tambahkan visualisasi matplotlib untuk error budget burndown selama 30 hari.

Rangkuman Kunci Sesi 12

Microservices memberikan fault isolation tapi menciptakan tantangan baru: cascade failure, network unreliability, dan distributed tracing

Chaos Engineering bukan merusak sistem — tapi membuktikan keandalan dengan mengontrol kondisi failure secara terencana

SLO + Error Budget memberikan framework objektif: kapan tim boleh berinovasi vs kapan harus fokus reliability

Circuit Breaker mencegah cascade failure dengan 3 state: CLOSED (normal) → OPEN (fail-fast) → HALF_OPEN (recovery probe)

Tiga pilar observability: Metrics (apa yang terjadi), Logs (mengapa terjadi), Traces (di mana terjadi dalam distributed system)

SLO 100% adalah target yang salah — sistem yang berusaha 100% uptime terlalu lambat berinovasi. Error budget adalah keseimbangan antara reliability dan velocity.