[{"data":1,"prerenderedAt":225},["ShallowReactive",2],{"tag-labels":3,"site-header":4,"article-dead-letter-queue-retry-policy-rabbitmq-resilient-messaging-id":72},{},{"featuredPost":5,"navigations":29},{"id":6,"title":7,"title_en":8,"slug":9,"excerpt":10,"excerpt_en":11,"has_translation":12,"published_at":13,"reading_time":14,"hidden_from_listing":15,"tags":16,"thumbnail_image":21,"og_image":22,"series":23,"authors":28},110,"Ketika Pilih Framework Cuma Biar Kelihatan Beda","When You Pick a Framework Just to Look Different","ketika-pilih-framework-cuma-biar-kelihatan-beda","Terakhir kali kamu milih framework atau library baru, itu karena masalahmu butuh alat itu, atau karena alatnya kedengaran keren dan belum banyak yang pakai? Pertanyaan ini nggak enak dijawab. Soalnya keputusan teknologi sering diambil dengan alasan yang nggak pernah ditulis di dokumen teknis mana pun.","The last time you chose a new framework or library, was it because your problem needed that tool, or because it sounded cool and few people around you were using it? That question is uncomfortable. Technology decisions are often made for reasons that never appear in any technical document.",true,"2026-10-06T15:25:21.000Z",5,false,[17,18,19,20],"karier","framework","opini","pengalaman",null,"\u002Fog\u002Fketika-pilih-framework-cuma-biar-kelihatan-beda.png",{"id":24,"title":25,"title_en":26,"slug":27},9,"Artikel","Articles","all",[],[30,46,67],{"collection":31,"item":32},"series",{"__typename":31,"title":25,"title_en":26,"slug":27,"children_series":33},[34,38,42],{"title":35,"title_en":36,"slug":37},"Kecerdasan Artifisial","Artificial Intelligence","kecerdasan-artifisial",{"title":39,"title_en":40,"slug":41},"Konsep Dasar Pemrograman","Programming Fundamentals","konsep-dasar-pemrograman",{"title":43,"title_en":44,"slug":45},"Algoritma dan Struktur Data","Algorithms and Data Structures","algoritma-struktur-data",{"collection":31,"item":47},{"__typename":31,"title":48,"title_en":48,"slug":49,"children_series":50},"Tutorial","tutorial",[51,55,59,63],{"title":52,"title_en":53,"slug":54},"Belajar Python","Learning Python","belajar-python",{"title":56,"title_en":57,"slug":58},"Belajar Golang","Learning Go","belajar-golang",{"title":60,"title_en":61,"slug":62},"Belajar Java","Learning Java","belajar-java",{"title":64,"title_en":65,"slug":66},"Belajar Pascal","Learning Pascal","belajar-pascal",{"collection":31,"item":68},{"__typename":31,"title":69,"title_en":69,"slug":70,"children_series":71},"Lab","lab",[],{"article":73,"renderedBody":102,"renderedTakeaway":103,"latestArticles":104,"toc":206},{"id":74,"title":75,"title_en":76,"slug":77,"excerpt":78,"excerpt_en":79,"has_translation":12,"published_at":80,"reading_time":14,"hidden_from_listing":15,"tags":81,"thumbnail_image":21,"og_image":89,"series":21,"authors":90,"body_locale":97,"available_locales":98,"body":100,"takeway":101,"prerequisite":21},43,"Dead Letter Queue (DLQ) & Retry Policy: Menyelamatkan Pesan Nyasar di RabbitMQ Tanpa Bikin Worker Macet","Dead Letter Queue (DLQ) & Retry Policy: Rescuing Stray Messages in RabbitMQ Without Stalling Workers","dead-letter-queue-retry-policy-rabbitmq-resilient-messaging","Pernah nggak sih, *worker* kalian nolak satu pesan terus-terusan sampai CPU melonjak 100%? Itu namanya *poison message loop*, jebakan klasik di RabbitMQ. Yuk, kita bahas cara pasang *retry policy* dan *dead letter queue* yang bener!","Has a *worker* ever rejected the same message over and over until CPU spiked to 100%? That is the *poison message loop*, a classic RabbitMQ trap. Here is how to set up a *retry policy* and a *dead letter queue* properly.","2026-09-03T04:16:07.481Z",[82,83,84,85,86,87,88],"rabbitmq","message-broker","backend","architecture","microservices","async","python","\u002Fog\u002Fdead-letter-queue-retry-policy-rabbitmq-resilient-messaging.png",[91],{"authors_id":92},{"id":93,"name":94,"slug":95,"role":96,"profile_picture":21},2,"Inva","inva","Writer","id",[99,97],"en","Kalian sedang ngejalanin sistem e-commerce yang lumayan sibuk. Begitu pelanggan berhasil bayar, sistem nerbitin (*publish*) event `order.paid` ke antrean [RabbitMQ](https:\u002F\u002Fwww.rabbitmq.com\u002F). Dari situ, *worker* bakal nyetak resi pengiriman, motong stok gudang, dan ngirim invoice lewat email.\n\nTiba-tiba, API penyedia logistik pihak ketiga mengalami gangguan (*down*). *Worker* pengiriman pun gagal memproses pesan itu. Nah, dari titik ini kita bakal lihat gimana jebakannya muncul.\n\nApa yang biasanya dilakukan developer pemula? Mereka nangkep *exception* (`try...except`), lalu manggil baris sakti ini:\n\n```python\n# ANTI-PATTERN: Menolak pesan dan menyuruh RabbitMQ memasukkannya kembali ke antrean\nchannel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)\n```\n\nHasilnya? Pesan langsung dibalikin ke depan antrean, diambil lagi oleh *worker* dalam hitungan mikrodetik, gagal lagi, dikembalikan lagi, dan begitu terus jutaan kali per menit. 😅\n\nFenomena ini disebut **poison message loop**. Bukan cuma bikin antrean macet total (*head-of-line blocking*), CPU server *worker* kalian bakal langsung melonjak ke 100%!\n\nTerus, gimana cara nangani pesan gagal secara elegan tanpa ngeblokir antrean lain? Jawabannya ada dua senjata: **retry policy** dan *dead letter queue* (*DLQ*).\n\n## Apa Sih Sebenarnya *Dead Letter Exchange* Itu?\n\nDi [RabbitMQ](https:\u002F\u002Fwww.rabbitmq.com\u002F), *dead lettering* bukanlah fitur magis yang rumit. *Dead Letter Exchange* (*DLX*) sebenarnya cuma *exchange* AMQP biasa yang kita daftarkan ke sebuah antrean lewat argumen konfigurasi `x-dead-letter-exchange`.\n\nSebuah pesan dianggap \"mati\" (*dead-lettered*) dan diteruskan otomatis oleh *broker* RabbitMQ ke *DLX* hanya kalau terjadi salah satu dari tiga kondisi ini.\n\nPertama, pesan ditolak eksplisit oleh *worker* pakai `basic.reject` atau `basic.nack` dengan parameter `requeue=False`.\n\nKedua, masa berlaku pesan habis (*TTL expired*). Pesan punya batas waktu hidup (*Time-To-Live*), baik di level antrean (`x-message-ttl`) atau per pesan individual, dan waktunya kedaluwarsa sebelum sempat diproses.\n\nKetiga, antrean melebihi batas (*max length reached*). Antrean utama udah nyampe batas maksimum pesan (`x-max-length`), jadi pesan paling awal dibuang ke *DLX*.\n\n## Kenapa Butuh *Retry* Bertingkat Sih?\n\nKetika sebuah pesan gagal diproses, penyebabnya biasanya terbagi dua kategori.\n\nAda *transient failure*, alias kesalahan sementara: jaringan *glitch* sesaat, *database* lagi *lock contention*, atau API pihak ketiga lambat merespons. Solusinya, coba lagi beberapa detik kemudian (*retry with backoff*).\n\nAda juga *permanent failure*, alias kesalahan permanen: payload JSON rusak, ID pengguna nggak valid, atau bug logika di kode. Berapa kali pun dicoba ulang, pesan ini nggak akan pernah berhasil!\n\nStrategi idealnya, kasih kesempatan *retry* otomatis beberapa kali dengan jeda yang makin lama (misal 5 detik, 15 detik, 60 detik). Kalau setelah 3 atau 5 percobaan masih tetap gagal, barulah pesannya dilempar ke *Dead Letter Queue* (*DLQ*) akhir buat dianalisis developer.\n\n## Pola Andalan: *Delayed Retry* via TTL dan DLX\n\nSalah satu pola paling andal di RabbitMQ tanpa *plugin* eksternal adalah memanfaatkan antrean perantara (*retry queue*) yang dipasangi TTL.\n\nPertama, *worker* mengonsumsi pesan dari *main queue* (`orders.process`). Kalau gagal sementara, *worker* mem-*publish* pesan ke `retry.exchange` sambil nyatet counter percobaan di header `x-retries: 1`.\n\nKedua, pesan mendarat di *retry queue* (`orders.retry.30s`). Antrean ini nggak punya *worker* sama sekali! Dia cuma dipasangi `x-message-ttl: 30000` (30 detik) dan `x-dead-letter-exchange: orders.exchange`.\n\nKetiga, setelah 30 detik tidur di *retry queue*, RabbitMQ otomatis menganggap pesan itu \"expired\" dan meneruskannya kembali ke `orders.process` buat dicoba ulang oleh *worker*.\n\nKeempat, kalau header `x-retries` udah nyampe batas maksimum (misal 3), *worker* langsung ngirim pesan ke `orders.dlq` (*terminal queue*). Selesai, nggak ada drama berulang. 😎\n\n## Contoh Implementasi di Python (Pika \u002F Aio-Pika)\n\nBerikut contoh deklarasi antrean tangguh pakai Python dan pustaka `pika`:\n\n```python\nimport json\nimport pika\n\nconnection = pika.BlockingConnection(pika.ConnectionParameters(host=\"localhost\"))\nchannel = connection.channel()\n\n# 1. Buat Dead Letter Exchange (DLX) dan Antrean DLQ\nchannel.exchange_declare(exchange=\"dlx.orders\", exchange_type=\"direct\")\nchannel.queue_declare(queue=\"orders.dlq\", durable=True)\nchannel.queue_bind(exchange=\"dlx.orders\", queue=\"orders.dlq\", routing_key=\"order.failed\")\n\n# 2. Buat Main Exchange & Queue dengan konfigurasi DLX bawaan\nchannel.exchange_declare(exchange=\"orders.exchange\", exchange_type=\"direct\")\n\nqueue_args = {\n    \"x-dead-letter-exchange\": \"dlx.orders\",\n    \"x-dead-letter-routing-key\": \"order.failed\",\n}\n\nchannel.queue_declare(queue=\"orders.main\", durable=True, arguments=queue_args)\nchannel.queue_bind(exchange=\"orders.exchange\", queue=\"orders.main\", routing_key=\"order.process\")\n\n# 3. Callback Worker yang Aman\ndef on_message(ch, method, properties, body):\n    try:\n        data = json.loads(body)\n        # Eksekusi logika bisnis (misal call API logistik)...\n        process_shipment(data)\n        ch.basic_ack(delivery_tag=method.delivery_tag)\n    except TransientNetworkError:\n        # Kesalahan sementara: bisa dipublish ke retry queue\n        print(\"Network timeout, routing to retry...\")\n        ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)  # Masuk ke DLQ jika batas habis\n    except Exception as e:\n        # Kesalahan permanen: buang langsung ke DLQ, JANGAN REQUEUE!\n        print(f\"Permanent error: {e}. Moving to DLQ.\")\n        ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)\n\nchannel.basic_consume(queue=\"orders.main\", on_message_callback=on_message)\nprint(\"Worker siap melayani pesanan...\")\nchannel.start_consuming()\n```\n\n## Terus, Pesan yang Nyangkut di DLQ Diapain?\n\nAntrean *DLQ* bukan tempat sampah tempat data dilupakan gitu aja. *DLQ* itu brankas penyelamat, dan ada tiga hal yang wajib kalian lakukan.\n\nPasang *alerting* terintegrasi. Setel metrik *monitoring* (misal via Prometheus RabbitMQ Exporter) buat bunyiin alarm begitu jumlah pesan di `orders.dlq` lebih besar dari 0.\n\nLakukan inspeksi dan *root cause analysis*. Developer bisa lihat payload asli dan header error di RabbitMQ Management UI buat mendiagnosis apakah ada payload aneh atau bug baru.\n\nTerakhir, jalankan *replay message*. Setelah bug di kode diperbaiki atau layanan pihak ketiga kembali online, kalian bisa pakai CLI `rabbitmqadmin` atau *script* sederhana buat mindahin (*shovel*) pesan dari *DLQ* balik ke antrean utama. Nggak ada transaksi pelanggan yang hilang!\n\n## Jangan Biarkan Satu Pesan Gagal Nurunin Sistemmu\n\nSistem terdistribusi yang tangguh (*resilient*) bukan sistem yang nggak pernah gagal, melainkan sistem yang tahu cara memperlakukan kegagalan dengan anggun.\n\nDengan mengganti `requeue=True` yang ceroboh jadi kombinasi *retry backoff* dan *dead letter queue*, kalian melindungi *worker* backend dari kelumpuhan. Setiap pesan penting tetap tersimpan dengan selamat, siap diproses ulang kapan pun bug-nya kelar dibetulin.\n\nSelamat ber-*messaging* ria, dan semoga nggak ada lagi pesanan pelanggan yang nyasar ke *void*! 👋\n","- Tiga pemicu pesan masuk ke *Dead Letter Exchange* (*DLX*) di RabbitMQ: penolakan negatif (`basic.reject` \u002F `basic.nack` dengan `requeue=false`), TTL pesan kedaluwarsa, atau antrean yang melampaui kapasitas (`x-max-length`).\n- Jebakan fatal bernama *infinite poison message loop* muncul saat *worker* terus menolak pesan dengan `requeue=true` sampai CPU nembus 100%.\n- Arsitektur *exponential backoff retry* dibangun dari kombinasi header `x-delivery-count` \u002F metadata *retry* custom dan *delayed message exchange*.\n- Antrean *DLQ* yang terisolasi jadi *safety net* buat inspeksi manual, *audit log*, dan proses *re-play* pesan setelah bug diperbaiki.","\u003Cp>Kalian sedang ngejalanin sistem e-commerce yang lumayan sibuk. Begitu pelanggan berhasil bayar, sistem nerbitin (\u003Cem>publish\u003C\u002Fem>) event \u003Ccode>order.paid\u003C\u002Fcode> ke antrean \u003Ca href=\"https:\u002F\u002Fwww.rabbitmq.com\u002F\">RabbitMQ\u003C\u002Fa>. Dari situ, \u003Cem>worker\u003C\u002Fem> bakal nyetak resi pengiriman, motong stok gudang, dan ngirim invoice lewat email.\u003C\u002Fp>\n\u003Cp>Tiba-tiba, API penyedia logistik pihak ketiga mengalami gangguan (\u003Cem>down\u003C\u002Fem>). \u003Cem>Worker\u003C\u002Fem> pengiriman pun gagal memproses pesan itu. Nah, dari titik ini kita bakal lihat gimana jebakannya muncul.\u003C\u002Fp>\n\u003Cp>Apa yang biasanya dilakukan developer pemula? Mereka nangkep \u003Cem>exception\u003C\u002Fem> (\u003Ccode>try...except\u003C\u002Fcode>), lalu manggil baris sakti ini:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%23%20ANTI-PATTERN%3A%20Menolak%20pesan%20dan%20menyuruh%20RabbitMQ%20memasukkannya%20kembali%20ke%20antrean%0Achannel.basic_nack(delivery_tag%3Dmethod.delivery_tag%2C%20requeue%3DTrue)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># ANTI-PATTERN: Menolak pesan dan menyuruh RabbitMQ memasukkannya kembali ke antrean\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.basic_nack(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">delivery_tag\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">method.delivery_tag, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">requeue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Hasilnya? Pesan langsung dibalikin ke depan antrean, diambil lagi oleh \u003Cem>worker\u003C\u002Fem> dalam hitungan mikrodetik, gagal lagi, dikembalikan lagi, dan begitu terus jutaan kali per menit. 😅\u003C\u002Fp>\n\u003Cp>Fenomena ini disebut \u003Cstrong>poison message loop\u003C\u002Fstrong>. Bukan cuma bikin antrean macet total (\u003Cem>head-of-line blocking\u003C\u002Fem>), CPU server \u003Cem>worker\u003C\u002Fem> kalian bakal langsung melonjak ke 100%!\u003C\u002Fp>\n\u003Cp>Terus, gimana cara nangani pesan gagal secara elegan tanpa ngeblokir antrean lain? Jawabannya ada dua senjata: \u003Cstrong>retry policy\u003C\u002Fstrong> dan \u003Cem>dead letter queue\u003C\u002Fem> (\u003Cem>DLQ\u003C\u002Fem>).\u003C\u002Fp>\n\u003Ch2 id=\"apa-sih-sebenarnya-dead-letter-exchange-itu\">Apa Sih Sebenarnya \u003Cem>Dead Letter Exchange\u003C\u002Fem> Itu?\u003C\u002Fh2>\n\u003Cp>Di \u003Ca href=\"https:\u002F\u002Fwww.rabbitmq.com\u002F\">RabbitMQ\u003C\u002Fa>, \u003Cem>dead lettering\u003C\u002Fem> bukanlah fitur magis yang rumit. \u003Cem>Dead Letter Exchange\u003C\u002Fem> (\u003Cem>DLX\u003C\u002Fem>) sebenarnya cuma \u003Cem>exchange\u003C\u002Fem> AMQP biasa yang kita daftarkan ke sebuah antrean lewat argumen konfigurasi \u003Ccode>x-dead-letter-exchange\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Sebuah pesan dianggap &quot;mati&quot; (\u003Cem>dead-lettered\u003C\u002Fem>) dan diteruskan otomatis oleh \u003Cem>broker\u003C\u002Fem> RabbitMQ ke \u003Cem>DLX\u003C\u002Fem> hanya kalau terjadi salah satu dari tiga kondisi ini.\u003C\u002Fp>\n\u003Cp>Pertama, pesan ditolak eksplisit oleh \u003Cem>worker\u003C\u002Fem> pakai \u003Ccode>basic.reject\u003C\u002Fcode> atau \u003Ccode>basic.nack\u003C\u002Fcode> dengan parameter \u003Ccode>requeue=False\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Kedua, masa berlaku pesan habis (\u003Cem>TTL expired\u003C\u002Fem>). Pesan punya batas waktu hidup (\u003Cem>Time-To-Live\u003C\u002Fem>), baik di level antrean (\u003Ccode>x-message-ttl\u003C\u002Fcode>) atau per pesan individual, dan waktunya kedaluwarsa sebelum sempat diproses.\u003C\u002Fp>\n\u003Cp>Ketiga, antrean melebihi batas (\u003Cem>max length reached\u003C\u002Fem>). Antrean utama udah nyampe batas maksimum pesan (\u003Ccode>x-max-length\u003C\u002Fcode>), jadi pesan paling awal dibuang ke \u003Cem>DLX\u003C\u002Fem>.\u003C\u002Fp>\n\u003Ch2 id=\"kenapa-butuh-retry-bertingkat-sih\">Kenapa Butuh \u003Cem>Retry\u003C\u002Fem> Bertingkat Sih?\u003C\u002Fh2>\n\u003Cp>Ketika sebuah pesan gagal diproses, penyebabnya biasanya terbagi dua kategori.\u003C\u002Fp>\n\u003Cp>Ada \u003Cem>transient failure\u003C\u002Fem>, alias kesalahan sementara: jaringan \u003Cem>glitch\u003C\u002Fem> sesaat, \u003Cem>database\u003C\u002Fem> lagi \u003Cem>lock contention\u003C\u002Fem>, atau API pihak ketiga lambat merespons. Solusinya, coba lagi beberapa detik kemudian (\u003Cem>retry with backoff\u003C\u002Fem>).\u003C\u002Fp>\n\u003Cp>Ada juga \u003Cem>permanent failure\u003C\u002Fem>, alias kesalahan permanen: payload JSON rusak, ID pengguna nggak valid, atau bug logika di kode. Berapa kali pun dicoba ulang, pesan ini nggak akan pernah berhasil!\u003C\u002Fp>\n\u003Cp>Strategi idealnya, kasih kesempatan \u003Cem>retry\u003C\u002Fem> otomatis beberapa kali dengan jeda yang makin lama (misal 5 detik, 15 detik, 60 detik). Kalau setelah 3 atau 5 percobaan masih tetap gagal, barulah pesannya dilempar ke \u003Cem>Dead Letter Queue\u003C\u002Fem> (\u003Cem>DLQ\u003C\u002Fem>) akhir buat dianalisis developer.\u003C\u002Fp>\n\u003Ch2 id=\"pola-andalan-delayed-retry-via-ttl-dan-dlx\">Pola Andalan: \u003Cem>Delayed Retry\u003C\u002Fem> via TTL dan DLX\u003C\u002Fh2>\n\u003Cp>Salah satu pola paling andal di RabbitMQ tanpa \u003Cem>plugin\u003C\u002Fem> eksternal adalah memanfaatkan antrean perantara (\u003Cem>retry queue\u003C\u002Fem>) yang dipasangi TTL.\u003C\u002Fp>\n\u003Cp>Pertama, \u003Cem>worker\u003C\u002Fem> mengonsumsi pesan dari \u003Cem>main queue\u003C\u002Fem> (\u003Ccode>orders.process\u003C\u002Fcode>). Kalau gagal sementara, \u003Cem>worker\u003C\u002Fem> mem-\u003Cem>publish\u003C\u002Fem> pesan ke \u003Ccode>retry.exchange\u003C\u002Fcode> sambil nyatet counter percobaan di header \u003Ccode>x-retries: 1\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Kedua, pesan mendarat di \u003Cem>retry queue\u003C\u002Fem> (\u003Ccode>orders.retry.30s\u003C\u002Fcode>). Antrean ini nggak punya \u003Cem>worker\u003C\u002Fem> sama sekali! Dia cuma dipasangi \u003Ccode>x-message-ttl: 30000\u003C\u002Fcode> (30 detik) dan \u003Ccode>x-dead-letter-exchange: orders.exchange\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Ketiga, setelah 30 detik tidur di \u003Cem>retry queue\u003C\u002Fem>, RabbitMQ otomatis menganggap pesan itu &quot;expired&quot; dan meneruskannya kembali ke \u003Ccode>orders.process\u003C\u002Fcode> buat dicoba ulang oleh \u003Cem>worker\u003C\u002Fem>.\u003C\u002Fp>\n\u003Cp>Keempat, kalau header \u003Ccode>x-retries\u003C\u002Fcode> udah nyampe batas maksimum (misal 3), \u003Cem>worker\u003C\u002Fem> langsung ngirim pesan ke \u003Ccode>orders.dlq\u003C\u002Fcode> (\u003Cem>terminal queue\u003C\u002Fem>). Selesai, nggak ada drama berulang. 😎\u003C\u002Fp>\n\u003Ch2 id=\"contoh-implementasi-di-python-pika-aio-pika\">Contoh Implementasi di Python (Pika \u002F Aio-Pika)\u003C\u002Fh2>\n\u003Cp>Berikut contoh deklarasi antrean tangguh pakai Python dan pustaka \u003Ccode>pika\u003C\u002Fcode>:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"import%20json%0Aimport%20pika%0A%0Aconnection%20%3D%20pika.BlockingConnection(pika.ConnectionParameters(host%3D%22localhost%22))%0Achannel%20%3D%20connection.channel()%0A%0A%23%201.%20Buat%20Dead%20Letter%20Exchange%20(DLX)%20dan%20Antrean%20DLQ%0Achannel.exchange_declare(exchange%3D%22dlx.orders%22%2C%20exchange_type%3D%22direct%22)%0Achannel.queue_declare(queue%3D%22orders.dlq%22%2C%20durable%3DTrue)%0Achannel.queue_bind(exchange%3D%22dlx.orders%22%2C%20queue%3D%22orders.dlq%22%2C%20routing_key%3D%22order.failed%22)%0A%0A%23%202.%20Buat%20Main%20Exchange%20%26%20Queue%20dengan%20konfigurasi%20DLX%20bawaan%0Achannel.exchange_declare(exchange%3D%22orders.exchange%22%2C%20exchange_type%3D%22direct%22)%0A%0Aqueue_args%20%3D%20%7B%0A%20%20%20%20%22x-dead-letter-exchange%22%3A%20%22dlx.orders%22%2C%0A%20%20%20%20%22x-dead-letter-routing-key%22%3A%20%22order.failed%22%2C%0A%7D%0A%0Achannel.queue_declare(queue%3D%22orders.main%22%2C%20durable%3DTrue%2C%20arguments%3Dqueue_args)%0Achannel.queue_bind(exchange%3D%22orders.exchange%22%2C%20queue%3D%22orders.main%22%2C%20routing_key%3D%22order.process%22)%0A%0A%23%203.%20Callback%20Worker%20yang%20Aman%0Adef%20on_message(ch%2C%20method%2C%20properties%2C%20body)%3A%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20data%20%3D%20json.loads(body)%0A%20%20%20%20%20%20%20%20%23%20Eksekusi%20logika%20bisnis%20(misal%20call%20API%20logistik)...%0A%20%20%20%20%20%20%20%20process_shipment(data)%0A%20%20%20%20%20%20%20%20ch.basic_ack(delivery_tag%3Dmethod.delivery_tag)%0A%20%20%20%20except%20TransientNetworkError%3A%0A%20%20%20%20%20%20%20%20%23%20Kesalahan%20sementara%3A%20bisa%20dipublish%20ke%20retry%20queue%0A%20%20%20%20%20%20%20%20print(%22Network%20timeout%2C%20routing%20to%20retry...%22)%0A%20%20%20%20%20%20%20%20ch.basic_nack(delivery_tag%3Dmethod.delivery_tag%2C%20requeue%3DFalse)%20%20%23%20Masuk%20ke%20DLQ%20jika%20batas%20habis%0A%20%20%20%20except%20Exception%20as%20e%3A%0A%20%20%20%20%20%20%20%20%23%20Kesalahan%20permanen%3A%20buang%20langsung%20ke%20DLQ%2C%20JANGAN%20REQUEUE!%0A%20%20%20%20%20%20%20%20print(f%22Permanent%20error%3A%20%7Be%7D.%20Moving%20to%20DLQ.%22)%0A%20%20%20%20%20%20%20%20ch.basic_nack(delivery_tag%3Dmethod.delivery_tag%2C%20requeue%3DFalse)%0A%0Achannel.basic_consume(queue%3D%22orders.main%22%2C%20on_message_callback%3Don_message)%0Aprint(%22Worker%20siap%20melayani%20pesanan...%22)%0Achannel.start_consuming()\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> json\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> pika\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">connection \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> pika.BlockingConnection(pika.ConnectionParameters(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">host\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"localhost\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> connection.channel()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 1. Buat Dead Letter Exchange (DLX) dan Antrean DLQ\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.exchange_declare(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"dlx.orders\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange_type\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"direct\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.queue_declare(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">queue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.dlq\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">durable\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.queue_bind(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"dlx.orders\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">queue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.dlq\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">routing_key\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"order.failed\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 2. Buat Main Exchange &#x26; Queue dengan konfigurasi DLX bawaan\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.exchange_declare(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.exchange\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange_type\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"direct\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">queue_args \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"x-dead-letter-exchange\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"dlx.orders\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    \"x-dead-letter-routing-key\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"order.failed\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">,\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.queue_declare(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">queue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.main\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">durable\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">True\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">arguments\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">queue_args)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.queue_bind(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">exchange\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.exchange\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">queue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.main\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">routing_key\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"order.process\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 3. Callback Worker yang Aman\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> on_message\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(ch, method, properties, body):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    try\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        data \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> json.loads(body)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Eksekusi logika bisnis (misal call API logistik)...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        process_shipment(data)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ch.basic_ack(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">delivery_tag\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">method.delivery_tag)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    except\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> TransientNetworkError:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Kesalahan sementara: bisa dipublish ke retry queue\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Network timeout, routing to retry...\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ch.basic_nack(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">delivery_tag\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">method.delivery_tag, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">requeue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">False\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D\"># Masuk ke DLQ jika batas habis\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    except\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> Exception\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> as\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> e:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Kesalahan permanen: buang langsung ke DLQ, JANGAN REQUEUE!\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Permanent error: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">e\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">. Moving to DLQ.\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        ch.basic_nack(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">delivery_tag\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">method.delivery_tag, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">requeue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">False\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.basic_consume(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">queue\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"orders.main\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">on_message_callback\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">on_message)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Worker siap melayani pesanan...\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">channel.start_consuming()\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Ch2 id=\"terus-pesan-yang-nyangkut-di-dlq-diapain\">Terus, Pesan yang Nyangkut di DLQ Diapain?\u003C\u002Fh2>\n\u003Cp>Antrean \u003Cem>DLQ\u003C\u002Fem> bukan tempat sampah tempat data dilupakan gitu aja. \u003Cem>DLQ\u003C\u002Fem> itu brankas penyelamat, dan ada tiga hal yang wajib kalian lakukan.\u003C\u002Fp>\n\u003Cp>Pasang \u003Cem>alerting\u003C\u002Fem> terintegrasi. Setel metrik \u003Cem>monitoring\u003C\u002Fem> (misal via Prometheus RabbitMQ Exporter) buat bunyiin alarm begitu jumlah pesan di \u003Ccode>orders.dlq\u003C\u002Fcode> lebih besar dari 0.\u003C\u002Fp>\n\u003Cp>Lakukan inspeksi dan \u003Cem>root cause analysis\u003C\u002Fem>. Developer bisa lihat payload asli dan header error di RabbitMQ Management UI buat mendiagnosis apakah ada payload aneh atau bug baru.\u003C\u002Fp>\n\u003Cp>Terakhir, jalankan \u003Cem>replay message\u003C\u002Fem>. Setelah bug di kode diperbaiki atau layanan pihak ketiga kembali online, kalian bisa pakai CLI \u003Ccode>rabbitmqadmin\u003C\u002Fcode> atau \u003Cem>script\u003C\u002Fem> sederhana buat mindahin (\u003Cem>shovel\u003C\u002Fem>) pesan dari \u003Cem>DLQ\u003C\u002Fem> balik ke antrean utama. Nggak ada transaksi pelanggan yang hilang!\u003C\u002Fp>\n\u003Ch2 id=\"jangan-biarkan-satu-pesan-gagal-nurunin-sistemmu\">Jangan Biarkan Satu Pesan Gagal Nurunin Sistemmu\u003C\u002Fh2>\n\u003Cp>Sistem terdistribusi yang tangguh (\u003Cem>resilient\u003C\u002Fem>) bukan sistem yang nggak pernah gagal, melainkan sistem yang tahu cara memperlakukan kegagalan dengan anggun.\u003C\u002Fp>\n\u003Cp>Dengan mengganti \u003Ccode>requeue=True\u003C\u002Fcode> yang ceroboh jadi kombinasi \u003Cem>retry backoff\u003C\u002Fem> dan \u003Cem>dead letter queue\u003C\u002Fem>, kalian melindungi \u003Cem>worker\u003C\u002Fem> backend dari kelumpuhan. Setiap pesan penting tetap tersimpan dengan selamat, siap diproses ulang kapan pun bug-nya kelar dibetulin.\u003C\u002Fp>\n\u003Cp>Selamat ber-\u003Cem>messaging\u003C\u002Fem> ria, dan semoga nggak ada lagi pesanan pelanggan yang nyasar ke \u003Cem>void\u003C\u002Fem>! 👋\u003C\u002Fp>\n","\u003Cul>\n\u003Cli>Tiga pemicu pesan masuk ke \u003Cem>Dead Letter Exchange\u003C\u002Fem> (\u003Cem>DLX\u003C\u002Fem>) di RabbitMQ: penolakan negatif (\u003Ccode>basic.reject\u003C\u002Fcode> \u002F \u003Ccode>basic.nack\u003C\u002Fcode> dengan \u003Ccode>requeue=false\u003C\u002Fcode>), TTL pesan kedaluwarsa, atau antrean yang melampaui kapasitas (\u003Ccode>x-max-length\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>Jebakan fatal bernama \u003Cem>infinite poison message loop\u003C\u002Fem> muncul saat \u003Cem>worker\u003C\u002Fem> terus menolak pesan dengan \u003Ccode>requeue=true\u003C\u002Fcode> sampai CPU nembus 100%.\u003C\u002Fli>\n\u003Cli>Arsitektur \u003Cem>exponential backoff retry\u003C\u002Fem> dibangun dari kombinasi header \u003Ccode>x-delivery-count\u003C\u002Fcode> \u002F metadata \u003Cem>retry\u003C\u002Fem> custom dan \u003Cem>delayed message exchange\u003C\u002Fem>.\u003C\u002Fli>\n\u003Cli>Antrean \u003Cem>DLQ\u003C\u002Fem> yang terisolasi jadi \u003Cem>safety net\u003C\u002Fem> buat inspeksi manual, \u003Cem>audit log\u003C\u002Fem>, dan proses \u003Cem>re-play\u003C\u002Fem> pesan setelah bug diperbaiki.\u003C\u002Fli>\n\u003C\u002Ful>\n",[105,126,139,152,165,186],{"id":106,"title":107,"title_en":21,"slug":108,"excerpt":109,"excerpt_en":21,"has_translation":15,"published_at":110,"reading_time":111,"hidden_from_listing":15,"tags":112,"thumbnail_image":21,"og_image":114,"series":115,"authors":120},133,"Python Strings: the Badge That Only Worked for One Name","python-strings","The badge script printed the right name for the person who wrote it and the wrong name for everybody else. Slicing counts characters, and a name is words.","2026-10-28T13:00:00.000Z",6,[88,113],"string","\u002Fog\u002Fpython-strings.png",{"id":116,"title":117,"title_en":118,"slug":119},30,"Koleksi dan String","Collections and Strings","python-collections",[121],{"authors_id":122},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},1,"Daffa Izzuddin","izzudd",{"id":127,"title":128,"title_en":21,"slug":129,"excerpt":130,"excerpt_en":21,"has_translation":15,"published_at":131,"reading_time":111,"hidden_from_listing":15,"tags":132,"thumbnail_image":21,"og_image":134,"series":135,"authors":136},132,"Python Tuples: Why All Three Points Are the Same Point","python-tuples","Three points walk into a loop and come out identical. The list was never copied three times; the name was. A tuple makes that bug impossible.","2026-10-28T12:00:00.000Z",[88,133],"tuple","\u002Fog\u002Fpython-tuples.png",{"id":116,"title":117,"title_en":118,"slug":119},[137],{"authors_id":138},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},{"id":140,"title":141,"title_en":21,"slug":142,"excerpt":143,"excerpt_en":21,"has_translation":15,"published_at":144,"reading_time":111,"hidden_from_listing":15,"tags":145,"thumbnail_image":21,"og_image":147,"series":148,"authors":149},131,"Python Sets: the Duplicate That Keeps Coming Back","python-sets","A de-duplication loop that works on clean data leaves four tags where there should be two. The bug is case, and the fix is a `set`.","2026-10-28T11:00:00.000Z",[88,146],"set","\u002Fog\u002Fpython-sets.png",{"id":116,"title":117,"title_en":118,"slug":119},[150],{"authors_id":151},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},{"id":153,"title":154,"title_en":21,"slug":155,"excerpt":156,"excerpt_en":21,"has_translation":15,"published_at":157,"reading_time":111,"hidden_from_listing":15,"tags":158,"thumbnail_image":21,"og_image":160,"series":161,"authors":162},130,"Python Dictionaries: the Key That Isn't There","python-dictionary","A word counter dies the first time it meets a word twice. The fix is one line; what takes longer is knowing which of a dict's three lookups to reach for.","2026-10-28T10:00:00.000Z",[88,159],"dictionary","\u002Fog\u002Fpython-dictionary.png",{"id":116,"title":117,"title_en":118,"slug":119},[163],{"authors_id":164},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},{"id":166,"title":167,"title_en":168,"slug":169,"excerpt":170,"excerpt_en":171,"has_translation":12,"published_at":172,"reading_time":14,"hidden_from_listing":15,"tags":173,"thumbnail_image":21,"og_image":180,"series":181,"authors":183},129,"Biar RAG nggak kena 504: pisahin jalur query dan ingestion","So Your RAG Stops Hitting 504: Split the Query Path From Ingestion","rag-pisah-jalur-query-dan-ingestion","Satu service RAG yang merangkap parsing dan menjawab kueri bakal tumbang pas ada yang upload PDF 200 halaman. Obatnya dua jalur dan satu queue.","One RAG service that both parses uploads and answers queries will fall over the moment someone uploads a 200-page PDF. The cure is two paths and one queue.","2026-10-22T01:30:00.000Z",[174,175,176,177,178,179],"rag","cloud-run","cloud-tasks","gcp","serverless","arsitektur","\u002Fog\u002Frag-pisah-jalur-query-dan-ingestion.png",{"id":182,"title":35,"title_en":36,"slug":37},8,[184],{"authors_id":185},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},{"id":187,"title":188,"title_en":189,"slug":190,"excerpt":191,"excerpt_en":192,"has_translation":12,"published_at":193,"reading_time":111,"hidden_from_listing":15,"tags":194,"thumbnail_image":21,"og_image":201,"series":202,"authors":203},128,"Linear atau graf: cara mengatur banyak agent tanpa loop abadi","Linear or Graph: How to Run Many Agents Without an Eternal Loop","multi-agen-linear-atau-grafik","Satu agent dengan dua puluh tool gampang kacau. Begitu beberapa agent saling revisi, kamu butuh graf yang mendukung cycle dan angka yang membuatnya berhenti.","One agent with twenty tools gets messy fast. Once several agents revise each other, you need a graph that allows cycles and a number that makes it stop.","2026-10-21T16:05:00.000Z",[195,196,197,198,199,200],"multi-agent","langgraph","autogen","orkestrasi","checkpoint","llm","\u002Fog\u002Fmulti-agen-linear-atau-grafik.png",{"id":182,"title":35,"title_en":36,"slug":37},[204],{"authors_id":205},{"id":123,"name":124,"slug":125,"role":96,"profile_picture":21},[207,210,213,216,219,222],{"text":208,"depth":93,"id":209},"Apa Sih Sebenarnya Dead Letter Exchange Itu?","apa-sih-sebenarnya-dead-letter-exchange-itu",{"text":211,"depth":93,"id":212},"Kenapa Butuh Retry Bertingkat Sih?","kenapa-butuh-retry-bertingkat-sih",{"text":214,"depth":93,"id":215},"Pola Andalan: Delayed Retry via TTL dan DLX","pola-andalan-delayed-retry-via-ttl-dan-dlx",{"text":217,"depth":93,"id":218},"Contoh Implementasi di Python (Pika \u002F Aio-Pika)","contoh-implementasi-di-python-pika-aio-pika",{"text":220,"depth":93,"id":221},"Terus, Pesan yang Nyangkut di DLQ Diapain?","terus-pesan-yang-nyangkut-di-dlq-diapain",{"text":223,"depth":93,"id":224},"Jangan Biarkan Satu Pesan Gagal Nurunin Sistemmu","jangan-biarkan-satu-pesan-gagal-nurunin-sistemmu",1793295834020]