1. Pertanyaan
Ketika sebuah pesanan dibuat, layanan pesanan harus melakukan commit status dan memublikasikan event OrderCreated untuk konsumen inventaris dan notifikasi. Basis data dan broker tidak memiliki two-phase commit bersama. Rancang transactional outbox agar crash pada proses tidak menghilangkan event secara diam-diam, sementara pengiriman duplikat dan backlog relay tetap dapat dikelola.
2. Batasan dan klarifikasi
- Data pesanan dan tabel outbox berbagi satu basis data transaksional lokal.
- Broker menyediakan pengiriman at-least-once, tanpa pengurutan global atau pengiriman transaksional.
- Konsistensi eventual dapat diterima; konsumen inventaris harus idempoten.
- Jelaskan pengurutan per-agregat, apakah pengurutan lintas-agregat diperlukan, serta jendela retensi/penghapusan.
3. Pendekatan inti
Tulis perubahan pesanan dan satu baris outbox dalam transaksi basis data yang sama. Baris tersebut memuat event_id unik, kunci agregat, tipe event, urutan, payload, waktu pembuatan, dan status publikasi. Commit yang berhasil membuat data bisnis dan event yang tertunda menjadi durable; rollback tidak mengekspos keduanya, sehingga menghilangkan jendela dual-write di tingkat aplikasi.
Relay independen melakukan polling atau berlangganan ke outbox, memublikasikan ke broker, lalu menandai baris telah terkirim. Jika proses mengalami crash antara penerimaan acknowledgement broker dan pembaruan status, event dapat dipublikasikan kembali. Oleh karena itu, konsumen melakukan deduplikasi berdasarkan event_id alih-alih mengasumsikan pengiriman exactly-once.
4. Implementasi referensi
createOrder(command):
begin transaction
order = insert orders(...)
event = insert outbox(
event_id=uuid(), aggregate_id=order.id,
aggregate_version=order.version, type="OrderCreated",
payload=serialize(order), status="pending"
)
commit
return order.id
relayBatch():
rows = select pending outbox rows
order by aggregate_id, aggregate_version, created_at
for update skip locked limit BATCH_SIZE
for row in rows:
try:
broker.publish(key=row.aggregate_id, id=row.event_id, body=row.payload)
mark_sent(row.event_id) // conditional update
except transient_error:
increment_attempts_and_schedule_retry(row.event_id)
consume(message):
begin transaction
inserted = insert processed_messages(message.id) on conflict do nothing
if inserted:
apply_business_change(message)
commit5. Keandalan dan kebenaran
Jika transaksi bisnis berhasil di-commit tetapi relay mengalami crash sebelum memublikasikan, baris yang tertunda akan ditemukan pada pemindaian berikutnya. Jika publikasi berhasil tetapi pembaruan status mengalami crash, putaran berikutnya akan memublikasikannya kembali. Oleh karena itu, semantik end-to-end adalah at-least-once; tabel deduplikasi konsumen atau kunci idempotensi bisnis harus berbagi transaksi dengan pembaruan bisnis milik konsumen.
Pengurutan per-agregat dapat menggunakan versi monotonik dan partisi berdasarkan kunci agregat; jangan menjanjikan pengurutan global lintas agregat. Kolom SELECT ... FOR UPDATE SKIP LOCKED atau lease mencegah beberapa relay mengklaim baris yang sama, tetapi keduanya tidak menggantikan idempotensi konsumen. Beri indeks pada status, waktu percobaan ulang, dan waktu pembuatan, lalu arsipkan atau hapus baris lama secara aman untuk membatasi pertumbuhan tabel.
6. Tindak lanjut dan jebakan
- Menghapus baris segera setelah publikasi yang "berhasil" dapat menimbulkan celah yang tidak dapat dipulihkan jika acknowledgement hilang; simpan status pengiriman atau pertahankan catatan audit terlebih dahulu.
- Timeout acknowledgement broker tidak membuktikan bahwa broker melewatkan pesan, sehingga percobaan ulang harus mentoleransi duplikat.
- Menulis ke basis data terlebih dahulu lalu memanggil broker di dalam penanganan error aplikasi masih memiliki race condition dual-write; try/catch tidak dapat menjadikannya atomik.
- Jika outbox dan tabel bisnis tidak dapat berbagi batas transaksi, gunakan CDC, perpesanan transaksional, atau definisikan ulang jaminan konsistensi.
7. Bacaan lebih lanjut
Bandingkan relay berbasis polling dengan relay CDC: polling lebih sederhana untuk di-deploy tetapi menambah pemindaian dan latensi, sementara CDC menurunkan latensi dengan konsekuensi log-capture dan dependensi operasional. Diskusikan poison message, exponential backoff, dead-letter queue, pemantauan usia antrean tertunda (pending-age monitoring), dan kompatibilitas skema konsumen.
8. Poin penilaian wawancara
Mampu menemukan jendela dual-write
Kandidat harus menjelaskan mengapa transaksi lokal biasa tidak dapat melakukan commit pembaruan basis data dan pengiriman broker secara bersamaan, lalu menempatkan perubahan pesanan dan baris outbox dalam satu transaksi.
Mampu menjelaskan at-least-once dan idempotensi
Kandidat harus mendeskripsikan jendela crash relay yang menciptakan duplikat dan membuat konsumen melakukan deduplikasi berdasarkan ID event dalam transaksi yang sama dengan pembaruan bisnisnya.
Mampu menangani pengurutan dan konkurensi
Kandidat harus membedakan urutan per-agregat dari urutan global dan menjelaskan bagaimana kunci partisi, versi, lock, atau lease membatasi klaim konkuren.
Mampu mencakup batasan operasional
Kandidat harus mengusulkan retry backoff, dead letter, peringatan backlog, pembersihan arsip, dan evolusi skema daripada hanya berhenti pada definisi tabel.