กับดักในบทเรียน Backend
บทเรียนต่างๆ มักจะแสดงขั้นตอนที่ดูเรียบง่าย รับ webhook อัปเดตฐานข้อมูล ส่งค่า 200 OK กลับไป
โค้ดของคุณทำงานได้ดีในการทดสอบ เมื่อคุณนำขึ้น production คุณกลับพบข้อมูลซ้ำซ้อนในฐานข้อมูล ผู้ใช้ได้รับเครดิตซ้ำสองครั้ง และข้อมูลเริ่มสะสมจนล้น
บทเรียนเหล่านี้มักมองข้ามความเป็นจริงเรื่องความล้มเหลวของเครือข่าย (network failure)
ปัญหา: เครือข่ายที่ไม่เสถียร เครือข่ายสามารถล้มเหลวได้ เซิร์ฟเวอร์ของคุณอาจประมวลผลข้อมูลช้า หรือข้อผิดพลาดของ DNS อาจทำให้การตอบกลับ 200 OK ของคุณส่งไปไม่ถึงผู้ส่ง
เมื่อบริการหนึ่งไม่ได้รับการยืนยันจากคุณ มันจะพยายามใหม่ (retry) โดยการส่ง webhook เดิมซ้ำมาอีกครั้ง หากโค้ดของคุณยอมรับทุกคำขอ คุณก็จะสร้างข้อมูลซ้ำซ้อนขึ้นมา
วิธีแก้ปัญหา: Idempotency Idempotency หมายถึงการที่การส่งคำขอที่เหมือนกันหลายๆ ครั้ง ให้ผลลัพธ์เหมือนกับการส่งคำขอเพียงครั้งเดียว
ลองนึกถึงปุ่มกดลิฟต์ การกดปุ่มชั้น 5 เพียงครั้งเดียวเป็นการบอกลิฟต์ว่าต้องไปที่ไหน การกดปุ่มเดิมสิบครั้งก็ไม่ได้ทำให้ลิฟต์วิ่งไปถึงชั้น 50 ผลลัพธ์ยังคงเหมือนเดิม
Webhook ของคุณต้องทำงานเหมือนกับปุ่มนั้น
วิธีแก้ไข ทำตามขั้นตอนเหล่านี้เพื่อสร้าง webhook ที่ปลอดภัย:
- ค้นหา ID ที่ไม่ซ้ำกัน (unique ID) ของเหตุการณ์นั้น
- ตรวจสอบในฐานข้อมูลว่ามี ID นั้นอยู่แล้วหรือไม่ก่อนที่จะเริ่มทำอะไร
- หากพบ ID นั้นอยู่แล้ว ให้หยุดทำงาน และส่งค่า 200 OK กลับไป เพื่อให้ผู้ส่งหยุดพยายามส่งซ้ำ
- หากเป็น ID ใหม่ ให้เริ่มประมวลผลข้อมูล
- บันทึก ID ของเหตุการณ์ลงในฐานข้อมูลของคุณทันที
ตัวอย่างตรรกะใน Node.js:
const eventId = req.body.event_id;
const existingEvent = await db.processedEvents.findUnique({
where: { id: eventId }
});
if (existingEvent) {
return res.status(200).send('Already processed');
}
await updateUserData(req.body.data);
await db.processedEvents.create({ data: { id: eventId } });
return res.status(200).send('Success');
การสร้างระบบสำหรับสภาวะที่สมบูรณ์แบบนั้นเป็นเรื่องง่าย แต่การสร้างระบบเพื่อรองรับความล้มเหลวต่างหากคือวิศวกรรมที่แท้จริง
คุณเคยเจอปัญหาข้อมูลซ้ำจากการ retry บ้างไหม? และคุณจัดการกับ idempotency อย่างไร?