Skip to main content
System design langkah demi langkah · Pelajaran 10 dari 11 · 14 menit

Apa Itu Idempotency? Retry Aman dan Idempotency Key

Apa itu idempotency: method HTTP safe vs idempotent, kenapa retry membutuhkannya, cara kerja idempotency key, idempotent consumer, dan upsert.

Oleh Matthews WongDitinjau pada

Poin utama

Idempotency berarti menjalankan sebuah operasi dua kali punya efek yang sama dengan menjalankannya sekali. Ini penting karena jaringan bisa putus di tengah request: setelah timeout, klien tidak tahu apakah pekerjaannya sudah terjadi, jadi ia melakukan retry. GET, PUT, dan DELETE idempotent sejak definisinya; POST tidak, jadi API menambahkan idempotency key agar server memutar ulang jawaban pertamanya alih-alih mengulang pekerjaannya.

Setelah pelajaran ini, Anda bisa

  • Membedakan method safe dari method idempotent, dan tahu request mana yang boleh di-retry sendiri oleh klien setelah timeout.
  • Merancang alur idempotency key: siapa yang membuat key, apa yang disimpan server, berapa lama, dan apa jawaban untuk duplikat yang masih diproses.
  • Membuat consumer dan script aman dijalankan ulang dengan upsert, tabel pesan yang sudah diproses, dan write yang menyetel nilai alih-alih menambahkannya.
Lihat cara kerjanya

Ikuti diagramnya langkah demi langkah

Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.

Respons yang hilang, sebuah retry, dan idempotency key

Sebuah request pembayaran berhasil tetapi responsnya hilang; jika di-retry tanpa key, kartu ditagih dua kali, sedangkan dengan idempotency key server menyimpan jawaban pertamanya lalu memutarnya ulang, menjawab 409 untuk duplikat yang masih diproses, dan menolak key yang dipakai lagi dengan body berbeda.

Langkah 1 dari 7. Aplikasi checkout mengirim POST /charges ke payments API, yang mencatat setiap charge ke sebuah tabel. Balasannya kembali lewat jaringan. Penyimpanan key belum dipakai.

Tombol: ← → langkah · Home / End · K putar atau jeda

  1. Aplikasi checkout mengirim POST /charges ke payments API, yang mencatat setiap charge ke sebuah tabel. Balasannya kembali lewat jaringan. Penyimpanan key belum dipakai.

  2. API menagih kartu dan menjawab 201, tetapi jaringan menjatuhkan balasannya. Klien hanya melihat timeout, jadi ia tidak tahu apakah tagihannya sudah terjadi.

    Tebak langkah berikutnya Klien mengirim POST yang sama lagi, tanpa tambahan apa pun. Apa yang dilakukan API? (Mengembalikan jawaban pertama · Menagih kartu sekali lagi · Menolaknya sebagai duplikat)
  3. Tanpa key, API tidak bisa membedakan retry dari order baru, jadi ia menagih lagi. POST tidak idempotent: dua kiriman identik, dua efek.

  4. Perbaikan: klien membuat satu idempotency key sebelum percobaan pertama. API mengklaim key itu, menagih, lalu menyimpan hasilnya di bawah key tersebut. Balasan ini juga hilang.

    Idempotency-Key: "8e03978e-40d5-43e8-bc93-6894a57f9324"
  5. Retry membawa key yang sama. API menemukan k1 dan mengembalikan 201 yang tersimpan tanpa menagih lagi. Request sama, efek sama: endpoint ini sekarang idempotent.

    Idempotent-Replayed: trueTebak langkah berikutnya Order baru mengirim key k2, lalu duplikat dengan k2 tiba saat yang pertama masih berjalan. Apa yang seharusnya diterima duplikat itu? (Tagihan kedua · Jawaban tersimpan milik k1 · Konflik: coba lagi nanti)
  6. Key diklaim sebagai sedang diproses sebelum pekerjaan dimulai. Duplikat yang tiba di tengah proses mendapat 409 Conflict dan mencoba lagi nanti, bukan berlomba dengan request pertama.

    HTTP/1.1 409 Conflict
  7. Memakai k1 lagi dengan body berbeda adalah bug di klien: draft IETF menjawab 422, Resend 409. Key juga kedaluwarsa: Stripe menyimpannya minimal 24 jam.

    HTTP/1.1 422 Unprocessable Content

Apa itu idempotency?

Sebuah operasi disebut idempotent jika menjalankannya dua kali punya efek yang sama dengan menjalankannya sekali. Menyetel saklar lampu ke posisi mati itu idempotent; membalik posisinya tidak. RFC 9110, spesifikasi HTTP, mendefinisikannya untuk request: sebuah method idempotent jika efek yang dimaksud di server dari beberapa request identik sama dengan efek dari satu request saja.

Baca selengkapnya (1)

Dua kata dalam definisi itu yang paling penting. Efek, bukan respons: RFC 9110 mencatat bahwa mengulang request idempotent punya efek yang sama walaupun request pertama sudah berhasil, meski responsnya bisa berbeda. DELETE kedua untuk item yang sama tidak menemukan apa pun untuk dihapus, tetapi item itu tetap hilang. Yang dimaksud: server tetap bebas mencatat setiap request secara terpisah di log atau menyimpan riwayat revisi. Idempotency berbicara tentang apa yang diminta klien.

Safe vs idempotent: method HTTP mana yang termasuk?

Method safe bersifat read-only sejak definisinya: klien tidak meminta dan tidak mengharapkan perubahan state di server. RFC 9110 menetapkan GET, HEAD, OPTIONS, dan TRACE sebagai method safe. Setiap method safe juga idempotent, begitu pula PUT dan DELETE, yang mengubah state tetapi berakhir di state yang sama jika diulang. POST tidak termasuk keduanya. RFC 5789 menyebut PATCH juga tidak safe dan tidak idempotent, sambil mencatat bahwa PATCH bisa ditulis supaya idempotent.

Baca selengkapnya (1)

Perbedaan PUT dan POST ada pada niatnya. PUT membawa state baru yang lengkap untuk resource yang disebut klien, jadi mengirimnya dua kali tetap menghasilkan state yang sama. POST menyerahkan datanya ke resource untuk diproses menurut aturannya sendiri, misalnya membuat order baru, jadi setiap kiriman bisa membuat order lain. RFC 9110 sendiri menarik garisnya: service yang memilihkan URI untuk resource baru atas nama klien sebaiknya memakai POST, bukan PUT.

Kenapa retry membuat idempotency jadi soal kebenaran data?

Timeout itu ambigu. Request-nya mungkin tidak pernah sampai, server mungkin berhenti di tengah jalan, atau pekerjaannya sudah selesai dan hanya balasannya yang hilang. Panduan error Stripe menyatakannya dengan jelas: koneksi yang pulih tidak memberi tahu Anda apakah request awal berhasil. Klien pun tinggal punya dua pilihan buruk, menyerah dan mungkin kehilangan order, atau retry dan mungkin mengerjakannya dua kali.

Baca selengkapnya (1)

RFC 9110 menetapkan aturan lanjutannya. Klien boleh mengulang request idempotent secara otomatis setelah koneksi gagal. Klien sebaiknya tidak me-retry request non-idempotent secara otomatis, kecuali ia tahu request itu memang idempotent, atau tahu request awalnya tidak pernah dijalankan, dan proxy sama sekali tidak boleh me-retry request non-idempotent. Pelajaran reliability (pelajaran 5) membahas kapan dan seberapa sering retry; pelajaran ini membuat retry-nya aman, sehingga percobaan at-least-once tetap menghasilkan efek tepat satu kali.

Bagaimana idempotency key bekerja, dan apa yang disimpan server?

Klien membuat satu key unik untuk satu operasi, sebelum percobaan pertama, lalu mengirimnya di setiap retry lewat header Idempotency-Key. Pilihan yang umum adalah UUID acak: draft IETF menyarankan UUID atau identifier acak sejenis, Stripe menyarankan UUID V4, dan crypto.randomUUID() bisa membuatnya di browser maupun di Node. Buat key per operasi, jangan per percobaan, karena key baru terlihat seperti request baru. Stripe juga menyarankan menurunkan key dari sesuatu yang sudah dimiliki pengguna, misalnya ID keranjang belanja, untuk mencegah double submit.

Baca selengkapnya (3)

Server menyimpan catatan kecil per key: fingerprint dari body request, status pekerjaannya (masih diproses atau selesai), serta status code dan body yang dijawabnya. Pada request pertama, server mengklaim key, mengerjakan tugasnya, lalu menyimpan hasilnya. Pada retry, server mengembalikan hasil tersimpan tanpa mengerjakannya lagi, dan Stripe menandai balasan seperti ini dengan Idempotent-Replayed: true. Stripe baru menyimpan hasil setelah endpoint mulai berjalan, jadi request yang ditolak validasi bisa langsung di-retry, sedangkan 500 yang sudah tersimpan akan diputar ulang sebagai 500.

Key tidak disimpan selamanya, dan server sebaiknya mengumumkan berapa lama ia menyimpannya. Stripe menyimpannya minimal 24 jam dan bisa menghapusnya setelah itu, jadi key yang dipakai lagi belakangan bisa dijalankan sebagai request baru; Resend menyimpannya selama 24 jam. Ada dua kasus tepi yang perlu jawaban. Duplikat yang tiba saat request pertama masih berjalan mendapat 409 Conflict menurut draft IETF, dan panduan Stripe menyarankan menunggu lalu me-retry dengan key yang sama. Key yang dipakai lagi dengan body berbeda mendapat 422 menurut draft; Resend menjawab kedua kasus dengan 409 dan nama error yang berbeda.

Nama header ini berasal dari draft working group HTTPAPI di IETF. Per 2026-10-18, datatracker IETF mencatatnya sebagai Internet-Draft yang sudah kedaluwarsa, dengan revisi terakhir versi 07 pada 2025-10-15, jadi ia bukan standar. Meski begitu, Stripe dan Resend memakai nama header yang sama, sehingga nama itu pilihan default yang aman untuk API Anda sendiri.

Apa itu at-least-once delivery dan idempotent consumer?

Message queue menghadapi masalah yang sama dari sisi lain. Panduan reliability RabbitMQ menyebut acknowledgement menjamin at-least-once delivery, dan tanpanya hanya at-most-once yang dijamin. Consumer sebaiknya meng-ack pesan hanya setelah pekerjaannya selesai. Jika channel atau koneksinya tertutup lebih dulu, setiap delivery yang belum di-ack akan dimasukkan lagi ke antrean dan datang kembali dengan flag redelivered menyala. Publisher yang kehilangan confirm juga bisa mengirim satu pesan dua kali.

Baca selengkapnya (1)

Jadi duplikat itu hal biasa, dan RabbitMQ menyarankan merancang consumer agar idempotent alih-alih melakukan deduplikasi secara eksplisit. Flag redelivered hanya petunjuk: jika false, pesannya baru; jika true, consumer mungkin sudah pernah melihatnya. Resep yang umum adalah pola Idempotent Consumer di microservices.io: di dalam transaksi database yang sama dengan perubahan bisnisnya, catat ID setiap pesan yang sudah diproses. Pesan yang dikirim ulang akan menemukan ID-nya sudah tercatat, lalu consumer melewati pekerjaannya dan meng-ack pesan itu.

Apa itu idempotency alami, dan di mana Anda sudah memakainya?

Idempotency termurah tidak butuh key sama sekali: operasi write yang menyetel sebuah state, bukan mengubahnya sebesar suatu jumlah. Setel stok menjadi 11 boleh dijalankan dua kali; kurangi satu tidak boleh. PUT mengganti sebuah resource, jadi aman diulang. Di SQL, upsert melakukan hal yang sama untuk insert: INSERT dengan ON CONFLICT DO NOTHING, di PostgreSQL dan di SQLite sejak 3.24.0, melewati baris yang unique key-nya sudah ada, dan ON CONFLICT DO UPDATE mengubah insert menjadi update. PostgreSQL menjamin hasil insert-atau-update yang atomik untuk DO UPDATE, bahkan saat konkurensinya tinggi.

Baca selengkapnya (1)

Tool deklaratif idempotent sejak rancangannya, karena ia membandingkan state yang Anda deklarasikan dengan state yang ada. docker compose up hanya membuat ulang container sebuah service jika konfigurasi atau image-nya berubah setelah container itu dibuat, jadi up -d kedua tanpa perubahan apa pun membiarkan container yang sedang berjalan. Shell punya pembagian yang sama: chmod 600 menyetel mode secara absolut, mkdir -p tidak gagal jika direktorinya sudah ada, dan > mengganti isi file sedangkan >> menambahkan di akhir, jadi menjalankan ulang script yang memakai >> menggandakan setiap baris. Header scripts/setup-resend-templates.mjs di repo ini menyebut bahwa menjalankannya ulang akan memperbarui template: script itu mencarinya lewat alias yang tetap, bukan membuat template baru.

Cara situs ini melakukannya

Konfigurasi nyata di balik pelajaran ini

Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.

app/api/contact/route.ts:148-156
    if (error) {
      console.error("Resend send error:", error);
      return apiError({
        status: 502,
        code: "upstream_error",
        message: "Failed to send message.",
        hint: "The email provider rejected the message. Retry later, or email matthewswong2610@gmail.com directly.",
      });
    }

API kontak situs ini adalah POST non-idempotent yang jujur. Panggilan send tepat di atasnya (baris 138-146) memberi Resend satu argumen, yaitu pesannya, tanpa idempotency key, jadi setiap POST adalah email baru. SDK yang terpasang (resend 6.17.1) juga mengubah kegagalan jaringan menjadi nilai error yang sama, bukan melempar exception, jadi cabang ini tidak bisa membedakan pesan yang ditolak dari koneksi yang putus setelah emailnya mungkin sudah terkirim, padahal hint-nya menyuruh retry.

app/[locale]/(pages)/contact/page.tsx:279-286
      setShowSuccessModal(true);
      resetWizard();
    } catch (error) {
      console.error(error);
      setShowErrorModal(true);
    } finally {
      setIsSubmitting(false);
    }

Dari sinilah retry berasal. Kegagalan apa pun, termasuk koneksi yang putus, masuk ke catch dan membuka dialog yang bilang pesannya tidak terkirim dan meminta pengunjung mencoba lagi. Hanya jalur sukses yang memanggil resetWizard, jadi pesan yang sudah diketik masih ada dan satu tekan tombol Send lagi mengirimnya sebagai request baru. isSubmitting menonaktifkan tombol selama request berjalan, yang mencegah double click tetapi tidak mencegah retry.

.github/workflows/deploy.yml:226-244
            cat > .env <<'ENVEOF'
            GROQ_API_KEY=${{ secrets.GROQ_API_KEY }}
            RESEND_API_KEY=${{ secrets.RESEND_API_KEY }}
            CONTACT_FROM_EMAIL=${{ vars.CONTACT_FROM_EMAIL }}
            CONTACT_TO_EMAIL=${{ vars.CONTACT_TO_EMAIL }}
            IMAGE=${{ needs.build-and-push.outputs.image }}
            APP_PORT=${{ vars.APP_PORT || 3002 }}
            ENVEOF
            chmod 600 .env

            echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
            docker compose pull
            # Normal path updates the container in place. On the first cutover the
            # pre-existing (non-compose) container holds the name — remove it once,
            # then compose adopts the name going forward.
            docker compose up -d --remove-orphans || {
              docker rm -f matthewswong-portfolio 2>/dev/null || true
              docker compose up -d --remove-orphans
            }

Baca langkah deploy ini sebagai script yang harus aman dijalankan dua kali, dan itulah yang membuat menjalankan ulang job setelah deploy yang setengah jalan tidak berbahaya. cat > .env menulis ulang seluruh file setiap kali, sedangkan >> akan menambahkan salinan kedua dari setiap baris. chmod 600 menyetel mode secara absolut. docker compose up -d hanya membuat ulang container jika konfigurasi atau image-nya berubah. Bahkan fallback-nya aman diulang: rm -f dengan || true tetap berhasil, ada atau tidak ada container lamanya.

scripts/merge-blog-staging.mjs:12-23
 * Idempotent: a slug already in BLOG_POSTS_STATIC is skipped.
 *
 *   node scripts/merge-blog-staging.mjs [--dry-run]
 *
 * Design note — why this is plan-then-write rather than validate-then-write:
 * the previous version validated the staged CONTENT completely, then began
 * writing, and resolved each insertion anchor at write time. When a constant
 * moved module (REAL_IMAGE_SLUGS left blog-posts-static.ts), the third write
 * threw after messages/*.json and the post registry had already been updated —
 * a half-merged repo that a re-run would not fix, because by then the slug
 * existed in en.json and got skipped. Anchors are now resolved during
 * validation, so either every edit is possible or nothing is touched.

Idempotent dengan cara melewati: post yang sudah terdaftar tidak di-merge lagi. Catatan desainnya merekam bagaimana cara itu gagal. Satu write gagal setelah write sebelumnya sudah masuk, jadi penanda selesai sudah ada, run berikutnya melewati post itu, dan repo tertinggal setengah ter-merge. Perbaikannya adalah aturan di balik setiap job yang aman diulang: pastikan semua yang bisa gagal sudah diputuskan sebelum write pertama, sehingga job itu selesai sepenuhnya atau tidak tersentuh sama sekali.

Yang sering rusak

Kesalahan yang benar-benar membuang waktu

  • Key baru di setiap percobaan

    Membuat key di dalam loop retry menjadikan setiap retry sebuah request baru, dan perlindungannya hilang. Buat key sekali per operasi, simpan bersama operasi yang belum selesai, lalu pakai ulang di setiap retry, restart proses, dan reload halaman. Panduan Stripe memperingatkan bahwa key baru bisa memulai operasi kedua.

  • Cek key lalu simpan, dalam dua langkah terpisah

    Dua duplikat yang tiba bersamaan sama-sama tidak menemukan catatan untuk key itu, sama-sama mengerjakan tugasnya, dan pelanggan ditagih dua kali. Klaim key lebih dulu dalam satu langkah atomik yang diputuskan oleh unique constraint, tandai sebagai sedang diproses, dan hanya request yang memenangkan klaim yang mengerjakan tugasnya. Yang lain mendapat 409.

  • Penanda selesai yang ditulis sebelum pekerjaan selesai

    Lewati-jika-sudah-ada hanya idempotent jika penandanya berarti seluruh job sudah selesai. Jika langkah berikutnya gagal, run ulang akan selamanya melewati job yang setengah jadi, seperti yang pernah terjadi pada merge script repo ini. Tulis penandanya dalam transaksi yang sama dengan pekerjaannya, atau putuskan semua yang bisa gagal sebelum write pertama.

  • Menganggap 500 sebagai izin untuk mencoba dari awal

    Status 500 itu tidak pasti: operasinya mungkin sudah menimbulkan efek. Panduan Stripe menyarankan untuk tidak me-retry dengan key baru sampai hasil request awal jelas, dan berhenti me-retry operasi yang belum jelas setelah jendela 24 jam lalu merekonsiliasinya.

  • Yang akan saya ubah di sini: satu key per pesan kontak

    Form-nya bisa membuat crypto.randomUUID() sekali per pesan yang sudah diisi, mengirimnya bersama POST, dan menghapusnya hanya setelah sukses. Route-nya lalu meneruskan key itu ke emails.send sebagai idempotencyKey, yang disimpan Resend selama 24 jam, sehingga tekan tombol kedua setelah balasan hilang mengembalikan hasil pertama, bukan email kedua. Teks error-nya juga sebaiknya menyebut bahwa pesannya mungkin sudah terkirim.

Method HTTP menurut RFC 9110 dan RFC 5789: safe, idempotent, dan apa yang boleh dilakukan klien setelah timeout
GET, HEAD, OPTIONSYaYaRetry otomatis
PUTTidakYaRetry: menyetel state yang sama lagi
DELETETidakYaRetry: resource-nya tetap terhapus
POSTTidakTidakRetry hanya dengan idempotency key
PATCHTidakTidak, kecuali patch-nya dirancang begituRetry hanya jika idempotent atau memakai key
Coba sendiri

Lakukan di komputer Anda sendiri

Anda butuh Node 22 atau lebih baru, curl, dan sqlite3 versi command line (sudah terpasang di macOS; di Linux, pasang lewat package manager). Server pembayaran mainan di 127.0.0.1:4242 butuh 1,5 detik per charge, dan tidak ada API pembayaran atau email sungguhan yang terlibat.

  1. Simpan server ini sebagai server.mjs dan jalankan dengan node server.mjs di terminal tersendiri. Server mencetak jumlah charge setiap kali ada charge baru.

    // server.mjs: a toy payments API. Each charge takes 1.5 s. With an
    // Idempotency-Key header, the first result is stored and replayed.
    import { createHash } from "node:crypto";
    import http from "node:http";
    
    const charges = [];
    const keys = new Map(); // key -> { fingerprint, done, status, body }
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    
    function reply(res, status, body, headers = {}) {
      res.writeHead(status, { "Content-Type": "application/json", ...headers });
      res.end(JSON.stringify(body) + "\n");
    }
    
    http
      .createServer(async (req, res) => {
        if (req.method === "GET") return reply(res, 200, { charges: charges.length });
        let raw = "";
        for await (const chunk of req) raw += chunk;
        const key = req.headers["idempotency-key"];
        const fingerprint = createHash("sha256").update(raw).digest("hex");
        if (key) {
          const seen = keys.get(key);
          if (seen && seen.fingerprint !== fingerprint)
            return reply(res, 422, { error: "key reused with a different body" });
          if (seen && !seen.done)
            return reply(res, 409, { error: "a request with this key is in progress" });
          if (seen) return reply(res, seen.status, seen.body, { "Idempotent-Replayed": "true" });
          keys.set(key, { fingerprint, done: false }); // claim the key BEFORE the work
        }
        await sleep(1500); // the slow part, like a call to a card network
        const charge = { id: `ch_${charges.length + 1}`, body: raw };
        charges.push(charge);
        if (key) keys.set(key, { fingerprint, done: true, status: 201, body: charge });
        console.log("charges so far:", charges.length);
        reply(res, 201, charge);
      })
      .listen(4242, "127.0.0.1", () => console.log("listening on 127.0.0.1:4242"));
  2. Hilangkan sebuah balasan dengan sengaja. curl -m 1 menyerah setelah satu detik, sebelum charge 1,5 detik itu menjawab, jadi curl mencetak timeout (error 28) padahal server sudah menagih. Retry POST yang sama lalu baca hitungannya: 2 charge untuk satu order.

    curl -sS -m 1 -X POST http://127.0.0.1:4242/charges -d 'order=A-1'
    curl -sS -X POST http://127.0.0.1:4242/charges -d 'order=A-1'
    curl -sS http://127.0.0.1:4242/charges
  3. Ulangi dengan idempotency key yang dibuat sekali, sebelum percobaan pertama. Panggilan pertama timeout lagi; retry-nya mendapat 201 yang tersimpan dengan Idempotent-Replayed: true, dan hitungannya naik satu, bukan dua.

    KEY=$(node -p "crypto.randomUUID()")
    curl -sS -m 1 -X POST http://127.0.0.1:4242/charges -H "Idempotency-Key: $KEY" -d 'order=B-1'
    sleep 1
    curl -sS -i -X POST http://127.0.0.1:4242/charges -H "Idempotency-Key: $KEY" -d 'order=B-1'
    curl -sS http://127.0.0.1:4242/charges
  4. Kirim duplikat saat request pertama masih berjalan. Duplikatnya langsung mencetak 409; request aslinya mencetak 201 saat selesai.

    KEY=$(node -p "crypto.randomUUID()")
    curl -sS -w '%{http_code}\n' -X POST http://127.0.0.1:4242/charges -H "Idempotency-Key: $KEY" -d 'order=C-1' &
    sleep 0.2
    curl -sS -w '%{http_code}\n' -X POST http://127.0.0.1:4242/charges -H "Idempotency-Key: $KEY" -d 'order=C-1'
    wait
  5. Di terminal yang sama, pakai lagi key tadi dengan body berbeda. Server menolak dengan 422, sesuai saran draft IETF, alih-alih memutar ulang jawaban untuk request yang berbeda.

    curl -sS -w '%{http_code}\n' -X POST http://127.0.0.1:4242/charges -H "Idempotency-Key: $KEY" -d 'order=C-2'
  6. Sekarang idempotency alami di SQLite. Upsert menyisipkan sekali (changes 1) dan tidak melakukan apa-apa di kali kedua (changes 0). Mengurangi dua kali menyisakan 10; menyetel 11 dua kali tetap 11.

    sqlite3 :memory: <<'SQL'
    CREATE TABLE processed (message_id TEXT PRIMARY KEY);
    CREATE TABLE stock (sku TEXT PRIMARY KEY, qty INTEGER);
    INSERT INTO stock VALUES ('mug', 12);
    -- a redelivered message, handled twice
    INSERT INTO processed VALUES ('msg-42') ON CONFLICT (message_id) DO NOTHING;
    SELECT changes();
    INSERT INTO processed VALUES ('msg-42') ON CONFLICT (message_id) DO NOTHING;
    SELECT changes();
    -- add-by-amount twice, then set-a-value twice
    UPDATE stock SET qty = qty - 1 WHERE sku = 'mug';
    UPDATE stock SET qty = qty - 1 WHERE sku = 'mug';
    SELECT qty FROM stock;
    UPDATE stock SET qty = 11 WHERE sku = 'mug';
    UPDATE stock SET qty = 11 WHERE sku = 'mug';
    SELECT qty FROM stock;
    SQL
  7. Hentikan server dengan Ctrl+C di terminalnya. Semua datanya hanya di memori, jadi tidak ada yang tertinggal.

Kuis singkat

Tiga pertanyaan sebelum lanjut

Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada nilai, jadi tebak saja dengan bebas; jawaban yang salah disimpan di browser ini agar kursus bisa menanyakannya lagi nanti.

  1. 1.Method HTTP mana yang idempotent tetapi tidak safe?
  2. 2.POST dengan idempotency key k1 timeout, lalu klien melakukan retry. Kapan klien sebaiknya membuat key baru?
  3. 3.Sebuah consumer RabbitMQ menulis baris order dari sebuah pesan, lalu crash sebelum meng-ack. Apa yang terjadi berikutnya?
Dari pelajaran sebelumnya

Dua pertanyaan dari pelajaran sebelumnya

Mengingat kembali pelajaran lama setelah jeda membuatnya lebih melekat. Jawab dari ingatan sebelum mencari apa pun.

  1. 1.Sebuah dashboard menampilkan jumlah order secara live. Server mendorong pembaruan; browser tidak mengirim apa pun selain saat memuat halaman. Mana yang paling sedikit bagian bergeraknya?
  2. 2.Diminta merancang situs ini dalam interview, Anda langsung memilih Postgres dan Redis. Langkah mana yang Anda lewati?

Dari: 9. Belajar WebSocket dan SSE untuk Pemula, Termasuk Long Polling · 7. Contoh Studi Kasus System Design: Merancang Situs Ini

Pikirkan dulu

Jawab dalam hati, lalu cek

  1. 1.Kenapa DELETE idempotent walaupun panggilan kedua bisa menjawab berbeda?Lihat jawabannya

    Jawaban:Idempotency berbicara tentang efek yang dimaksud di server, bukan responsnya. Setelah satu atau dua kali DELETE, resource-nya tetap hilang; RFC 9110 menyebut respons untuk pengulangan bisa berbeda.

  2. 2.Apa yang disimpan server untuk setiap idempotency key, dan kapan?Lihat jawabannya

    Jawaban:Fingerprint dari body, status pekerjaannya, serta status dan body yang dijawabnya. Server mengklaim key sebagai sedang diproses sebelum mengerjakan tugasnya, sehingga duplikat yang masih jalan mendapat 409, lalu menyimpan hasilnya saat pekerjaan selesai.

  3. 3.Script deploy menambahkan isi ke .env dengan >>. Apa yang terjadi saat deploy dijalankan lagi, dan bagaimana perbaikannya?Lihat jawabannya

    Jawaban:Setiap baris muncul dua kali, dan file-nya membesar di setiap run. Tulis ulang seluruh file dengan >, seperti yang dilakukan deploy situs ini dengan cat > .env, sehingga setiap run menghasilkan file yang sama.

  4. 4.Kapan idempotency alami sudah cukup, dan kapan Anda butuh key?Lihat jawabannya

    Jawaban:Jika request menyebut resource-nya dan membawa state lengkapnya, seperti PUT, operasi set, atau upsert pada unique key alami, pengulangan akan berakhir di hasil yang sama. Jika setiap request membuat sesuatu yang baru, misalnya charge atau email, hanya key pilihan klien yang bisa membedakan retry dari request baru.

Pelajari lebih dalam

Bahasan mendalam terkait di situs ini