การเรียกใช้ API การชำระเงินของคุณล้มเหลวเวลาตี 2 เป็นเพราะเครือข่ายขัดข้อง, ติดขีดจำกัดอัตรา (rate limit) หรือเซิร์ฟเวอร์ตาย? คำตอบจะเป็นตัวตัดสินว่าการลองใหม่จะช่วยบันทึกรายการ (transaction) หรือทำให้ลูกค้าถูกเรียกเก็บเงินสองครั้ง
การลองใหม่ (retries) เป็นรูปแบบความทนทาน (resilience pattern) ที่พบได้บ่อยที่สุดในระบบแบบกระจาย (distributed systems) และเป็นสิ่งที่ทำผิดพลาดบ่อยที่สุด การวนซ้ำรอบการเรียกใช้ HTTP ดูเหมือนจะเป็นการเขียนโปรแกรมเชิงป้องกัน แต่ถ้าทำผิดพลาด มันจะเปลี่ยนการหยุดทำงาน 30 วินาทีให้กลายเป็นการหยุดทำงาน 30 นาที เพราะมีลูกค้านับพันรายพยายามส่งคำขอไปยังเซิร์ฟเวอร์ที่กำลังมีปัญหาในเวลาเดียวกัน แต่ถ้าทำอย่างถูกต้อง การลองใหม่จะช่วยดูดซับความล้มเหลวชั่วคราวได้อย่างแนบเนียนจนผู้ใช้ของคุณไม่สังเกตเห็นเลย
คู่มือนี้ครอบคลุมตรรกะการลองใหม่ (retry logic) ที่ระบบในสายการผลิต (production systems) ใช้: รหัสสถานะใดที่ควรรองรับการลองใหม่ สูตรการหน่วงเวลาแบบ exponential backoff พร้อม full jitter, เฮดเดอร์ Retry-After, กุญแจ idempotency (idempotency keys), งบประมาณการลองใหม่ (retry budgets) และ circuit breakers นอกจากนี้ คุณจะได้เห็นวิธีพิสูจน์ว่าไคลเอ็นต์ของคุณทำงานได้อย่างถูกต้องโดยการจำลองข้อผิดพลาด 429 และ 503 ด้วย mock server ของ Apidog เพราะรูปแบบการลองใหม่ที่คุณไม่เคยทดสอบกับเซิร์ฟเวอร์ที่ล้มเหลวก็เป็นเพียงการคาดเดา ไม่ใช่การออกแบบ ทีมที่สร้าง ตรรกะการลองใหม่สำหรับ API ด้าน FinTech เรียนรู้เรื่องนี้ด้วยวิธีที่แพง คุณไม่จำเป็นต้องเจอแบบนั้น
ทำไมการลองใหม่อย่างซื่อๆ (Naive Retries) ถึงทำให้การหยุดทำงานแย่ลง
ลองนึกภาพบริการที่จัดการคำขอ 1,000 คำขอต่อวินาที มันสะดุดไปห้าวินาที ลูกค้าทุกคนลองใหม่ทันที คนละสามครั้ง ความต้องการ 1,000 คำขอต่อวินาทีของคุณจะกลายเป็น 4,000 คำขอต่อวินาทีที่มุ่งเป้าไปที่เซิร์ฟเวอร์ที่กำลังมีปัญหาอยู่แล้ว มันล้มเหลวโดยสมบูรณ์ ตอนนี้ลูกค้าทุกคนลองใหม่อีกครั้ง
ลูปความคิดเห็น (feedback loop) นั้นมีชื่อว่า: retry storm (พายุการลองใหม่) การรุมเร้าพร้อมกันเมื่อเซิร์ฟเวอร์กลับมาใช้งานได้คือ thundering herd (ฝูงชนที่ส่งเสียงดังกึกก้อง) หนังสือ SRE ของ Google กล่าวถึงรูปแบบนี้ในบทที่ว่าด้วย การจัดการความล้มเหลวแบบต่อเนื่อง (cascading failures): การลองใหม่ที่ไม่มี backoff จะเพิ่มภาระงานในเวลาที่ระบบไม่สามารถรับมือได้ และสามารถทำให้บริการหยุดทำงานเป็นเวลานานหลังจากที่ข้อผิดพลาดเดิมได้รับการแก้ไขแล้ว
ข้อบกพร่องในการออกแบบสองประการที่ทำให้เกิด retry storm ส่วนใหญ่:
- ไม่มีการหน่วงเวลาระหว่างการลอง. การลองใหม่ทันทีจะเพิ่มภาระงานในช่วงเวลาที่แย่ที่สุด
- การหน่วงเวลาแบบคงที่. หากลูกค้าทุกคนรอหนึ่งวินาทีเท่ากัน พวกเขาทั้งหมดจะกลับมาพร้อมกัน เซิร์ฟเวอร์จะได้รับคลื่นของทราฟฟิกที่ซิงโครไนซ์กัน แทนที่จะเป็นการเพิ่มขึ้นอย่างราบรื่น
วิธีแก้ปัญหาไม่ใช่ "ห้ามลองใหม่" วิธีแก้ปัญหาคือการลองใหม่อย่างเลือกสรร ด้วยการหน่วงเวลาแบบสุ่มที่เพิ่มขึ้น และมีขีดจำกัดสูงสุดที่แน่นอนว่าการลองใหม่ของคุณจะเพิ่มภาระงานพิเศษได้มากแค่ไหน
ลองใหม่ความล้มเหลวเหล่านี้ ห้ามลองใหม่ความล้มเหลวเหล่านั้นเด็ดขาด
ก่อนการคำนวณ backoff ใดๆ ไคลเอ็นต์ของคุณต้องมีตารางการตัดสินใจ การลองใหม่คำขอที่เซิร์ฟเวอร์ปฏิเสธว่าไม่ถูกต้องไปแล้วนั้นเป็นการสิ้นเปลืองความจุและทำให้บันทึก (logs) สกปรก การลองใหม่ข้อผิดพลาดชั่วคราวคือจุดประสงค์ทั้งหมด
ลองใหม่สิ่งเหล่านี้:
| สัญญาณ | ความหมาย |
|---|---|
| 429 Too Many Requests | คุณติดขีดจำกัดอัตรา (rate limit) ถอยห่างออกไปแล้วค่อยกลับมาใหม่ช้าๆ |
| 502 Bad Gateway | ฮอปต้นน้ำ (upstream hop) ส่งข้อมูลที่ไม่ถูกต้องกลับมา มักจะเป็นความผิดพลาดชั่วคราว |
| 503 Service Unavailable | เซิร์ฟเวอร์โอเวอร์โหลดหรือกำลังรีสตาร์ท |
| 504 Gateway Timeout | การพึ่งพาต้นน้ำ (upstream dependency) ทำงานช้าเกินไป |
| การรีเซ็ตการเชื่อมต่อ, ข้อผิดพลาด DNS, หมดเวลาซ็อกเก็ต | คำขออาจไม่เคยมาถึง |
504 Gateway Timeout ต้องการการดูแลเป็นพิเศษ: ต้นทางอาจประมวลผลคำขอของคุณไปแล้ว แม้ว่าเกตเวย์จะหยุดรอไปแล้วก็ตาม ความแตกต่างนี้มีความสำคัญเมื่อเราพูดถึง idempotency
ห้ามลองใหม่สิ่งเหล่านี้เด็ดขาด:
| สัญญาณ | ความหมาย |
|---|---|
| 400 Bad Request | เพย์โหลดของคุณมีรูปแบบไม่ถูกต้อง และมันจะยังคงมีรูปแบบไม่ถูกต้องในการลองใหม่ครั้งหน้า |
| 401 Unauthorized | ข้อมูลประจำตัวของคุณไม่ถูกต้องหรือหมดอายุ รีเฟรชโทเค็น อย่าวนซ้ำ |
| 403 Forbidden | คุณไม่มีสิทธิ์ การลองใหม่จะไม่ให้สิทธิ์นั้น |
| 422 Unprocessable Entity | การตรวจสอบความถูกต้องล้มเหลว แก้ไขข้อมูล ไม่ใช่เวลา |
กฎคือ: ลองใหม่เมื่อความล้มเหลวเกี่ยวกับสถานะของเซิร์ฟเวอร์หรือเครือข่าย ล้มเหลวอย่างรวดเร็วเมื่อความล้มเหลวเกี่ยวกับคำขอของคุณ รหัส 429 อยู่ตรงกลาง; มันสามารถลองใหม่ได้ แต่ก็เป็นสัญญาณว่าอัตราคำขอโดยรวมของคุณจำเป็นต้องได้รับการปรับปรุง ซึ่งเป็นปัญหา การจำกัดอัตรา (rate limiting) ที่ต้องแก้ไขก่อนลูปการลองใหม่ใดๆ
สูตร exponential backoff และทำไม jitter จึงสำคัญ
Exponential backoff หมายถึงการลองใหม่แต่ละครั้งจะรอนานกว่าครั้งก่อน โดยจะเพิ่มเป็นสองเท่าโดยค่าเริ่มต้น:
delay = base * 2^retry_count
ด้วยค่าฐาน 500 มิลลิวินาที จะได้ 0.5 วินาที, 1 วินาที, 2 วินาที, 4 วินาที, 8 วินาที เพิ่มขีดจำกัด (เช่น 30 วินาที) เพื่อไม่ให้การหน่วงเวลานานเป็นนาที:
delay = min(cap, base * 2^retry_count)
สิ่งนี้ช่วยแก้ปัญหา "การรุมเร้า" แต่ไม่ใช่ปัญหาการซิงโครไนซ์ หากลูกค้า 5,000 รายล้มเหลวพร้อมกัน การใช้ exponential backoff ธรรมดาจะทำให้ทั้ง 5,000 รายกลับมาทำงานที่ t=0.5s, จากนั้น t=1s, จากนั้น t=2s ยังคงเป็นคลื่น ยังคงเป็นฝูงชน แต่เป็นฝูงชนที่มีมารยาทมากขึ้น
Jitter ทำลายการซิงโครไนซ์โดยการสุ่มค่าการหน่วงเวลา บล็อก AWS Architecture ได้วิเคราะห์ตัวเลขใน การวิเคราะห์ exponential backoff และ jitter โดยจำลองลูกค้าที่แข่งขันกันกับทรัพยากรที่มีการแย่งชิง Backoff ที่ไม่มี jitter ยังคงสร้างสไปค์การเรียกใช้ที่จับกลุ่มกัน Full jitter ซึ่งเลือกค่าการหน่วงเวลาแบบสุ่มระหว่างศูนย์ถึงขีดจำกัด exponential สร้างทั้งจำนวนการเรียกใช้ทั้งหมดน้อยที่สุดและเวลาเสร็จสิ้นที่สั้นที่สุดใกล้เคียง:
delay = random_between(0, min(cap, base * 2^retry_count))
ผลลัพธ์นั้นทำให้ผู้คนประหลาดใจ การสุ่มทั้งหมดลงไปถึงศูนย์ให้ความรู้สึกไม่เรียบร้อยเมื่อเทียบกับตารางการเพิ่มเป็นสองเท่าที่แม่นยำ แต่การกระจายไคลเอ็นต์อย่างสม่ำเสมอทั่วทั้งช่วงเวลาคือสิ่งที่ทำให้ภาระงานของเซิร์ฟเวอร์คงที่ การวิเคราะห์ของ AWS ยังได้ทดสอบ "equal jitter" (ครึ่งหนึ่งคงที่, ครึ่งหนึ่งสุ่ม) และ "decorrelated jitter"; full jitter และ decorrelated jitter ได้ผลดีกว่า และ full jitter เป็นวิธีที่ง่ายที่สุดในการเขียนให้ถูกต้อง ใช้เป็นรูปแบบการลองใหม่เริ่มต้นของคุณ เว้นแต่คุณจะมีข้อมูลการวัดที่บอกเป็นอย่างอื่น
ให้ความสำคัญกับ Retry-After เมื่อเซิร์ฟเวอร์แจ้งคุณ
Backoff คือการที่ไคลเอ็นต์ของคุณคาดเดาว่าจะรอนานแค่ไหน บางครั้งเซิร์ฟเวอร์ก็ขจัดความจำเป็นในการคาดเดานั้น เฮดเดอร์ Retry-After ซึ่งกำหนดไว้สำหรับการตอบกลับ 429 และ 503 จะระบุจำนวนวินาทีหรือวันที่แบบ HTTP:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
เมื่อมีเฮดเดอร์นี้อยู่ มันจะแทนที่ค่า backoff ที่คุณคำนวณไว้ เซิร์ฟเวอร์รู้ว่าเมื่อใดที่ช่วงเวลาจำกัดอัตรา (rate-limit window) จะรีเซ็ตหรือการบำรุงรักษาจะสิ้นสุดลง ตารางเวลา exponential ของคุณไม่รู้ การที่ไคลเอ็นต์ละเว้น Retry-After เป็นหนึ่งในเหตุผลที่ผู้ให้บริการยกระดับจากการควบคุม (throttling) ไปสู่การแบนโดยสมบูรณ์ วิเคราะห์มัน เคารพมัน และยังคงใช้ขีดจำกัดและจำนวนการลองใหม่สูงสุดของคุณ เพื่อที่ Retry-After: 86400 ที่เป็นอันตรายหรือไม่สมบูรณ์จะไม่อาจทำให้ worker ของคุณค้างไปทั้งวันได้
Idempotency: เงื่อนไขเบื้องต้นสำหรับการลองใหม่คำขอ POST
นี่คือกับดักในรหัส 504 ที่กล่าวถึงก่อนหน้านี้ คำขอ GET, PUT และ DELETE เป็นแบบ idempotent ตามสัญญา: การส่งสองครั้งจะทำให้ระบบอยู่ในสถานะเดียวกัน แต่ POST ไม่ใช่ หาก POST /v1/payments หมดเวลาหลังจากเซิร์ฟเวอร์ประมวลผลไปแล้ว การลองใหม่ของคุณจะสร้างการชำระเงินครั้งที่สอง ขอแสดงความยินดี คุณได้สร้างเครื่องเรียกเก็บเงินซ้ำซ้อนที่มี uptime ที่ยอดเยี่ยม
วิธีแก้คือ idempotency key: ID ที่สร้างโดยไคลเอ็นต์ที่ไม่ซ้ำกัน (โดยปกติคือ UUID) ที่ส่งเป็นเฮดเดอร์ในการดำเนินการเชิงตรรกะแต่ละครั้ง เซิร์ฟเวอร์จะจัดเก็บคีย์พร้อมกับการตอบกลับครั้งแรกและเล่นการตอบกลับที่จัดเก็บไว้นั้นซ้ำสำหรับคำขอที่ซ้ำกันใดๆ คำขอ idempotent ของ Stripe ทำงานในลักษณะนี้ และ API การชำระเงินและการจัดเตรียมส่วนใหญ่ก็ทำตามมา
กฎสองข้อที่ทำให้คีย์ทำงานได้:
- การดำเนินการเดียวกัน, คีย์เดียวกัน. การลองใหม่ทุกครั้งของการชำระเงินเชิงตรรกะหนึ่งครั้งจะใช้คีย์เดิมซ้ำ การดำเนินการของผู้ใช้ใหม่จะได้รับคีย์ใหม่
- สร้างคีย์ก่อนการส่งครั้งแรก ไม่ใช่ภายในลูปการลองใหม่ มิฉะนั้นการลองใหม่แต่ละครั้งจะดูเหมือนเป็นการดำเนินการใหม่ และการป้องกันจะหายไป
หาก API ที่คุณเรียกใช้ไม่รองรับ idempotency keys อย่าลองใหม่การเขียนที่ไม่ใช่ idempotent โดยอัตโนมัติ ให้แสดงความล้มเหลวและปล่อยให้มนุษย์หรือกระบวนการกระทบยอด (reconciliation job) เป็นผู้ตัดสินใจ
งบประมาณการลองใหม่ (Retry budgets) และ Circuit breakers: ทางออกฉุกเฉิน
Backoff กำหนดว่าการลองใหม่จะเกิดขึ้นเมื่อใด แต่ไม่ได้จำกัดจำนวนครั้งที่เกิดขึ้น ในระหว่างการหยุดทำงานที่ยาวนาน แม้แต่ไคลเอ็นต์ที่มี jitter ที่ดีก็ยังสะสมภาระการลองใหม่ และการลองใหม่แบบหลายชั้นจะเพิ่มขึ้น: หาก API gateway ของคุณลองใหม่ 3 ครั้ง และไคลเอ็นต์บริการของคุณลองใหม่ 3 ครั้ง การคลิกของผู้ใช้หนึ่งครั้งอาจกลายเป็นการเรียกใช้ 9 คำขอ
กลไกสองอย่างที่จำกัดความเสียหาย:
งบประมาณการลองใหม่ (Retry budgets). แทนที่จะเป็น "3 ครั้งต่อคำขอ" ให้บังคับใช้ "การลองใหม่อาจเพิ่มภาระงานพิเศษได้ไม่เกิน 10%" โดยวัดจากช่วงเวลาแบบเลื่อน (sliding window) เมื่อใช้งบประมาณหมดแล้ว ความล้มเหลวจะถูกส่งกลับทันที สิ่งนี้ช่วยให้การขยายตัวของการลองใหม่ถูกจำกัด ไม่ว่าจะมีการร้องขอล้มเหลวกี่รายการพร้อมกัน Linkerd และ Envoy ทั้งคู่มีสิ่งนี้เป็นการกำหนดค่าระดับสูง
Circuit breakers. ติดตามอัตราความล้มเหลวของปลายทาง (downstream) แต่ละตัว เมื่อข้ามเกณฑ์ที่กำหนด เบรกเกอร์จะเปิด: การเรียกใช้จะล้มเหลวทันทีโดยไม่ต้องสัมผัสเครือข่าย หลังจากช่วงพัก (cooldown) คำขอตรวจสอบเล็กน้อยจะทดสอบว่าการพึ่งพา (dependency) ฟื้นตัวแล้วหรือไม่ก่อนที่เบรกเกอร์จะปิดอีกครั้ง ในขณะที่ backoff ช่วยชะลอการรุมเร้าอย่างสุภาพ เบรกเกอร์จะยกเลิกมัน การออกแบบการลองใหม่ที่จริงจังทุกครั้งจะใช้ทั้งสองอย่างร่วมกัน เพราะ backoff เพียงอย่างเดียวยังคงส่งทุกคำขอไปในที่สุด
ตัวอย่างพร้อมใช้งานในสายการผลิต (Production-Ready) ด้วย Python
นี่คือรูปแบบทั้งหมดในที่เดียว: การกรองสถานะที่สามารถลองใหม่ได้, full jitter, การรองรับ Retry-After, idempotency key และขีดจำกัดการลองใหม่ที่แน่นอน
import random
import time
import uuid
import requests
RETRYABLE = {429, 502, 503, 504}
BASE = 0.5 # seconds
CAP = 30.0 # ceiling on any single delay
MAX_RETRIES = 5
def create_payment(payload):
idempotency_key = str(uuid.uuid4()) # one key per logical payment
headers = {"Idempotency-Key": idempotency_key}
for retry_count in range(MAX_RETRIES + 1):
try:
resp = requests.post(
"https://api.acmepay.com/v1/payments",
json=payload, headers=headers, timeout=10,
)
if resp.status_code < 400:
return resp.json()
if resp.status_code not in RETRYABLE:
resp.raise_for_status() # 400/401/403/422: fail fast
retry_after = resp.headers.get("Retry-After")
except (requests.ConnectionError, requests.Timeout):
retry_after = None # network fault: fall through to backoff
if retry_count == MAX_RETRIES:
raise RuntimeError("payment failed after all retries")
if retry_after and retry_after.isdigit():
delay = min(CAP, float(retry_after))
else:
delay = random.uniform(0, min(CAP, BASE * 2 ** retry_count))
time.sleep(delay)
สิ่งที่ควรสังเกต: คีย์ถูกสร้างขึ้นครั้งเดียว นอกลูป Retry-After มีความสำคัญกว่า backoff ที่คำนวณไว้ แต่ก็ยังคงเคารพขีดจำกัด สถานะที่ไม่สามารถลองใหม่ได้จะส่งข้อผิดพลาดทันที หากคุณใช้ JavaScript, ไลบรารี axios-retry จะให้รูปแบบเดียวกันด้วย hooks retryCondition และ retryDelay; ตารางการตัดสินใจยังคงเหมือนเดิม
วิธีทดสอบพฤติกรรมการลองใหม่ ก่อนที่ระบบในสายการผลิต (Production) จะทำแทนคุณ
ทีมส่วนใหญ่ปล่อยโค้ดการลองใหม่ที่ไม่เคยมีการรันส่วนของความล้มเหลวเลยแม้แต่ครั้งเดียว Happy path ได้รับการทดสอบแล้ว แต่เส้นทาง 503 กลับทำงานเป็นครั้งแรกในระหว่างการหยุดทำงานจริง คุณสามารถทำได้ดีกว่านั้นด้วยฟีเจอร์สองอย่างของ Apidog
จำลองความล้มเหลวด้วย mock server. smart mock ของ Apidog ช่วยให้คุณสามารถกำหนด endpoint เช่น /v1/payments และเขียนสคริปต์การตอบกลับได้ ทำให้มันคืนค่า 503 สำหรับการเรียกใช้สองครั้งแรกและ 200 ในครั้งที่สาม หรือคืนค่า 429 พร้อม Retry-After: 5 หรือเพิ่มการหน่วงเวลา 15 วินาทีเพื่อกระตุ้นให้ไคลเอ็นต์ของคุณหมดเวลา ชี้ไคลเอ็นต์ของคุณไปยัง URL ของ mock และดูว่าลูปการลองใหม่จัดการแต่ละสถานการณ์อย่างไร โดยไม่จำเป็นต้องเกิดเหตุการณ์จริงในสายการผลิต
ยืนยันพฤติกรรมของไคลเอ็นต์ด้วย test scenarios. test scenarios ของ Apidog เชื่อมโยงคำขอเข้ากับการยืนยัน (assertions) และการตรวจสอบเวลา สร้าง scenario ที่เรียกใช้ mock ที่มีข้อผิดพลาดของคุณ และยืนยันว่าการเรียกใช้สำเร็จในที่สุด เวลาที่ผ่านไปทั้งหมดอยู่ในช่วง backoff ที่คุณคาดไว้ และมีการสร้างทรัพยากรขึ้นมาเพียงรายการเดียว (พิสูจน์ว่า idempotency key ของคุณทำงานได้) เชื่อมโยง scenario นี้เข้ากับ CI และตรรกะการลองใหม่ของคุณจะถูกทดสอบทุกครั้งที่ commit แทนที่จะเป็นทุกครั้งที่เกิดการหยุดทำงาน
นี่คือความแตกต่างระหว่าง "เราเพิ่มการลองใหม่แล้ว" กับ "เรายืนยันแล้วว่าไคลเอ็นต์ของเราสามารถรอดชีวิตจากการพึ่งพาที่ถูกจำกัดอัตราและล้มเหลวบางส่วน" ดาวน์โหลด Apidog ฟรี แล้วคุณจะสามารถมี mock server ที่ล้มเหลวทำงานกับไคลเอ็นต์ของคุณได้ภายในประมาณสิบนาที
คำถามที่พบบ่อย (FAQ)
ฉันควรลองใหม่รหัส 429 หรือไม่?
ใช่ และนี่เป็นสถานะเดียวที่เซิร์ฟเวอร์มักจะบอกคุณว่าควรทำอย่างไร อ่านเฮดเดอร์ Retry-After และรออย่างน้อยตามนั้น; ถ้าไม่มีเฮดเดอร์ ให้ใช้ exponential backoff พร้อม jitter นอกจากนี้ ให้ถือว่า 429 ที่เกิดขึ้นซ้ำๆ เป็นสัญญาณที่บ่งบอกว่าต้องแก้ไขอัตราคำขอของคุณด้วยการจำกัดฝั่งไคลเอ็นต์ (client-side throttling) หรือแคช ไม่ใช่การทำงานปกติ
Full jitter คืออะไร?
Full jitter จะเลือกการหน่วงเวลาการลองใหม่แต่ละครั้งแบบสุ่มอย่างสม่ำเสมอระหว่างศูนย์กับขีดจำกัด exponential: random(0, min(cap, base * 2^n)) ช่วยป้องกันคลื่นการลองใหม่ที่ซิงโครไนซ์จากไคลเอ็นต์จำนวนมาก ในการจำลองของ AWS มันเอาชนะ plain backoff และ equal jitter ได้ทั้งในด้านจำนวนการเรียกใช้ทั้งหมดและเวลาที่ใช้ในการดำเนินการ ซึ่งเป็นเหตุผลว่าทำไมมันถึงเป็นค่าเริ่มต้นใน AWS SDKs
การลองใหม่คำขอ POST ปลอดภัยหรือไม่?
ปลอดภัยเฉพาะเมื่อคำขอนั้นเป็น idempotent ในทางปฏิบัติ ซึ่งสำหรับ POST หมายถึงการส่ง idempotency key ที่เซิร์ฟเวอร์ใช้ในการกำจัดข้อมูลซ้ำซ้อน หากไม่มีคีย์ การลองใหม่หลังจากหมดเวลาอาจทำให้เกิดการซ้ำซ้อนของการชำระเงิน คำสั่งซื้อ หรือบันทึก เพราะเซิร์ฟเวอร์อาจประมวลผลคำขอที่คุณคิดว่าล้มเหลวไปแล้ว AI agents ที่เรียกใช้ write API พบปัญหานี้อยู่เสมอ; รูปแบบการกู้คืนข้อผิดพลาดของ agent ก็เหมือนกับที่กล่าวถึงในที่นี้: การเขียนแบบมีคีย์ (keyed writes), การลองใหม่แบบมีขีดจำกัด (capped retries) และ circuit breaker
ฉันควรลองใหม่กี่ครั้ง?
สามถึงห้าครั้งก็สามารถจัดการข้อผิดพลาดชั่วคราวได้เกือบทั้งหมด; หลังจากนั้น อัตราความสำเร็จจะคงที่ ในขณะที่ภาระงานและความล่าช้ายังคงเพิ่มขึ้นเรื่อยๆ จับคู่ขีดจำกัดต่อคำขอเข้ากับงบประมาณการลองใหม่ทั่วโลก (เช่น การลองใหม่อาจเพิ่มปริมาณทราฟฟิกพิเศษ 10%) เพื่อให้การหยุดทำงานเต็มรูปแบบไม่สามารถเพิ่มภาระงานของคุณได้ หากการพึ่งพา (dependency) ยังคงหยุดทำงานหลังจากที่คุณลองใหม่ครั้งสุดท้าย นั่นเป็นขอบเขตของ circuit-breaker ไม่ใช่ขอบเขตของการลองใหม่
