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.
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]
Dua konsekuensi yang perlu Anda sebut sendiri sebelum interviewer menanyakannya [2]:
- Duplikat. Relay bisa crash setelah publish tapi sebelum menandai baris terkirim, sehingga event dikirim lagi. AWS: "make the consuming service idempotent by tracking the processed messages" (lesson 0017).
- Urutan. Kirim event dalam urutan update database; AWS menyimpan timestamp dan sequence number di baris outbox untuk itu.
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 yang benar. Alarm DLQ datang terlambat; Amazon "focus more on measuring the age of messages that are in the queue." [4]
- Batasi kerja yang sedang berjalan di consumer. RabbitMQ memakai prefetch count: saat jumlah pesan belum di-ack mencapai batas, RabbitMQ "will stop delivering more messages on the channel until at least one of the outstanding ones is acknowledged." [5]
- Kirim back-pressure ke producer. Kalau satu workload membuat backlog yang tidak terkejar, banyak sistem Amazon "automatically start rejecting work more aggressively in the producer", misalnya dengan menurunkan batas throttle sebanding dengan ukuran backlog. Di HTTP, bentuknya biasanya
429 Too Many Requests(rate limiting di lesson 0021). [4] - Buang atau sisihkan yang basi. Untuk pesan yang tidak berguna lagi kalau terlambat, AWS memakai TTL pesan atau memindahkan traffic lama ke queue terpisah supaya traffic baru tetap cepat. [4]
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:
- Panggilan langsung kalau hanya satu layanan yang perlu tahu dan ia cepat serta stabil.
- Satu transaksi database kalau semua efek (pesanan, stok) ada di database yang sama: tidak ada dual write, tidak perlu outbox.
- Tabel job di database (lesson 0016) sebagai queue sekaligus outbox, sebelum menambah broker.
- Perbaiki kapasitas dulu (index, batching tulis, scale vertical) kalau rata-rata beban, bukan hanya puncaknya, sudah melebihi kemampuan consumer. Queue tidak menambah kapasitas.
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.
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)