- Uber ได้สร้าง Global Rate Limiter (GRL) เพื่อวางระบบป้องกันโอเวอร์โหลดแบบรวมศูนย์ในสภาพแวดล้อมไมโครเซอร์วิสหลายพันตัวที่ประมวลผล RPC หลายร้อยล้านครั้งต่อวินาที
- GRL ใช้สถาปัตยกรรม วงจรป้อนกลับ 3 ชั้น ที่ประกอบด้วย client-aggregator-controller ทำให้ตัดสินคำขอในเครื่องได้ แต่ยังประสานงานระดับโลกได้ภายในไม่กี่วินาที
- เปลี่ยนจากแนวทาง token bucket เดิมไปเป็น โมเดล drop แบบความน่าจะเป็น เพื่อแก้ปัญหาด้านความเป็นธรรมและการขยายระบบ พร้อมลด latency บน hot path ให้ต่ำที่สุด
- เมื่อเทียบกับตัวจำกัดอัตราที่อิง Redis ค่า latency ที่ P99.5 ดีขึ้นสูงสุด 90% และยังรองรับทราฟฟิกพุ่งขึ้น 15 เท่ารวมถึงการโจมตี DDoS ได้โดยไม่ทำให้บริการด้อยลง
- Rate Limit Configurator (RLC) วิเคราะห์รูปแบบทราฟฟิกในอดีตเพื่ออัปเดตลิมิตโดยอัตโนมัติ ทำให้ระบบพัฒนาจากการตั้งค่าแบบคงที่ไปสู่ระบบที่ปรับตัวได้เอง
ปัญหาของการจำกัดอัตราแบบเดิม
- ในสภาพแวดล้อมไมโครเซอร์วิสยุคแรกของ Uber แต่ละทีมต่างสร้าง throttling ของตัวเองด้วย business logic, custom middleware, ตัวนับที่อิง Redis เป็นต้น
- ส่งผลให้เกิดปัญหาอย่าง การตั้งค่าที่ไม่สอดคล้องกัน, การเพิ่ม latency จาก Redis, ต้อง redeploy เซิร์ฟเวอร์เมื่อเปลี่ยนลิมิต, และรับมือ incident ได้ยากเพราะมีตัวจำกัดที่ไม่ได้จัดทำเอกสาร
- บริการขนาดเล็กจำนวนมากถึงขั้น ไม่มีการจำกัดอัตราเลย และตัวจำกัดแต่ละตัวก็รายงาน metric กับ error ต่างกัน ทำให้ไม่สามารถสร้าง observability แบบรวมศูนย์ได้
- แนวทางที่ใช้ Redis เป็น ตัวนับแบบรวมศูนย์ ก่อให้เกิดปัญหา latency ที่ยอมรับไม่ได้และปัญหาความสอดคล้องกันข้ามรีเจียน ในสภาพแวดล้อมที่มีโฮสต์หลายแสนเครื่อง ปริมาณหลายร้อยล้านคำขอต่อวินาที และหลายรีเจียน
- แม้จะทำ sharding และ replication ก็ยังต้องใช้ Redis cluster หลายร้อยชุด และเพิ่ม failure mode ใหม่เข้ามา
- แนวทาง ซิงก์ตัวนับเป็นระยะ แม้จะลด network overhead ได้ แต่ก็ถูกตัดออกเพราะข้อมูลไม่สดพอและตอบสนองต่อทราฟฟิกพุ่งขึ้นอย่างฉับพลันได้ช้า
- ข้อสรุปสุดท้ายคือ มีเพียงสถาปัตยกรรมแบบกระจายเต็มรูปแบบที่ให้ local proxy ตัดสินจากโหลดที่ถูกรวมแล้ว เท่านั้นที่ทำให้ได้ทั้ง latency ต่ำและการขยายตัวระดับโลกพร้อมกัน
วิสัยทัศน์ของตัวจำกัดอัตราแบบรวมที่ระดับโครงสร้างพื้นฐาน
- แนวทางแก้คือฝังการจำกัดอัตราไว้ใน service mesh ของ Uber ซึ่งเป็นชั้นโครงสร้างพื้นฐานสำหรับทราฟฟิก RPC ระหว่างบริการ
- เมื่อฝังตัวจำกัดไว้ในชั้นนี้ จะสามารถตรวจและประเมินทุกคำขอก่อนถึงปลายทางได้ โดยไม่ขึ้นกับภาษา หรือเฟรมเวิร์ก ของผู้เรียก
- เป้าหมายคือให้บริการ ระบบจำกัดอัตราแบบรวมศูนย์ ที่ทีมสามารถตั้งโควตาตามผู้เรียกและตาม procedure ได้โดยไม่ต้องแก้โค้ด
- จำเป็นต้องขยายรองรับคำขอหลายร้อยล้านครั้งต่อวินาที service pair หลายหมื่นคู่ และโฮสต์ฟลีตในหลายภูมิภาคทางภูมิศาสตร์ โดยเพิ่ม latency ให้น้อยที่สุด
สถาปัตยกรรม GRL: วงจรป้อนกลับ 3 ชั้น
- หัวใจของ GRL คือโครงสร้าง วงจรป้อนกลับ 3 ชั้น
- Rate-limit client (service mesh data plane): ตัดสินคำขอแต่ละรายการในเครื่องตามคำสั่งที่ได้รับจาก aggregator และรายงานจำนวนคำขอต่อวินาทีต่อโฮสต์ไปยัง aggregator ระดับโซน
- Aggregator (ระดับโซน): รวบรวม metric จาก client ทั้งหมดในโซนเดียวกัน คำนวณการใช้งานระดับโซน และส่งต่อไปยัง controller
- Controller (ระดับรีเจียน/ระดับโลก): รวมข้อมูลระดับโซนเพื่อตัดสินการใช้งานระดับโลก แล้วส่ง คำสั่งอัตราการ drop ที่อัปเดตแล้วกลับไปยัง aggregator และ client
- การรวมข้อมูลแบบลำดับชั้นนี้ทำให้รักษา latency ต่ำ บน hot path ได้เพราะการตัดสินเกิดขึ้นในเครื่อง ขณะเดียวกันก็ทำ การประสานงานระดับโลก ได้ภายในไม่กี่วินาที
- หาก control plane ขัดข้อง client จะทำงานในโหมด fail open โดยปล่อยทราฟฟิกผ่านต่อไปเพื่อหลีกเลี่ยงการทำให้ระบบล่มด้วยตัวเอง
วิวัฒนาการของลอจิกการจำกัดอัตรา
-
แนวทาง token bucket ในระยะแรก
- ช่วงแรกมีการใช้ อัลกอริทึม token bucket บนแต่ละ proxy ของ network data plane
- แต่ละ proxy ติดตามจำนวนคำขอในเครื่องและเติม token ตามเวลา จากนั้นอนุญาตหรือปฏิเสธคำขอตามจำนวน token ที่มี
- อัตราการเติม token คำนวณจากสัดส่วนระหว่างโหลดในเครื่องของ proxy กับลิมิตระดับโลก:
ratio × limitRPS
- เพื่อรองรับ burst traffic จะเก็บ token ที่ไม่ได้ใช้ไว้ใน circular buffer โดยค่าเริ่มต้นเก็บไว้ 10 วินาที และตั้งได้สูงสุด 20 วินาที
- แต่เมื่อใช้งานจริงพบ ปัญหาด้านความเป็นธรรมและการขยายระบบ: หากจำนวนผู้เรียกเกินลิมิตจะไม่สามารถกระจายความจุได้อย่างเป็นธรรม และ burst traffic จากโฮสต์บางตัวอาจถูก drop เร็วเกินไปแม้ยังไม่ถึงลิมิตระดับโลก
-
การนำ Drop-by-Ratio มาใช้
- เมื่อโหลดรวมระดับโลกเกินลิมิตที่ตั้งไว้ client จะ drop คำขอด้วยสัดส่วนตามความน่าจะเป็น
- ตัวอย่าง: หาก RPS รวมของผู้เรียกเป็น 1.5 เท่าของลิมิต จะมีการ drop ราว 33% ในทุก instance โดยใช้สูตร
drop_ratio = (actual_rps - limit_rps) / actual_rps
- ด้วย สัญญาณการ drop ระดับโลก ที่ control plane อัปเดตทุกไม่กี่วินาที จึงสามารถ throttle ทราฟฟิกส่วนเกินอย่างเท่าเทียมในทุก instance ของผู้เรียก
- วิธีนี้มีประสิทธิภาพเป็นพิเศษกับ บริการแบบเกตเวย์ขนาดใหญ่ ที่มี instance ของผู้เรียกตั้งแต่หลายร้อยถึงหลายพันตัว
-
เปลี่ยนสู่โมเดลความน่าจะเป็นแบบรวม
- เมื่อ GRL เติบโตเต็มที่ Uber ได้เลิกใช้ token bucket ทั้งหมดและรวมเป็น โมเดล drop แบบความน่าจะเป็นที่อิง control plane เพียงแบบเดียว
- เพราะการใช้อัลกอริทึมสองแบบพร้อมกันเพิ่มความซับซ้อนของการตั้งค่าและ network overhead
- การรวมเป็นโมเดลเดียวช่วยให้การตั้งค่าง่ายขึ้น ลดแบนด์วิดท์ของ control plane และรวมการตัดสินการจำกัดอัตราทั้งหมดไว้ภายใต้ กลไกที่สอดคล้องกันระดับโลก
- ข้อแลกเปลี่ยนคือ ต้องพึ่งข้อมูลรวมระดับโลกที่อัปเดตทุกวินาที จึงมี ความหน่วงในการบังคับใช้ 2–3 วินาที
- ในทางปฏิบัติแทบไม่มีผลกับ workload ส่วนใหญ่ และจะเห็นผลเฉพาะใน burst ที่สั้นมากและรุนแรงมากเท่านั้น
-
ดีไซน์สุดท้าย: probabilistic drop ตามคำสั่งจาก control plane
- ปัจจุบัน GRL บังคับใช้ทั้งหมดที่ ชั้น client ของ network data plane
- เมื่อคำขอมาถึง จะมีขั้นตอนดังนี้:
- จับคู่คำขอกับ bucket ที่ตั้งค่าไว้ ซึ่งนิยามตามผู้เรียก, procedure หรือทั้งสองอย่าง
- หาก bucket นั้นมีคำสั่งอัตราการ drop ที่ยังใช้งานอยู่ ก็จะทำ probabilistic drop ตามสัดส่วนนั้น
- หากไม่มีคำสั่ง drop ก็ส่งต่อคำขอตามปกติ
- hot path จึงเบามาก: ไม่ต้องคำนวณ token ในเครื่องหรือสื่อสารกับ control plane ต่อคำขอ ใช้เพียง การสุ่มตัวอย่างตามความน่าจะเป็นอย่างง่าย เพื่อตัดสินภายในโปรเซส
- ส่วน aggregator และ controller จะทำงานคำนวณที่ซับซ้อนนอก data path ทุกวินาที เช่น การรวมจำนวนคำขอ เปรียบเทียบกับ threshold และคำนวณอัตราการ drop ใหม่
- ด้วยดีไซน์นี้ ระบบจึงขยายรองรับคำขอหลายร้อยล้านครั้งต่อวินาทีได้ พร้อมคง ความแม่นยำของการบังคับใช้ระดับโลกภายในไม่กี่วินาที
การตั้งค่าลิมิต
- เจ้าของบริการสามารถกำหนด bucket ของการจำกัดอัตราในไฟล์ตั้งค่า
- Scope: ระดับโลก, รายรีเจียน, รายโซน
- กฎการจับคู่: ชื่อผู้เรียก, procedure หรือทั้งสองอย่าง
- การทำงาน: deny (บังคับใช้) หรือ allow (shadow mode สำหรับทดสอบ)
- ใช้งานกับบริการปลายทางได้อย่างโปร่งใส โดยไม่ต้องแก้โค้ด
ผลลัพธ์ในการปฏิบัติการ
-
ลด latency และตัด overhead ออก
- ก่อนมี GRL หลายบริการใช้ ตัวจำกัดอัตราที่อิง Redis ซึ่งต้องมี network round trip ต่อคำขอ
- หลังเปลี่ยนมาเป็นการประเมินในเครื่องบน service mesh data plane จึงตัด hop เพิ่มเติมออกและลด latency ลงอย่างมาก
- ค่า latency ที่ P50 ลดลงประมาณ 1ms, P90 ลดลงหลายสิบ ms และ P99.5 ลดจากหลายร้อย ms เหลือเพียงหลายสิบ ms คิดเป็นการปรับปรุงสูงสุด 90%
-
การปฏิบัติการที่ง่ายขึ้นและใช้ทรัพยากรมีประสิทธิภาพขึ้น
- การรวมศูนย์การจำกัดอัตราไว้ใน service mesh data plane ช่วยให้โครงสร้างพื้นฐานง่ายขึ้น
- ไม่ต้องมี data store หรือ caching layer แยกต่างหากสำหรับการบังคับใช้โควตา
- มีการปลด Redis instance จำนวนมาก ที่เคยใช้เฉพาะงานจำกัดอัตรา ทำให้ได้ประสิทธิภาพด้านคอมพิวต์อย่างมีนัยสำคัญ
-
เพิ่มความเสถียรและการรับมือ incident
- หลังเปิดใช้งาน GRL ได้ป้องกันโอเวอร์โหลดซ้ำแล้วซ้ำเล่าในสถานการณ์สไปก์, failover และ retry storm
- ด้วยการ shed ทราฟฟิกส่วนเกินแบบความน่าจะเป็นที่ service mesh ระบบจึงรักษา เวลาในการตอบสนองที่สม่ำเสมอ ได้แม้โหลดขาเข้าเพิ่มขึ้นอย่างรวดเร็ว
- บริการสำคัญหนึ่งรองรับทราฟฟิกพุ่งจาก 22K เป็น 367K RPS หรือ เพิ่มขึ้น 15 เท่า ได้โดยไม่เสื่อมประสิทธิภาพ
- รองรับ การโจมตี DDoS ได้ก่อนที่ทราฟฟิกจะเข้าถึงระบบภายใน
- ระหว่างการรับมือ incident ทีมวิศวกรรม production สามารถใช้ GRL เพื่อ บังคับใช้การจำกัดอัตราแบบเจาะจง กับผู้เรียกหรือ procedure ที่มีทราฟฟิกสูง โดยการอัปเดตผ่าน control plane จะแพร่กระจายทุกวินาที ทำให้ตอบสนองต่อโอเวอร์โหลดได้ภายในไม่กี่วินาที
- สามารถ throttle รูปแบบทราฟฟิกเฉพาะได้อย่างรวดเร็วและปลอดภัยโดยไม่ต้อง redeploy บริการ
- ขนาดการใช้งานรวม: ประมาณ 80 ล้านคำขอต่อวินาที และมีการใช้โควตาแบบไดนามิกในบริการมากกว่า 1,100 ตัว
ระบบอัตโนมัติของการจำกัดอัตรา: Rate Limit Configurator (RLC)
-
ข้อจำกัดของการตั้งค่าด้วยมือ
- แม้ GRL จะรวมการบังคับใช้ไว้แล้ว แต่การตั้งค่าลิมิตยังคงต้องอาศัย งานแบบแมนนวล
- เจ้าของบริการต้องกำหนดโควตาตามผู้เรียกและ procedure ในไฟล์ YAML และปรับเมื่อรูปแบบทราฟฟิกเปลี่ยน
- ในสภาพแวดล้อมที่มีไมโครเซอร์วิสหลายร้อยตัวพัฒนาอย่างต่อเนื่อง การตั้งค่าแบบคงที่จะ ล้าสมัยได้รวดเร็ว
- หากเข้มงวดเกินไป ก็จะเกิด throttling แม้ในช่วงพีกของทราฟฟิกปกติ
- หากหลวมเกินไป ลิมิตก็จะสูงเกินความจุจริงจนปกป้องได้ไม่มาก
- การเปลี่ยนแปลงยังต้องอาศัยการวิเคราะห์แดชบอร์ดและการจูนด้วยมือ
-
วิธีทำงานของ RLC
- RLC (Rate Limit Configurator) ทำให้การตั้งค่า GRL อัปเดตทันสมัยโดยอัตโนมัติ
- โดยทำรอบถัดไปตามตารางคงที่ หรือทันทีเมื่อมีการเปลี่ยนการตั้งค่า:
- รวบรวม metric ย้อนหลังหลายสัปดาห์ จากแพลตฟอร์ม observability ของ Uber
- คำนวณ ลิมิตที่ปลอดภัย ตามผู้เรียกและตาม procedure โดยอาศัยค่ายอดพีกในอดีตและ buffer เผื่อไว้
- เขียนการตั้งค่าที่อัปเดตแล้วลงใน shared configuration store
- push ลิมิตใหม่ ไปยัง GRL ผ่าน control plane เดิม
- ด้วยกระบวนการ closed loop นี้ ลิมิตจะพัฒนาไปตามทราฟฟิกจริงและลดการแทรกแซงด้วยมือลงให้น้อยที่สุด
-
การออกแบบที่ขยายได้
- RLC ถูกออกแบบมาตั้งแต่ต้นให้รองรับ กลยุทธ์การคำนวณลิมิตหลายแบบ
- นโยบายพื้นฐานอิงข้อมูล RPS ในอดีต แต่สามารถเพิ่มนโยบายแบบใหม่ได้ในลักษณะโมดูลาร์
- บริการอย่างข้อมูลแผนที่และตำแหน่ง ใช้ โมเดลพยากรณ์ ที่สะท้อนการคาดการณ์ทราฟฟิกและความจุที่วางแผนไว้ล่วงหน้า โดยทำนายโหลดในอนาคตแทนการยึดแนวโน้มในอดีต
- ยังรองรับการจัดสรร fixed quota ตามข้อตกลงทางสัญญาหรือการปฏิบัติการที่กำหนดไว้ล่วงหน้า
- ผ่านอินเทอร์เฟซแบบโมดูลาร์ แต่ละโดเมนบริการจึงเลือกใช้ลอจิกการคำนวณที่เหมาะสมได้ระหว่างรูปแบบทราฟฟิกกึ่งเรียลไทม์ การคาดการณ์ หรือโควตาคงที่
-
shadow mode และการตรวจสอบความถูกต้อง
- เพื่อความปลอดภัย สามารถสร้างและมอนิเตอร์ลิมิตใน shadow mode ได้โดยยังไม่บังคับใช้
- เจ้าของบริการสามารถสังเกตพฤติกรรมของการจำกัดอัตราใน production ก่อนเปิดใช้งานจริง
- แดชบอร์ดและระบบแจ้งเตือนเฉพาะช่วยแสดงภาพทราฟฟิกที่สังเกตได้และการ drop เสมือน เพื่อสร้างความมั่นใจก่อน rollout
-
ผลของระบบอัตโนมัติ
- กฎการจำกัดอัตราหลายพันรายการถูก อัปเดตอัตโนมัติ โดยไม่ต้องแก้ไขด้วยมือ
- มีการสร้าง นโยบายที่สอดคล้องกัน ครอบคลุมทุกบริการจากสูตรและแหล่งข้อมูลเดียวกัน
- ด้วยนโยบายหลายประเภท ทีมจึงเลือกและขยายลอจิกการคำนวณที่เหมาะกับ workload ของตนได้
- shadow mode ช่วยรับประกันความแม่นยำก่อนบังคับใช้
ทิศทางในอนาคต
- หลังนำ RLC มาใช้ Uber ยังเดินหน้าขยายการ ปรับจูน buffer, นำ การจำกัดอัตราระดับรีเจียน มาใช้เพื่อลดขอบเขตผลกระทบของการเปลี่ยนแปลงการตั้งค่า และเพิ่ม ความถี่ของการอัปเดต เพื่อให้ตอบสนองต่อทราฟฟิกจริงได้ดีขึ้น
- ชั้น throttler ของ Uber ยังให้การป้องกันโอเวอร์โหลดเพิ่มเติมในตำแหน่งที่ใกล้กับแอปพลิเคชันมากขึ้น
- ปัจจุบัน GRL เป็นองค์ประกอบหลักของ สแตกความเสถียรหลายชั้น ของ Uber ที่ช่วยรักษาเสถียรภาพและความเป็นธรรมของแพลตฟอร์มแม้ภายใต้โหลดระดับสุดขีด
ยังไม่มีความคิดเห็น