Conditional Requests และ Idempotency Keys
validator ตัวเดียวกับที่เร่งความเร็วในการอ่านก็ทำให้การเขียนปลอดภัยขึ้นด้วย มีสอง pattern ที่จัดการกรณียาก ๆ ได้: การแก้ไขแบบ concurrent และการ retry การสร้าง
Optimistic concurrency ด้วย If-Match
หัวข้อที่มีชื่อว่า “Optimistic concurrency ด้วย If-Match”เมื่อ 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 จากนั้น client ก็ re-fetch เอาการแก้ไขของตัวเองมาวางทับบนเวอร์ชันปัจจุบัน แล้ว retry ใหม่ — ไม่มีการเขียนทับแบบเงียบ ๆ อีก
Idempotency keys สำหรับ POST
หัวข้อที่มีชื่อว่า “Idempotency keys สำหรับ POST”จำไว้ว่า POST ไม่เป็น idempotent ดังนั้นการ retry การสร้างอาจทำให้เกิดข้อมูลซ้ำได้ idempotency key แก้ปัญหานี้: client สร้าง key ที่ไม่ซ้ำกันและส่งมาพร้อม request; server จัดเก็บผลลัพธ์ไว้กับ key นั้นและคืนผลลัพธ์ เดิม สำหรับทุก request ที่ซ้ำ
ธรรมเนียมปฏิบัติทั่วไปคือ request header ชื่อ Idempotency-Key; server จะเก็บการ mapping ไว้ช่วงเวลาหนึ่ง (เช่น 24 ชั่วโมง) เพื่อให้การ retry ภายในช่วงเวลานั้นปลอดภัย
| Pattern | ป้องกันอะไร | ทำงานอย่างไร |
|---|---|---|
Conditional PUT (If-Match) | lost update — สอง client แก้ resource เดียวกัน | ส่ง ETag, server reject ถ้า resource เปลี่ยนแล้ว |
| Idempotency Key | duplicate 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-Keyheader สำหรับ POST /charges, POST /payment_intents- เก็บ key ใน client storage — retry ด้วย key เดิมได้อย่างปลอดภัย
GitHub API:
If-Matchheader สำหรับ update operation ที่ต้องการ optimistic locking- 412 Precondition Failed เมื่อ resource เปลี่ยนระหว่างที่ client อ่านและ update