• 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 ที่ช่วยรักษาเสถียรภาพและความเป็นธรรมของแพลตฟอร์มแม้ภายใต้โหลดระดับสุดขีด

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น