Steven
Steven2 นาทีที่อ่าน

วันที่แอปของเรายิง DDoS ใส่ตัวเอง

กองงานอัปโหลดที่ค้างอยู่ ถูกปล่อยออกมาทีเดียวทั้งหมดตอนเปิดแอป กลายเป็นการเปลี่ยนทุก client ของ GeekBye ให้เป็นการโจมตีแบบ denial-of-service เล็กๆ ใส่เซิร์ฟเวอร์ของเราเอง วิธีแก้ — และบันได connection-liveness ที่มันบังคับให้เราต้องสร้าง — เป็นหนึ่งในสิ่งที่มีประโยชน์ที่สุดที่ v2 สอนเรา

ความน่าเชื่อถือ
เครือข่าย
วิศวกรรม
GeekBye Releases
วันที่แอปของเรายิง DDoS ใส่ตัวเอง

วิศวกรระบบกระจายทุกคนสุดท้ายก็ต้องได้เจอกับการแห่พร้อมกัน (thundering herd): client จำนวนมหาศาลทำสิ่งเดียวกันในเสี้ยววินาทีเดียวกัน แล้วทรัพยากรร่วมที่อยู่ข้างหลังก็ยุบลง โดยปกติมันเป็น client ของคนอื่น ใน GeekBye v2.0.1 มันเป็นของเรา และผู้โจมตีก็คือแอปของเราเอง

แอปจดประชุมโจมตีตัวเองได้ยังไง

GeekBye อัปโหลดไฟล์บันทึกเสียงไปที่ Google Drive ของคุณได้ ถ้าบันทึกไหนอัปโหลดทันทีไม่ได้ — คุณออฟไลน์อยู่ แอปปิดไป Drive สะดุด — มันจะเข้าไปอยู่ในกองงานค้างเพื่อลองใหม่ทีหลัง ก็สมเหตุสมผลดี

ความล้มเหลวอยู่ที่ ตอนไหน ที่ "ทีหลัง" เกิดขึ้น พอเปิดแอปครั้งถัดไป แอปจะพยายามระบายกองงานค้างทั้งหมดในทีเดียว ผู้ใช้คนหนึ่งที่มีบันทึกค้างสะสมทั้งสัปดาห์ กลายเป็นการระเบิดของ request อัปโหลดพร้อมกันในวินาทีที่เขาเปิดแอป คูณด้วยผู้ใช้ทุกคนที่เปิดแอปตอนเช้า แล้ว backend ของเราก็ถูกสั่งให้ยืนยันตัวตน ตรวจ rate และประมวลผลกำแพงทราฟฟิกในไม่กี่วินาทีเดียวกัน — จาก client ที่ในทางเทคนิคแล้วทุกตัวก็ทำตัวเรียบร้อยดี

backend ของเราทำสิ่งที่ถูกต้อง คือ rate-limit กระแสน้ำหลากนั้น แล้วความเสียหายลำดับที่สองก็เริ่มตรงนั้น response ของ rate-limit คือ HTTP 429 และ 429 ถ้าไม่ระวังมันหน้าตาเหมือนความล้มเหลวอย่างอื่นมากๆ:

  • การดึงข้อมูลโปรไฟล์ ตอนเปิดแอปได้ 429 มา แล้วแอปปฏิบัติต่อมันเหมือน auth ล้มเหลว — เตะผู้ใช้ออกจาก session ที่ยังใช้ได้ดีๆ ชั่วครู่
  • การเชื่อมต่อถอดเสียง ได้ 429 แบบทั่วไปมา แล้วโชว์ ป็อปอัปให้อัปเกรดว่าใช้ลิมิตเสียงหมดแล้ว — บอกผู้ใช้ที่จ่ายเงินว่าพวกเขาชน quota ที่จริงๆ ยังไม่ได้ชน

ดังนั้นต้นเหตุเดียว — กองงานค้างที่ไม่ได้จัดจังหวะ — ผลิตอาการที่มองเห็นได้สามอย่าง: เซิร์ฟเวอร์ตึง, การหลุด login แบบผิดๆ, และการยัดขายอัปเกรดแบบผิดๆ นี่คือรูปทรงคลาสสิกของ self-DoS: ภาระงานนั้นแย่อยู่แล้ว แต่สิ่งที่ผู้ใช้รู้สึกจริงๆ คือการ ตีความอาการของภาระงานนั้นผิด

วิธีแก้ ในสองชั้น

หยุดการแห่ถล่ม ตอนนี้กองงานอัปโหลดถูกจัดจังหวะแล้ว — request กระจายออกด้วย backoff และมี cooldown หลังจากทุก 429 เพื่อให้ client ถอยห่างจากเซิร์ฟเวอร์ที่กำลังตึง แทนที่จะไปกดทับมันหนักขึ้น การแห่พร้อมกัน (thundering herd) กลายเป็นแถวคิวที่เป็นระเบียบ

หยุดการตีความผิด 429 หรือ 5xx ชั่วคราวตอนเปิดแอปไม่เตะคุณออกจากระบบอีกแล้ว — client แยกแยะ "เซิร์ฟเวอร์แค่ยุ่งชั่วครู่" ออกจาก "session ของคุณใช้ไม่ได้แล้ว" และ 429 แบบ rate-limit ทั่วไปบนเส้นทางถอดเสียงก็ไม่ปลอมตัวเป็น error ของ quota เสียงอีก มีแต่ response quota ตัวจริงเท่านั้นที่จะโชว์ป็อปอัปอัปเกรด บทเรียนที่ติดตัว: error code ไม่ใช่ความหมายของ error 429 แปลว่า "ช้าลงหน่อย" ไม่ใช่ "คุณไม่มีสิทธิ์" และไม่ใช่ "quota คุณหมดแล้ว" — และ client ต้องรู้ความต่างนี้

บันได liveness ที่ตามมา

การจัดจังหวะฝูงที่แห่ ทำให้ปัญหาที่เงียบกว่านั้นโผล่ออกมา: ตอนที่การเชื่อมต่อมัน เสีย จริงๆ ภายใต้ภาระงาน เราสังเกตเห็นเร็วแค่ไหน? ช้ากว่าที่เราอยากให้เป็น ดังนั้น v2.0.4 จึงสร้างบันได connection-liveness สำหรับการถอดเสียงเรียลไทม์ขึ้นมา:

  • Heartbeat — socket ถอดเสียงถูก ping เป็นช่วงเวลาคงที่ เพื่อให้การเชื่อมต่อที่ตายไปเงียบๆ ถูกตรวจเจอในไม่กี่วินาที แทนที่จะรอให้เสียงชิ้นถัดไปล้มเหลว
  • การเตะให้ reconnect เมื่อเน็ตกลับมา — เมื่อ OS รายงานว่าเน็ตกลับมาแล้ว แอปจะรีบสร้างการเชื่อมต่อขึ้นใหม่ทันที แทนที่จะรอไปสะดุดเจอเอาเอง
  • liveness แบบ control ping/pong — การวิ่งไปกลับระดับแอปพลิเคชัน ที่ยืนยันไม่ใช่แค่ว่า "socket เปิดอยู่" แต่ยืนยันว่า "ปลายทางอีกฝั่งกำลังตอบจริงๆ"
  • ประตู reconnect ที่ service เป็นเจ้าของ — มีที่เดียวที่ตัดสินใจว่าจะ reconnect ไหม แทนที่จะให้หลายส่วนของแอปแย่งกันทำแล้วเหยียบเท้ากันเอง

ไม่มีอันไหนหรูหราเลย แต่ทั้งหมดนั่นแหละคือเหตุผลที่ session บน v2 รู้สึกเหมือนมันแค่…ต่ออยู่ตลอด

มันเกิดขึ้นอีกในสัปดาห์นี้ — ต่ำลงไปหนึ่งชั้น

นี่คือส่วนที่ทำให้เรื่องนี้เป็นมากกว่าเรื่องเล่าวีรกรรม ความล้มเหลวคลาสเดียวกันนี้โผล่มาอีกเมื่อไม่กี่วันก่อน ต่ำลงไปจากเราหนึ่งระดับ client ตัวเดียวยิง การเริ่ม session ถอดเสียง 21 ครั้งภายในไม่ถึง 400 มิลลิวินาที — แล้วไปสะดุดลิมิตระดับทั้งบัญชีของผู้ให้บริการเสียงของเรา ที่ 15 request พร้อมกัน การแห่ถล่มโครงสร้างพื้นฐานร่วม เหมือนกับกองงานอัปโหลดเป๊ะ แค่เล็งไปที่ตัว dependency แทนที่จะเป็น backend ของเราเอง

รูปทรงเหมือนกันเป๊ะ และวิธีแก้ก็เหมือนกัน: client ที่แห่ถล่มต้องมี single-flight guard เพื่อไม่ให้มันยิงยี่สิบครั้งเพื่อเจตนาเดียว และทรัพยากรร่วมต้องมีเพดานต่อผู้ใช้ เพื่อไม่ให้ client ตัวเดียวกินพูลจนหมด เราสร้างเวอร์ชันฝั่ง client ของสิ่งนี้มาก่อนแล้ว — ตัวจัดจังหวะการอัปโหลด คือ แพตเทิร์นนี้เอง ตอนนี้เราเอามันไปใช้กับการเริ่ม session การแห่พร้อมกัน (thundering herd) ไม่ใช่บั๊กที่แก้ครั้งเดียวจบ แต่มันคือรูปทรงที่คุณจะเรียนรู้ที่จะจำมันได้ ทุกที่ที่ client ไปเจอกับลิมิตที่แชร์กัน

สามสิ่งที่ควรเก็บกลับไป

  1. client ของคุณเองคือ load test ที่คุณไม่ได้นัดไว้ อะไรก็ตามที่ทำงานเป็นชุดตอนเปิดแอป, retry ตอนเปิดแอป, หรือ reconnect ตอนตื่นจาก sleep ล้วนเป็นการแห่พร้อมกัน (thundering herd) ที่รอแค่ให้มีผู้ใช้มากพอ จัดจังหวะมันเสียก่อนที่คุณจะมีผู้ใช้มากพอ
  2. แยก code ออกจากความหมาย 429 คือ status ที่ถูกอ่านผิดมากที่สุดเท่าที่มีอยู่ "ช้าลงหน่อย" ไม่ใช่ "logout" และไม่ใช่ "จ่ายเงินให้เรา" กำหนดเส้นทางความล้มเหลวแต่ละอย่างไปยังสิ่งที่มันหมายถึงจริงๆ
  3. liveness เป็นบันได ไม่ใช่ flag "การเชื่อมต่อยังมีชีวิตอยู่ไหม?" มีคำตอบที่ซื่อสัตย์อยู่หลายคำในหลายชั้น — socket เปิดอยู่, byte กำลังไหล, ปลายอีกฝั่งกำลังตอบ, มีเน็ต แอปที่แข็งแรงจะตรวจมากกว่าหนึ่งอย่าง

นี่คือกลไกไม่หรูหราที่อยู่ใต้ความนิ่งของ GeekBye v2 สำหรับฟีเจอร์ความน่าเชื่อถือที่มันเปิดทางให้ ดูทำไม AI notetaker ของคุณถึงหยุดเมื่อ Wi-Fi ห่วย และการถอดเสียงสดเมื่อไฟร์วอลล์บล็อก WebSocket (v2.0.8) สำหรับรีลีสข้างเคียงในซีรีส์นี้ ดูทำไม AI notetaker ของคุณถึงหยุดบันทึกเสียงกลางประชุม (v2.0.9)

บทความที่เกี่ยวข้อง

เมื่อไฟร์วอลล์บล็อก WebSocket แล้วการถอดเสียงเรียลไทม์จะเป็นยังไง
Steven
Steven3 นาทีที่อ่าน

เมื่อไฟร์วอลล์บล็อก WebSocket แล้วการถอดเสียงเรียลไทม์จะเป็นยังไง

เครือข่ายองค์กรชอบปล่อย HTTPS ให้ผ่าน แต่แอบฆ่า WebSocket upgrade ทิ้งเงียบๆ ซึ่งทำให้การถอดเสียงเรียลไทม์พังแบบไร้เสียง GeekBye v2.0.8 จะ fall back ไปยัง transport แบบ HTTPS ล้วนโดยอัตโนมัติ — และระหว่างที่ทำมันออกมา เราก็เจอบั๊กที่จะทำให้ทั้งฟีเจอร์นี้ไร้ประโยชน์ไปเลย

ถอดเสียง
เครือข่าย
วิศวกรรม
ทำไมการบันทึกหน้าจอถึงจับมอนิเตอร์ผิดตัว (และวิธีที่เราแก้)
Steven
Steven2 นาทีที่อ่าน

ทำไมการบันทึกหน้าจอถึงจับมอนิเตอร์ผิดตัว (และวิธีที่เราแก้)

บนเครื่องที่ต่อสองจอ GeekBye บันทึกและแคปหน้าจอหลักเสมอ ไม่ว่าคุณจะทำงานอยู่บนจอไหน การแก้คือฟังก์ชันเล็กๆ ตัวเดียว — แต่เวอร์ชันแรกของมันผิด และ code review จับได้ว่าทำไม

บันทึกหน้าจอ
หลายจอ
วิศวกรรม
เวอร์ชัน 2 จริง ๆ แล้วต้องแลกด้วยอะไร: 206 คอมมิตแห่งสถานะที่ซื่อสัตย์
Steven
Steven2 นาทีที่อ่าน

เวอร์ชัน 2 จริง ๆ แล้วต้องแลกด้วยอะไร: 206 คอมมิตแห่งสถานะที่ซื่อสัตย์

GeekBye v2 ไม่ใช่การปล่อยฟีเจอร์ มันคือ 206 คอมมิตที่เล็งไปยังความคิดเดียว: แอปไม่ควรโกหกเกี่ยวกับสถานะของตัวเองเลย นี่คือราคาที่ต้องจ่าย — รวมถึงความผิดพลาดในไฟล์ล็อกเพียงบรรทัดเดียวที่เกือบทำให้เราส่งอะไรออกไปไม่ได้เลย

ความน่าเชื่อถือ
วิศวกรรม
การปล่อยเวอร์ชัน