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

Idempotency & Safety

มีสองคุณสมบัติที่ตัดสินว่า request หนึ่งจะทำซ้ำได้อย่างปลอดภัยหรือไม่ สองตัวนี้สับสนกันง่ายมาก เรามาแยกให้ชัดกันก่อน

  • Safe — request ไม่มีผลที่สังเกตได้ต่อ state ของ server ทำได้แค่อ่านอย่างเดียว ตัวอย่างเช่น GET, HEAD และ OPTIONS
  • Idempotent — ส่ง request เดิม N ครั้งแล้ว server ลงเอยที่ state เดียวกับส่งครั้งเดียว GET, HEAD, OPTIONS, PUT และ DELETE เป็น idempotent ส่วน POST และ (โดยทั่วไป) PATCH ไม่เป็น

ทุก method ที่ safe ล้วน idempotent แต่ไม่ใช่ทุก method ที่ idempotent จะ safe — DELETE เปลี่ยนแปลง state แต่ก็ยัง idempotent

flowchart TD
  M{Method} --> GET[GET / HEAD: safe + idempotent]
  M --> PUTDEL[PUT / DELETE: idempotent, not safe]
  M --> POST[POST: neither]
  M --> PATCH[PATCH: usually neither]
ตำแหน่งของแต่ละ method บนตาราง safe/idempotent

เครือข่ายทำ response หายได้ เมื่อ client (หรือ proxy หรือ load balancer) ไม่ได้รับการตอบกลับ ก็มีสิทธิ์ยิงซ้ำ ถ้า method เป็น idempotent การ retry ก็ไม่มีอันตราย แต่ถ้าไม่ใช่ — อย่าง POST ที่ตัดเงินจากบัตร — การ retry แบบมืดบอดอาจตัดเงินซ้ำสองครั้ง

JavaScript

เมื่อคุณจำเป็นจริง ๆ ที่จะให้การสร้างที่ไม่ idempotent ทนต่อการ retry ได้ ให้ใช้ idempotency key: client ส่ง header Idempotency-Key ที่ไม่ซ้ำกันมา และ server จดจำผลลัพธ์ของ key นั้นไว้ เพื่อให้การส่งซ้ำคืนผลลัพธ์เดิมกลับไปแทนที่จะทำงานสองครั้ง รายละเอียดของกลไกนี้อยู่ใน Versioning & Caching

PropertyความหมายMethods
Safeไม่เปลี่ยน state ของ serverGET, HEAD, OPTIONS
Idempotentทำซ้ำกี่ครั้งผลเดิมGET, PUT, DELETE (+ safe methods)
Neitherอาจเปลี่ยน state และผลต่างกันPOST, PATCH

POST ที่ไม่ Idempotent โดยไม่มี Guard อาการ:

  • user กด submit form สองครั้ง — สร้าง order สองใบ
  • network retry ทำให้ charge บัตรสองครั้ง
  • ต้องการ idempotency key สำหรับ POST ที่ sensitive เช่น payment

PATCH ที่ไม่ Idempotent โดยตั้งใจ อาการ:

  • PATCH /accounts/\{id\}/balance ด้วย { "increment": 100 } — ทำซ้ำ = บวกซ้ำ
  • client retry ทำให้ balance เพิ่มผิด
  • ใช้ absolute value: { "balance": 1100 } แทน increment

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

Stripe API:

  • Idempotency-Key header สำหรับ POST /charges
  • ส่ง key เดิมซ้ำ — Stripe return response เดิม ไม่ charge ซ้ำ
  • เก็บ key ใน client และ retry ได้อย่างปลอดภัย

AWS S3:

  • PUT object เป็น idempotent — upload ไฟล์เดิมซ้ำ = overwrite ไม่ใช่ duplicate
  • ทำให้ retry ปลอดภัยสำหรับ large file upload
ข้อความใดเป็นจริง?
ทำไมการ retry POST ที่ไม่ idempotent จึงมีความเสี่ยง?
อะไรที่ทำให้การสร้างที่ไม่ idempotent ทนต่อการ retry ได้อย่างปลอดภัย?