กดเผยแพร่สองครั้งตอนนี้เผยแพร่เพียงครั้งเดียว
คำขอเผยแพร่สามรายการสำหรับแอปเดียวกันมาถึง API ของเราห่างกัน 84 มิลลิวินาที และแต่ละรายการเริ่มการดีพลอยเต็มรูปแบบของแอปนั้น ทั้งหมดแข่งกันขึ้นไปยังโฮสติ้งเดียวกัน หลายรีลีสที่เกิดขึ้นล้มเหลวเพราะมีอีกรายการไปถึงก่อนแล้ว และสัญญาณเดียวที่มองเห็นได้คืออีเมล “release failed” ส่วนแอปเองสุดท้ายก็ทำงานได้ดี ตอนนี้การเผยแพร่ที่มาถึงในขณะที่อีกรายการกำลังทำงานให้แอปเดียวกันจะไม่เริ่มการสร้างครั้งที่สอง ฐานข้อมูลเป็นตัวตัดสินว่าคำขอไหนชนะ และรายการอื่นจะได้รับแจ้งความจริง: มีการเผยแพร่ที่กำลังทำงานอยู่แล้ว
เกิดอะไรขึ้น
เมื่อคุณเผยแพร่แอปบน Mythex API จะบันทึกการดีพลอยสำหรับแอปนั้น และขอให้ออร์เคสเตรเตอร์ของเรา ซึ่งเป็นบริการที่รันการสร้าง สร้างและปล่อยมัน การเผยแพร่สามารถเริ่มได้จากปุ่ม เผยแพร่ จากอีกแท็บของเบราว์เซอร์ หรือจากเอเจนต์เมื่อคุณขอให้มันเผยแพร่ การคลิกสองครั้ง สองแท็บ หรือคำขอของเอเจนต์ที่มาถึงสองครั้งอาจทำให้เกิดการเผยแพร่มากกว่าหนึ่งครั้งสำหรับแอปเดียวกันในเวลาใกล้เคียงกัน
ในกรณีนี้ สามรายการมาถึงภายใน 84 มิลลิวินาที แต่ละรายการรันการดีพลอยเต็มรูปแบบ บนแพลตฟอร์มโฮสติ้ง รีลีสสำหรับแอปเดียวกันถูกสร้างภายในวินาทีเดียวกัน และสามรายการล้มเหลวเพราะรายการอื่นกำลังดำเนินการอยู่แล้ว รีลีสหนึ่งที่ติดค้างอยู่ แต่แอปทำงานได้หลังจากนั้น
นั่นคือสิ่งที่ทำให้มองข้ามได้ง่าย ผู้ใช้เห็นแอปที่ทำงานได้หนึ่งแอป ความล้มเหลวปรากฏเฉพาะในอีเมล release-failure
ทำไมไม่มีอะไรหยุดมัน
โค้ดการเผยแพร่มีขั้นตอนที่ดูการดีพลอยที่กำลังดำเนินการอยู่สำหรับโปรเจกต์ แต่สิ่งที่มันทำคือทำเครื่องหมายทุกการดีพลอยที่กำลังดำเนินการว่าล้มเหลวก่อนเริ่ม ด้วยเหตุผล “แทนที่ด้วยการเผยแพร่ใหม่” มันตั้งใจไว้เพื่อเก็บกวาดการสร้างที่ค้างอยู่หลังจากโปรเซสที่พังไป
ในทางปฏิบัติ การเผยแพร่ที่สองซึ่งมาถึงขณะกำลังสร้างจะประกาศว่าอันแรกตายแล้วและเริ่มของตัวเอง ทั้งคู่รันจนเสร็จกับแอปเดียวกัน ไม่มี lock มีเพียงขั้นตอนที่เคลียร์ทางให้การแข่งขันอย่างแข็งขัน
มีอาการที่สองและแปลกกว่าสำหรับการเผยแพร่ครั้งแรกของแอป เมื่อยังไม่มีบันทึกการดีพลอยให้ใช้ซ้ำ การเผยแพร่ครั้งแรกสองครั้งพร้อมกันจะพยายามแทรกหนึ่งรายการเดียวกัน ฐานข้อมูลมี unique index บนชื่อลิงก์ของแอป (slug เช่น your-app ใน your-app.mythex.ai) ดังนั้นการแทรกที่สองล้มเหลว API แปลสิ่งนั้นเป็น “ชื่อลิงก์นั้นถูกใช้แล้ว” ซึ่งแนะนำให้ผู้ใช้เปลี่ยนชื่อแอปที่กำลังเผยแพร่สำเร็จภายใต้ชื่อนั้นในขณะนั้น
การแก้ไข: การอ้างสิทธิ์คือ lock
การอ้างสิทธิ์บันทึกการดีพลอยตอนนี้เป็นการอัปเดตฐานข้อมูลแบบมีเงื่อนไขเพียงคำสั่งเดียว: เอาแถวนี้และทำเครื่องหมายว่ากำลังสร้าง เฉพาะเมื่อยังไม่มีใครเผยแพร่มัน เพราะเป็นคำสั่งเดียว ฐานข้อมูลจึงตัดสินคำขอที่มาถึงในมิลลิวินาทีเดียวกันได้ มีเพียงหนึ่งในนั้นที่เปลี่ยนแถว อีกรายการเห็นว่าไม่มีแถวเปลี่ยน แล้วหยุด และไม่เคยไปถึงออร์เคสเตรเตอร์
lock ก็ต้องมีทางออกเมื่อผู้ถือตายด้วย ถ้า API รีสตาร์ทกลางการเผยแพร่ แถวจะค้างอยู่ที่ “กำลังสร้าง” ตลอดไปและแอปจะเผยแพร่ไม่ได้ ดังนั้นแถวที่เจ้าของหยุดส่ง heartbeat จึงอ้างสิทธิ์ซ้ำได้ การเผยแพร่ที่กำลังทำงานจะอัปเดตแถวทุก 30 วินาที และความเงียบห้านาทีนับว่าตาย เกณฑ์นั้นมาจาก heartbeat ไม่ใช่การเดาว่าการสร้างใช้เวลานานแค่ไหน การสร้าง Dockerfile ของผู้ใช้เองอาจใช้เวลานานได้อย่างชอบธรรม และการสร้างที่ช้าแต่ยังส่ง heartbeat จะรักษาการอ้างสิทธิ์ไว้
ขั้นตอนที่ทำให้การดีพลอยที่กำลังดำเนินการทั้งหมดล้มเหลวจะไม่รันก่อนการเผยแพร่อีกต่อไป จะเคลียร์เฉพาะแถวที่โปรเซสที่ตายทิ้งไว้เท่านั้น
บอกผู้แพ้อย่างตรงไปตรงมา
คำขอที่แพ้ตอนนี้จะได้คำตอบที่ชัดเจน: publish_in_progress พร้อม ID ของการดีพลอยที่กำลังทำงาน ทุกจุดที่เริ่มการเผยแพร่ได้จัดการเรื่องนี้:
- พื้นที่ทำงาน จะติดตามการสร้างที่กำลังทำงานอยู่แทนการแสดงข้อผิดพลาดทับ
- เอเจนต์ จะได้รับแจ้งว่ามีการเผยแพร่ของแอปนี้กำลังทำงานอยู่และให้รอ แทนที่จะถูกบอกว่ามีบางอย่างล้มเหลว ซึ่งก่อนหน้านี้อาจทำให้มันไป “แก้” Dockerfile และลองใหม่
- การชนกันในการเผยแพร่ครั้งแรก บนชื่อลิงก์จะถูกตรวจ: ถ้าแถวที่ถือชื่ออยู่เป็นการเผยแพร่ที่กำลังทำงานของโปรเจกต์นี้เอง คำตอบคือ “กำลังเผยแพร่อยู่แล้ว” ไม่ใช่ “ชื่อนั้นถูกใช้แล้ว” การชนกันจริงกับโปรเจกต์อื่นยังได้ข้อความให้เปลี่ยนชื่อ
วิธีที่เราตรวจสอบ
เราเขียนสคริปต์ทำซ้ำที่รันการเผยแพร่สามครั้งของแอปหนึ่งพร้อมกันบนฐานข้อมูลจริงและออร์เคสเตรเตอร์สตับ แล้วนับว่ามีการขอการดีพลอยกี่ครั้ง ครอบคลุมทั้งการเผยแพร่ครั้งแรกและการเผยแพร่ซ้ำของแอปที่ใช้งานอยู่ ก่อนแก้ไขมันนับสองการดีพลอย หลังแก้ไขนับหนึ่ง
สคริปต์ยังครอบคลุมทางหนี: มันทำเครื่องหมายแถวของแอปว่ากำลังสร้างโดยไม่มี heartbeat เป็นเวลาสิบนาที เผยแพร่อีกครั้ง และตรวจว่าการเผยแพร่ใหม่อ้างสิทธิ์แถวและรันได้
unit test ยึด lock กับสองสิ่งที่ lock ต้องทำ: จากการเผยแพร่ซ้ำพร้อมกันสามรายการ มีเพียงหนึ่งที่ผ่านได้ และอีกสองรายการจะได้รับแจ้งว่ามีการเผยแพร่กำลังทำงาน และการสร้างที่เจ้าของตายแล้วสามารถถูกอ้างสิทธิ์ได้ การทดสอบอีกรายการตรวจว่าความพยายามใหม่ไม่รับข้อความข้อผิดพลาดของความพยายามก่อนหน้าไปด้วย
สิ่งที่เราได้เรียนรู้
- โค้ดเก็บกวาดอาจเป็นการแข่งขันที่ซ่อนอยู่ “ล้มเหลวทุกอย่างที่กำลังทำงานแล้วค่อยเริ่ม” ดูเหมือนการรักษาความสะอาด แต่เมื่อมีผู้เรียกสองราย มันคือการที่แต่ละรายยกเลิกอีกราย
- ให้ฐานข้อมูลเป็นผู้ตัดสิน การอ่านแล้วตามด้วยการเขียนเปิดช่องว่างให้คำขออื่น ส่วนการอัปเดตแบบมีเงื่อนไขคำสั่งเดียวไม่มี
- ทุก lock ต้องมีการหมดอายุที่ผูกกับการยังมีชีวิต ถ้าไม่มี การล่มจะกลายเป็นแอปที่ค้างตลอดไป
- ข้อความข้อผิดพลาดคือคำสั่ง “ชื่อนั้นถูกใช้แล้ว” บอกให้คนทำสิ่งที่ผิด ข้อความที่ถูกต้องคือข้อความที่ง่ายกว่าและเป็นความจริง
เอกสารเรื่องการเผยแพร่: วิธีเผยแพร่ และการแก้ไขที่เกี่ยวข้อง: แอปที่เผยแพร่แล้วยังคงออนไลน์ระหว่างที่คุณเผยแพร่ซ้ำ