Semantik Pengiriman

At-least-once, at-most-once, exactly-once, idempotency, dan dead letter queue.

Daftar isi

Topik ini membahas delivery semantics, yaitu jaminan urutan dan kelengkapan pengiriman pesan antara broker dan consumer, beserta konsekuensinya bagi kode yang menerima pesan. Pembahasan mencakup tiga semantik dasar, teknik idempotency untuk menetralkan pesan ganda, dan dead letter queue sebagai tempat pesan yang gagal dikerjakan, semuanya disertai simulasi yang dapat dieksekusi.

Tiga semantik pengiriman#

Jaringan dan proses gagal, sehingga setiap sistem pesan harus memilih apa yang dijamin ketika kegagalan terjadi. At-most-once menjamin setiap pesan dikirim paling banyak satu kali, karena consumer mengakui pesan sebelum mengerjakannya, sehingga kegagalan pengerjaan membuat pesan itu hilang dan tidak pernah dikirim ulang. At-least-once menjamin setiap pesan dikerjakan paling tidak satu kali, karena consumer mengakui setelah pengerjaan berhasil, sehingga pesan yang belum diakui dikirim ulang, tetapi pesan yang sudah dikerjakan lalu gagal diakui dapat dikerjakan sekali lagi. Exactly-once menjamin setiap pesan memberikan efek tepat satu kali, dan jaminan ini paling sulit dicapai karena membutuhkan kesepakatan atomik antara pengerjaan dan pengakuan.

Pilihan menyesuaikan nilai data yang dikirim. Penghitungan metrik penayangan biasanya toleran terhadap kehilangan, sehingga at-most-once cukup digunakan. Pembaruan salinan data umumnya memakai at-least-once karena kehilangan pesan lebih berbahaya daripada duplikasi. Exactly-once yang sesungguhnya biasanya dicapai sebagai at-least-once ditambah idempotency, sehingga efek akhirnya sama dengan pengerjaan tepat satu kali meskipun pengiriman ganda tetap terjadi.

Simulasi at-least-once dan pesan ganda#

Simulasi berikut menyerupai consumer yang gagal dengan peluang sekitar 35 persen pada setiap pengerjaan, ditambah satu pesan racun yang selalu gagal. Broker bekerja dengan semantik at-least-once, yaitu mengirim ulang setiap pesan yang belum diakui, dan seed acak dibekukan agar keluarannya selalu sama. Simulasi terdiri atas tiga bagian, yaitu pengiriman ulang tanpa idempotency, dedup berbasis message ID, dan pemindahan ke dead letter queue setelah batas percobaan.

python semantik_sim.py
"""Simulasi delivery semantics: at-least-once, idempotency, dan DLQ.

Bagian 1 memperlihatkan pengiriman ulang pada semantik at-least-once:
consumer kadang gagal, sehingga broker mengirim ulang pesan yang belum
diakui dan pesan yang sama bisa dikerjakan lebih dari satu kali.

Bagian 2 menambahkan idempotency melalui dedup berbasis message ID,
sehingga pengiriman ganda tidak lagi menghasilkan efek samping ganda.

Bagian 3 memindahkan pesan racun ke dead letter queue setelah batas
percobaan terlampaui.
"""

import random

random.seed(42)

PESAN = ["tugas-1", "tugas-2", "tugas-3", "tugas-4", "tugas-5"]
MAKS_PERCOBAAN = 3
AMBANG_GAGAL = 0.35   # peluang kegagalan consumer sekitar 35 persen


def proses(pesan, racun=False):
    """Menyerupai pengerjaan pesan; mengembalikan True bila berhasil."""
    if racun:
        return False
    return random.random() > AMBANG_GAGAL


print("Bagian 1: at-least-once tanpa idempotency")
total_pengerjaan = 0
for pesan in PESAN:
    percobaan = 0
    while True:
        percobaan += 1
        total_pengerjaan += 1
        if proses(pesan):
            break
    print(f"  {pesan}: dikerjakan {percobaan} kali selesai")
print(f"  5 pesan unik menghasilkan {total_pengerjaan} pengerjaan")
print()

print("Bagian 2: idempotency dengan dedup message ID")
dikerjakan = set()
efek = 0
PENGIRIMAN = ["tugas-3", "tugas-3", "tugas-1", "tugas-3", "tugas-1"]
for pesan in PENGIRIMAN:
    if pesan in dikerjakan:
        print(f"  {pesan}: duplikat, dikenali dan dilewati")
    else:
        dikerjakan.add(pesan)
        efek += 1
        print(f"  {pesan}: pengerjaan pertama, efek samping diterapkan")
print(f"  5 pengiriman hanya menghasilkan {efek} efek samping")
print()

print("Bagian 3: dead letter queue setelah batas percobaan")
ANTREAN_AWAL = ["tugas-6", "tugas-racun", "tugas-7"]
dlq = []
for pesan in ANTREAN_AWAL:
    for percobaan in range(1, MAKS_PERCOBAAN + 1):
        if proses(pesan, racun=(pesan == "tugas-racun")):
            print(f"  {pesan}: berhasil pada percobaan ke-{percobaan}")
            break
    else:
        print(f"  {pesan}: gagal {MAKS_PERCOBAAN} kali, dipindah ke dead letter queue")
        dlq.append(pesan)
print(f"  Isi dead letter queue: {dlq}")
text Keluaran
Bagian 1: at-least-once tanpa idempotency
  tugas-1: dikerjakan 1 kali selesai
  tugas-2: dikerjakan 4 kali selesai
  tugas-3: dikerjakan 1 kali selesai
  tugas-4: dikerjakan 1 kali selesai
  tugas-5: dikerjakan 2 kali selesai
  5 pesan unik menghasilkan 9 pengerjaan

Bagian 2: idempotency dengan dedup message ID
  tugas-3: pengerjaan pertama, efek samping diterapkan
  tugas-3: duplikat, dikenali dan dilewati
  tugas-1: pengerjaan pertama, efek samping diterapkan
  tugas-3: duplikat, dikenali dan dilewati
  tugas-1: duplikat, dikenali dan dilewati
  5 pengiriman hanya menghasilkan 2 efek samping

Bagian 3: dead letter queue setelah batas percobaan
  tugas-6: berhasil pada percobaan ke-3
  tugas-racun: gagal 3 kali, dipindah ke dead letter queue
  tugas-7: berhasil pada percobaan ke-3
  Isi dead letter queue: ['tugas-racun']

Bagian 1 memperlihatkan biaya at-least-once, yaitu lima pesan unik dikerjakan sembilan kali. Tugas-2 saja dikerjakan empat kali sebelum diakui. Pengiriman ulang adalah perilaku yang benar, bukan error, karena broker tidak tahu apakah pengerjaan yang gagal berhenti sebelum efek diterapkan atau sesudahnya. Bagian 3 memperlihatkan tugas-6 dan tugas-7 yang gagal pada dua percobaan pertama tetapi akhirnya berhasil, sedangkan pesan racun menghabiskan seluruh batas percobaan dan dipindahkan agar tidak menghambat pesan lain.

Idempotency#

Idempotency adalah sifat operasi yang memberikan hasil akhir sama ketika diterapkan lebih dari satu kali, misalnya mengatur nilai sebuah kolom menjadi angka tertentu. Pada sistem pesan, idempotency biasanya dicapai dengan menyimpan message ID setiap pesan yang pernah dikerjakan, lalu pesan dengan ID yang sudah tercatat dikenali sebagai duplikat dan dilewati. Bagian 2 simulasi menerapkan dedup tersebut pada lima pengiriman yang hanya mengandung dua pesan unik, sehingga efek samping hanya diterapkan dua kali meskipun penerimaan terjadi lima kali.

Penyimpanan dedup diberi batas waktu yang disebut TTL (time to live), karena menyimpan seluruh ID selamanya memakan penyimpanan. Jendela dedup harus lebih panjang daripada pengiriman ulang terlama yang mungkin terjadi, sebab duplikat yang datang setelah jendelanya ditutup akan dikerjakan lagi. Untuk operasi yang berisiko seperti pembayaran, beberapa API menyediakan idempotency key yang diberikan klien pada setiap permintaan, sehingga pengulangan dari sisi klien pun tetap dikenali sebagai permintaan yang sama.

Dead letter queue#

Pesan yang gagal berulang kali, biasanya karena data yang cacat atau bug pada penangan, disebut pesan racun (poison message) apabila dibiarkan dikirim ulang tanpa akhir dan menghabiskan kapasitas worker. Dead letter queue memutus siklus itu, karena setelah batas percobaan terlampaui, pesan dipindahkan ke queue terpisah dan pengerjaan berlanjut ke pesan berikutnya. Queue terpisah itu diperiksa oleh manusia atau alat khusus untuk memperbaiki data lalu mengirim ulang pesannya. Karena isinya menandai masalah yang belum selesai, kedalaman dead letter queue beserta laju kenaikannya dipasang sebagai alarm pada pemantauan.

Kesalahan umum#

  1. Mengakui pesan sebelum pengerjaan selesai pada semantik at-least-once. Pesan yang hilang bersama proses yang mati tidak akan pernah dikirim ulang, sehingga data hilang tanpa jejak. Urutan yang benar adalah mengerjakan pesan terlebih dahulu, baru mengirim pengakuan.
  2. Memasang jendela dedup yang terlalu pendek. TTL yang lebih pendek daripada pengiriman ulang terlama membiarkan duplikat lolos setelah jendelanya ditutup. Perbaikannya adalah menyamakan jendela dedup dengan batas pengiriman ulang terburuk broker.
  3. Membiarkan dead letter queue tumbuh tanpa alarm. Pesan yang gagal terus terakumulasi tanpa pernah selesai, dan pengguna tidak melihat jejaknya. Perbaikannya adalah alarm kedalaman dan prosedur pemeriksaan rutin beserta pemrosesan ulang.

Ringkasan#

  • At-most-once boleh kehilangan, at-least-once boleh menduplikasi, dan exactly-once pada praktiknya dicapai sebagai at-least-once ditambah idempotency.
  • Idempotency diterapkan lewat dedup berbasis message ID atau idempotency key dari klien, dengan jendela dedup yang lebih panjang daripada pengiriman ulang terlama.
  • Dead letter queue menghentikan pesan racun setelah batas percobaan, lalu isinya diperiksa dan diproses ulang di bawah alarm pemantauan.

Referensi#