Lesson 0018 · System Design · Modul Asinkron · ±20 menit

Event-driven, outbox pattern, back-pressure, load leveling

Cara mengirim event tanpa kehilangannya, dan apa yang terjadi saat producer lebih cepat daripada consumer

Sebelumnya, lesson 0017: anggap pesan at-least-once dan buat consumer idempotent. Lesson terakhir modul Asinkron ini menjawab dua pertanyaan sisanya: bagaimana event sampai ke broker dengan aman dari database, dan bagaimana sistem tidak tenggelam saat consumer tertinggal.

Target lesson ini: Anda bisa menjawab "Saat pesanan dibuat, layanan email, stok, dan analitik harus tahu. Bagaimana Anda memastikan event tidak hilang, dan apa yang terjadi kalau consumer tertinggal?" tanpa catatan, dalam 60 detik: event-driven, masalah dual write, outbox dalam satu transaksi, consumer idempotent, load leveling, dan back-pressure berdasarkan umur pesan.

1. Event-driven secara singkat

Arsitektur event-driven terdiri dari "event producers that generate a stream of events, event consumers that listen for these events, and event channels" (biasanya broker) di antaranya. Producer "doesn't know which consumers are listening", dan "new consumers can be added without modifying producers or other consumers." [1]

Event biasanya ditulis dalam bentuk lampau: OrderPlaced, PaymentCaptured. Bedanya dengan command (SendEmail): command meminta satu penerima melakukan sesuatu, event mengumumkan fakta ke siapa pun yang peduli. Harganya, menurut dokumen yang sama: data antarlayanan "isn't immediately consistent after an event is published" (eventual consistency), dan observability lebih sulit karena tidak ada satu call stack, jadi tracing harus direncanakan dari awal. [1]

2. Dual write dan outbox pattern

Kode yang terlihat wajar: simpan pesanan ke database, lalu publish OrderPlaced ke broker. Kalau broker down setelah commit database, pesanan ada tapi event hilang; kalau urutannya dibalik, event terkirim untuk pesanan yang mungkin rollback. AWS menyebut ini dual write: "when an application writes to two different systems", misalnya database dan sistem pesan. [2]

Transactional outbox menyelesaikannya dengan memakai satu-satunya transaksi yang Anda punya, yaitu transaksi database. Tabel bisnis dan tabel outbox di-update "in the same transaction"; kalau salah satu gagal, "the entire transaction is rolled back." Proses terpisah (sering disebut relay) membaca baris outbox yang sudah commit dan mengirimnya ke broker. [2]

Client Order service tabel orders tabel outbox Relay Broker Email Stok satu transaksi polling / CDC
Garis penuh = sinkron, di dalam satu transaksi database. Garis putus-putus = asinkron, setelah commit.

Dua konsekuensi yang perlu Anda sebut sendiri sebelum interviewer menanyakannya [2]:

Selain polling tabel outbox, relay bisa memakai change data capture (CDC): membaca log perubahan database (mis. WAL di PostgreSQL) dan mengubahnya menjadi event. Lebih sedikit beban polling, tapi satu komponen lagi.

Latihan 1 — Dual write dan outbox

Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review 0026.

Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil review 0026.

3. Load leveling: queue sebagai buffer

Queue-based load leveling memakai queue "that acts as a buffer between a task and the service that it invokes", untuk "smooth intermittent heavy loads". Contoh: flash sale 2.000 request/detik selama 5 menit, sementara database hanya sanggup 500 tulis/detik. Request ditaruh di queue, worker menulis dengan kecepatan yang aman, dan backlog habis setelah lonjakan reda. Pola ini "might not be suitable when the caller requires a low-latency, synchronous response." [3]

Kata kuncinya intermittent. Hitung: kalau rata-rata kedatangan 1.000 pesan/detik dan consumer hanya 200 pesan/detik terus-menerus, backlog tumbuh 800 pesan/detik, sekitar 2,9 juta pesan per jam. Queue tidak menyelesaikan itu; queue hanya menunda masalah sambil latency naik. Azure juga mengingatkan bahwa menambah consumer tanpa batas "only moves the overload to downstream dependencies", jadi pantau kedalaman queue dan skalakan consumer dalam batas aman. [3]

4. Back-pressure

Back-pressure artinya sinyal "pelan-pelan" mengalir ke hulu, dari consumer yang kewalahan ke producer. AWS Builders' Library menjelaskan kenapa ini penting: sistem berbasis queue punya perilaku bimodal; tanpa backlog latency rendah, tapi setelah backlog terbentuk butuh waktu lama untuk kembali normal. Contoh mereka: kalau butuh sekitar 30 menit sampai operator turun tangan, dan pesan yang terkumpul 10× kapasitas consumer, backlog itu butuh 300 menit untuk dihabiskan. [4]

ukur umur pesan tertua→ batasi in-flight di consumer (prefetch)→ tambah consumer dalam batas downstream→ throttle / tolak di producer (429)

Latihan 2 — Load leveling dan back-pressure

Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review 0026.

5. Kapan tidak perlu

Sinyal yang membenarkan: event-driven layak kalau satu kejadian ditunggu oleh beberapa layanan milik tim berbeda dan daftar penerimanya terus bertambah. Outbox layak begitu Anda punya database dan broker, dan kehilangan event berarti data tidak konsisten (stok tidak berkurang, email konfirmasi tidak terkirim). Load leveling layak kalau lonjakan puncak (angka dari estimasi, lesson 0004) melebihi kapasitas tulis downstream, tapi rata-ratanya masih di bawah.

Biaya dan risiko: eventual consistency yang harus dijelaskan ke product owner; debugging dan tracing lintas layanan; relay outbox yang harus dijalankan dan dipantau; tabel outbox yang tumbuh dan perlu dibersihkan; dan backlog yang bisa menyembunyikan masalah kapasitas sampai latency melonjak.

Alternatif lebih murah:

Latihan lisan — Ucapkan dengan suara keras

Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Saat pesanan dibuat, layanan email, stok, dan analitik harus tahu. Bagaimana Anda memastikan event tidak hilang, dan apa yang terjadi kalau consumer tertinggal?" (60 detik)
Bandingkan dengan jawaban contoh dan checklist-nya

"Saya publish event OrderPlaced lewat pub/sub, jadi checkout tidak perlu tahu siapa saja yang mendengarkan. Supaya tidak ada dual write, pesanan dan baris outbox ditulis dalam satu transaksi database; relay terpisah membaca outbox yang sudah commit dan publish ke broker. Relay bisa mengirim duplikat, jadi setiap consumer idempotent berdasarkan event ID. Kalau consumer tertinggal, queue meratakan lonjakan, tapi kalau rata-rata kedatangan melebihi kapasitas, backlog tumbuh terus. Saya pantau umur pesan tertua, bukan hanya DLQ, lalu tambah consumer dalam batas yang sanggup ditanggung database, atau kirim back-pressure ke producer, misalnya throttle atau 429. Kalau hanya satu layanan yang perlu tahu dan volumenya kecil, panggilan langsung atau job di tabel database lebih sederhana."

Checklist: apakah Anda menyebut…

  • Event-driven/pub/sub: producer tidak tahu siapa consumer-nya
  • Menyebut masalah dual write antara database dan broker
  • Outbox: data + baris outbox dalam satu transaksi, relay publish setelah commit
  • Consumer idempotent karena relay bisa mengirim duplikat
  • Back-pressure/load leveling: pantau umur pesan atau backlog, tambah consumer atau throttle producer
  • Membenarkan komponen dengan angka, atau menyebut alternatif lebih murah

Lulus jika minimal 4 dari 6.

Bacaan lanjutan (opsional, tidak dihitung dalam durasi) AWS Builders' Library: Avoiding insurmountable queue backlogs, ±20 menit. Pengalaman Amazon menjalankan sistem asinkron multitenant: umur pesan, spillover queue, back-pressure.

Berikutnya, lesson 0019: Review 2 untuk modul Data dan Caching. Modul Asinkron ini diuji di Review 3 (lesson 0026), bersama modul Reliabilitas & API.

Istilah: GLOSSARY.md (bagian "Asinkron") · Indeks: semua lesson System Design

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "Bandingkan outbox dengan polling dan outbox dengan CDC.", "Bagaimana back-pressure bekerja kalau producer-nya adalah browser user?"

Sumber: [1] Azure Architecture Center: Event-driven architecture style · [2] AWS Prescriptive Guidance: Transactional outbox pattern · [3] Azure Architecture Center: Queue-Based Load Leveling pattern · [4] AWS Builders' Library: Avoiding insurmountable queue backlogs · [5] RabbitMQ: Consumer Acknowledgements and Publisher Confirms (prefetch)