ทำไมจึงต้องมี Rate Limit (การจำกัดการใช้งาน)?
- ในแชต Twitch ที่มีผู้เข้าร่วมจำนวนมาก หากมีสแปมเมอร์เพียงคนเดียวและไม่มีการจำกัดการใช้งาน สแปมเมอร์คนนั้นก็อาจครอบงำบทสนทนาได้
- การจำกัดการใช้งานช่วยให้ผู้ใช้แต่ละคนมีโอกาสเข้าร่วมได้อย่างเป็นธรรม
- Rate Limiter (ตัวจำกัดการใช้งาน) ใช้ควบคุมทราฟฟิกของบริการด้วยการบล็อกคำขอที่เกินขีดจำกัดที่ตั้งไว้ภายในช่วงเวลาที่กำหนด ซึ่งมีประโยชน์นอกเหนือจากการควบคุมสแปมในแชต
- ตัวอย่างเช่น ในฟอร์มล็อกอิน เราสามารถใช้การจำกัดการใช้งานเพื่อยับยั้งการโจมตีแบบ brute force ขณะเดียวกันก็ยังยอมให้มีการเดาผิดได้เล็กน้อย
- API endpoint ก็มักถูกกำหนดการจำกัดการใช้งานไว้เช่นกัน เพื่อไม่ให้ผู้ใช้คนเดียวผูกขาดทรัพยากร
- หากอนุญาตให้ผู้ใช้เรียก API endpoint ที่มีต้นทุนสูงได้เพียง 100 ครั้งต่อนาที ก็สามารถใช้ตัวนับเพื่อติดตามจำนวน hit 100 ครั้งต่อนาที และบล็อกคำขอหลังจากนั้น
- นี่คือหนึ่งในอัลกอริทึมการจำกัดการใช้งานที่ง่ายที่สุด ซึ่งเรียกว่า Fixed Window Limiter
- เป็นวิธีทั่วไปในการควบคุมทราฟฟิกของบริการ
อัลกอริทึม Fixed windows
- จำกัดจำนวนคำขอภายในช่วงเวลาคงที่ (window)
- เมื่อต้นของแต่ละช่วงเวลา ตัวนับคำขอจะถูกรีเซ็ตกลับเป็น 0
- ข้อดี
- ติดตั้งใช้งานและทำความเข้าใจได้ง่าย
- คาดเดาได้สำหรับผู้ใช้
- ข้อเสีย
- หากคำขอเริ่มขึ้นใกล้ช่วงท้ายของหน้าต่างเวลา อาจเกิด burst ของคำขอได้สูงสุดถึง 2 เท่าของขีดจำกัด
- ตัวอย่างการใช้งานจริง: GitHub API ใช้ตัวจำกัดความเร็วแบบหน้าต่างเวลาคงที่ที่อนุญาตคำขอได้ 5,000 ครั้งต่อชั่วโมง
- แทนที่จะกำหนดเวลาเริ่มของหน้าต่างด้วยช่วงคงที่ แต่ละหน้าต่างเวลาอาจถูกสร้างขึ้นจากเวลาที่ผู้ใช้ส่งคำขอครั้งแรกภายในหน้าต่างนั้น
- ในแนวทางนี้ การแจ้งให้ผู้ใช้ทราบเวลาที่เหลือก่อนถึงหน้าต่างถัดไปมีความสำคัญเป็นพิเศษ
อัลกอริทึม Sliding windows
- แทนที่จะเติมความจุใหม่ทั้งหมดในครั้งเดียว Sliding Window จะเติมความจุทีละหนึ่งคำขอ
- ข้อดี
- ทำให้การกระจายของทราฟฟิกคำขอราบรื่นขึ้น
- เหมาะกับโหลดสูง
- ข้อเสีย
- คาดเดาได้ยากกว่าหน้าต่างเวลาคงที่สำหรับผู้ใช้
- การเก็บ timestamp ของทุกคำขอใช้ทรัพยากรมาก
- เนื่องจาก Sliding Window มีประโยชน์ที่สุดในสถานการณ์ที่มีทราฟฟิกสูง การที่อัลกอริทึมพื้นฐานใช้ทรัพยากรมากจึงกลายเป็นข้อเสียสำคัญ
- ดังนั้น ตัวจำกัดความเร็วแบบ Sliding Window ในการใช้งานจริงส่วนใหญ่จึงใช้วิธีประมาณค่า (approximation)
- วิธีประมาณค่านี้จะคำนวณจำนวนคำขอที่อนุญาตในหน้าต่างเวลาคงที่ก่อนหน้าและในหน้าต่างเวลาคงที่ปัจจุบัน แล้วถ่วงน้ำหนักคำขอที่อนุญาตในหน้าต่างก่อนหน้าตามสัดส่วนของช่วงทับซ้อนกับหน้าต่างเวลาแบบลอยตัวที่สิ้นสุด ณ เวลาปัจจุบัน
- วิธีประมาณค่านี้จำกัดคำขอได้ใกล้เคียงกับอัตราเดิมมาก แต่มีประสิทธิภาพกว่ามาก
- ตัวอย่างการใช้งานจริง: ตัวจำกัดความเร็วแบบปรับแต่งได้ของ Cloudflare ใช้ Approximate Sliding Window
อัลกอริทึม Token buckets
- แทนที่จะคิดเป็นระยะเวลาของหน้าต่างเวลา ให้จินตนาการถึงบักเก็ตที่ถูกเติมด้วย "โทเค็น" ในอัตราคงที่
- แต่ละคำขอจะดึงโทเค็นหนึ่งชิ้นออกจากบักเก็ตนี้ และหากบักเก็ตว่าง คำขอถัดไปจะถูกบล็อก
- ความจุของบักเก็ตแสดงถึงจำนวนคำขอสูงสุดที่ burst สามารถรองรับได้
- ช่วงเวลาการเติมกลับแสดงถึงช่วงห่างเฉลี่ยระยะยาวของคำขอที่อนุญาต
- หนึ่งในข้อดีหลักของอัลกอริทึมนี้คือ สามารถกำหนด burst และความจุเฉลี่ยที่แยกจากกันได้โดยไม่ต้องใช้ตัวจำกัดความเร็วหลายตัว
- ข้อดี
- อนุญาต burst ของทราฟฟิกสูง แต่ยังคงบังคับใช้อัตราคำขอเฉลี่ยในระยะยาว
- ยืดหยุ่นกว่าสำหรับผู้ใช้ โดยยอมให้ทราฟฟิกพุ่งสูงได้ภายในขอบเขตที่ยอมรับได้
- ข้อเสีย
- อธิบายข้อจำกัดและเวลาเติมกลับให้ผู้ใช้เข้าใจได้ยากกว่าหน้าต่างเวลาคงที่
- ตัวอย่างการใช้งานจริง
- Stripe ใช้ Token Bucket ที่มีขีดจำกัด 500 และช่วงเวลาการเติมกลับ 0.01 วินาทีต่อผู้ใช้ ทำให้สามารถรองรับคำขอได้ต่อเนื่อง 100 ครั้งต่อวินาที และรองรับ burst ได้สูงสุด 500 คำขอ
- ฟรีเทียร์ของ GPT-3.5 จาก OpenAI ใช้ Token Bucket ที่มีขีดจำกัด 200 และช่วงเวลาการเติมกลับ 86400 วินาที/200 จึงจำกัดไว้ที่ 200 คำขอต่อวัน
สิ่งที่ควรพิจารณาเมื่อใช้ Rate Limit
- ต้องมี persistent storage สำหรับ rate limiter
- หากการเชื่อมต่อของเซิร์ฟเวอร์ไปยัง persistent storage ล้มเหลว ควรอนุญาตทุกคำขอแทนที่จะบล็อก
- อาจเลือกควบคุม burst traffic เพิ่มเติมได้
- ต้องเลือกคีย์ที่เหมาะสม (เช่น user ID, API key เป็นต้น)
- ควรแสดงข้อผิดพลาดการจำกัดความเร็วที่เป็นประโยชน์ (เช่น เวลาที่ต้องรอก่อนส่งคำขอครั้งถัดไป, สถานะ HTTP 429, response header อย่าง x-ratelimit-* เป็นต้น)
2 ความคิดเห็น
พออ่านบทความสรุปเป็นภาษาเกาหลีแล้วก็คิดว่า "โอเค เข้าใจว่าคืออะไร แต่เนื้อหามันก็เหมือน ๆ กันไม่ใช่เหรอ?" แต่พอได้ไปอ่านบทความต้นฉบับตามลิงก์แล้ว อธิบายได้ดีมากจริง ๆ และการทำภาพให้เห็นภาพก็ถูกใจมากด้วย! 👍👍👍
ความคิดเห็นจาก Hacker News
สรุปรวมความคิดเห็นจาก Hacker News
ข้อพิจารณาเพิ่มเติมจากประสบการณ์อันยาวนาน:
ในสภาพแวดล้อมแบบมัลติเทนเนนต์ การทำ fair queueing เป็นแนวทางที่เหมาะที่สุดในการป้องกันการโจมตีแบบ DoS: จัดสรรคิวของตัวเองให้แต่ละไคลเอนต์ แล้วให้ background routine วนตรวจแต่ละคิวซ้ำ ๆ เพื่อประมวลผลคำขอ ไคลเอนต์ที่ส่งคำขอสแปมจะทำให้มีเพียงคิวของตัวเองที่แออัด
ประสบการณ์ในการพัฒนาโค้ดฝั่งไคลเอนต์: สงสัยมาตลอดว่ากลยุทธ์ backoff แบบใดดีที่สุดเมื่อชน rate limit การได้อ่าน trade-off จากมุมมองของบริการเป็นเรื่องน่าสนใจ
ข้อความแสดงความยินดี: เป็นงานภาพประกอบที่ดีที่สุดสำหรับคอนเทนต์สั้น ๆ ให้ข้อมูลแน่นและสรุปประเด็นได้ดีมาก
อัลกอริทึม GCRA: คิดว่าเป็นอัลกอริทึมที่ดีกว่าสำหรับ rate limiting และอยากให้เป็นที่รู้จักและถูกใช้งานแพร่หลายกว่านี้
งานที่ยอดเยี่ยม: สัมผัสได้ว่ามีการทุ่มเวลาและความพยายามไปกับโพสต์นี้มาก ทำได้ดีมาก
ปัญหา rate-limiting บน AWS Lambda: เคยพยายามทำ rate-limiting ใน NodeJS แต่บน AWS Lambda ตัวจับเวลาทำงานแปลก ๆ จนเกินเป้าหมาย ผ่านในการทดสอบบนเครื่องโลคัลแต่ล้มเหลวบน Lambda ไม่แน่ใจว่าเป็นปัญหาของตัวจับเวลาหรือของไลบรารี
การรับมือเมื่อเลเยอร์ rate limiting อิ่มตัว: สงสัยว่ามีตัวเลือกอื่นนอกจาก CF หรือไม่ และอยากรู้ว่ากฎ nftable มีประสิทธิภาพแค่ไหนในการป้องกันการโจมตีแบบ DoS บน VPS ขนาดเล็ก
ทรัพยากรที่เคยอยากได้ในหลายช่วงเวลา: เป็นทรัพยากรที่ตลอดเส้นทางอาชีพเคยต้องการหลายครั้ง ตอนนี้ดีใจที่มันมีอยู่แล้ว
แฟนข้อมูลภาพ: สงสัยว่าใช้ D3 อยู่หรือไม่