ข้ามไปยังเนื้อหา

Conditional Requests และ Idempotency Keys

validator ตัวเดียวกับที่เร่งความเร็วในการอ่านก็ทำให้การเขียนปลอดภัยขึ้นด้วย มีสอง pattern ที่จัดการกรณียาก ๆ ได้: การแก้ไขแบบ concurrent และการ retry การสร้าง

เมื่อ client สองตัวแก้ไข resource เดียวกัน ตัวที่สองอาจเขียนทับตัวแรกโดยไม่รู้ตัว นี่คือปัญหา “lost update” ซึ่ง conditional write ป้องกันได้ วิธีคือ client ส่ง ETag ที่เห็นล่าสุดมาใน If-Match แล้ว server จะยอมเขียนก็ต่อเมื่อ resource ยังตรงกันอยู่ ถ้าเปลี่ยนไปแล้ว server จะคืน 412 Precondition Failed:

sequenceDiagram
  participant A as Client A
  participant S as Server
  A->>S: PUT /articles/42 (If-Match: "v7")
  alt still "v7"
    S-->>A: 200 OK (now "v8")
  else changed to "v8"
    S-->>A: 412 Precondition Failed
  end
If-Match ปฏิเสธการเขียนที่สร้างบนเวอร์ชันที่ stale

จากนั้น client ก็ re-fetch เอาการแก้ไขของตัวเองมาวางทับบนเวอร์ชันปัจจุบัน แล้ว retry ใหม่ — ไม่มีการเขียนทับแบบเงียบ ๆ อีก

จำไว้ว่า POST ไม่เป็น idempotent ดังนั้นการ retry การสร้างอาจทำให้เกิดข้อมูลซ้ำได้ idempotency key แก้ปัญหานี้: client สร้าง key ที่ไม่ซ้ำกันและส่งมาพร้อม request; server จัดเก็บผลลัพธ์ไว้กับ key นั้นและคืนผลลัพธ์ เดิม สำหรับทุก request ที่ซ้ำ

JavaScript

ธรรมเนียมปฏิบัติทั่วไปคือ request header ชื่อ Idempotency-Key; server จะเก็บการ mapping ไว้ช่วงเวลาหนึ่ง (เช่น 24 ชั่วโมง) เพื่อให้การ retry ภายในช่วงเวลานั้นปลอดภัย

Patternป้องกันอะไรทำงานอย่างไร
Conditional PUT (If-Match)lost update — สอง client แก้ resource เดียวกันส่ง ETag, server reject ถ้า resource เปลี่ยนแล้ว
Idempotency Keyduplicate operation — retry สร้าง resource ซ้ำclient ส่ง unique key, server deduplicate

ไม่มี Optimistic Locking บน Write Operation อาการ:

  • user A และ user B เปิด edit form ของ record เดียวกัน
  • user A save ก่อน user B save ทับโดยไม่รู้
  • ใช้ If-Match: <etag> — server return 412 ถ้า ETag ไม่ match

Idempotency Key ที่ Client Generate ใหม่ทุกครั้ง อาการ:

  • retry logic generate UUID ใหม่ทุก attempt
  • server ถือว่าเป็น request ใหม่ทุกครั้ง — ยังสร้างซ้ำ
  • generate idempotency key ครั้งเดียวต่อ operation แล้ว reuse เมื่อ retry

💡 ตัวอย่างจากของจริง

Stripe API:

  • Idempotency-Key header สำหรับ POST /charges, POST /payment_intents
  • เก็บ key ใน client storage — retry ด้วย key เดิมได้อย่างปลอดภัย

GitHub API:

  • If-Match header สำหรับ update operation ที่ต้องการ optimistic locking
  • 412 Precondition Failed เมื่อ resource เปลี่ยนระหว่างที่ client อ่านและ update
conditional write แบบ If-Match ป้องกันปัญหาใด?
เมื่อ precondition ล้มเหลวเพราะ resource เปลี่ยนไป ควรใช้ status ใด?
idempotency key ทำให้การ retry POST ปลอดภัยได้อย่างไร?