بناء محدد معدل طلبات بنظام النافذة المنزلقة (Sliding Window) في Redis

استنفدت حصة YouTube API الخاصة بنا قبل الساعة 9:00 صباحاً مرتين خلال الربع الأخير. أدى ذلك إلى بقاء خلاصات المحتوى الرائج (trending feeds) قديمة وغير محدثة في نصف مناطقنا.

لم تكن المشكلة في نمو حركة المرور، بل كانت في محدد معدل الطلبات (rate limiter) الخاص بنا.

كنا نستخدم عداد النافذة الثابتة (fixed-window counter). سمح هذا بمرور دفعتين هائلتين من استدعاءات API إذا حدثتا تماماً عند حدود النافذة. بالنسبة لخط معالجة عالمي يضم 8 مناطق، كان تداخل الحدود هذا يحدث كثيراً، مما حول حصتنا اليومية الصارمة إلى تسريب في الميزانية.

استبدلت النافذة الثابتة بسجل النافذة المنزلقة (sliding window log) باستخدام مجموعات Redis المرتبة (sorted sets).

إليك آلية العمل:

  • استخدم ZADD لتسجيل طلب مع طابع زمني (timestamp).
  • استخدم ZREMRANGEBYSCORE لإزالة المدخلات القديمة خارج نطاق النافذة.
  • استخدم ZCARD لحساب عدد الطلبات المتبقية في النافذة بدقة.
  • استخدم PEXPIRE لتنظيف المفاتيح غير النشطة تلقائياً.

هذه الطريقة دقيقة، حيث لا توجد حدود يتم تجاوزها.

قمت بتنفيذ ذلك باستخدام نص Lua برمجي (Lua script) في Redis. يضمن هذا أن تكون عملية التحقق والتسجيل بأكملها ذرية (atomic). إذا قمت بتشغيل هذه الخطوات في كود التطبيق، فإن حالات التسابق (race conditions) ستسمح بمرور طلبات إضافية.

قرارات تقنية رئيسية للإنتاج:

  • استخدم Redis TIME: لا تستخدم الطوابع الزمنية من خوادم التطبيقات الخاصة بك؛ فالتفاوت في توقيت الساعات (clock skew) بين الخوادم يفسد دقة النافذة.
  • التكاليف الموزونة: ليست كل استدعاءات API متساوية. قد يكلف استدعاء بحث واحد 100 وحدة بينما يكلف استدعاء قائمة فيديو وحدة واحدة فقط. يتعامل النص البرمجي الخاص بي مع هذا من خلال إدراج أعضاء متعددين لكل استدعاء.
  • دقة "إعادة المحاولة بعد" (Retry-After): من خلال النظر إلى أقدم مدخل في المجموعة المرتبة، يحسب النظام بالضبط متى ستتوفر السعة مجدداً.
  • منطق الحماية من الفشل (Fail-safe): أستخدم EVALSHA مع توفير EVAL كبديل. إذا تم مسح ذاكرة التخزين المؤقت لنصوص Redis البرمجية أثناء إعادة التشغيل، سيتعامل التطبيق مع الأمر بسلاسة.

المقايضة هنا هي الذاكرة. يستهلك كل طلب حوالي 100 بايت. بالنسبة لحصة يومية قدرها 10,000 وحدة، فإن ذلك يستهلك حوالي 1 ميجابايت فقط من الذاكرة. بالنسبة لمعظم حالات الاستخدام، فإن الدقة تستحق هذه التكلفة.

منذ هذا التغيير، لم تُستنفد حصتنا ولو لمرة واحدة. تتوقف مهامنا بشكل نظيف بدلاً من مواجهة أخطاء 403.

إذا كان محدد معدل الطلبات الخاص بك يعيد الضبط عند رقم صحيح من الساعة، فأنت لا تملك حداً، بل تملك ثغرة.

المصدر: https://dev.to/ahmet_gedik778845/building-a-sliding-window-rate-limiter-in-redis-for-a-multi-region-video-api-50ni