הגבלת קצב ב-API: token bucket, sliding window ואכיפה מבוזרת
מאת צוות מדיה דיל · 09.08.2026 · API Engineering · 8 דק׳
API בלי הגבלת קצב הוא הזמנה לתקלה — לא רק מהתקפה, גם מבאג של לקוח תמים. מדריך עומק לאלגוריתמי rate limiting ולעיצוב נכון.
API בלי הגבלת קצב הוא בקשה פתוחה לתקלה — לא בהכרח מתקפה זדונית, לעיתים קרובות זה פשוט לקוח עם באג שיוצר לולאה אינסופית של קריאות, או קמפיין שיווקי מוצלח שמביא פי עשרה תעבורה בלי אזהרה מוקדמת. Rate limiting הוא המנגנון שמגן על המערכת מפני שני התרחישים גם יחד — הוא קובע כמה בקשות מותר לצרכן מסוים לשלוח בפרק זמן נתון, ומה קורה כשהוא חורג מהמכסה הזו.
מעבר להגנה טכנית, rate limiting הוא גם כלי עסקי: הוא מאפשר להבחין בין תוכניות שימוש שונות (free tier מול paid tier), ולהבטיח שצרכן אחד לא "גונב" משאבים משאר הצרכנים במערכת multi-tenant. ההחלטה איך לעצב אותו משפיעה גם על חוויית המפתחים שצורכים את ה-API — מדיניות לא ברורה או לא עקבית הופכת אינטגרציה פשוטה לתסכול מתמשך.
אלגוריתמי הגבלה: fixed window, sliding window, token bucket
האלגוריתם הפשוט ביותר הוא fixed window — סופרים בקשות בחלון זמן קבוע (למשל 100 בקשות בדקה, מתאפס בדיוק בתחילת כל דקה). הבעיה שלו היא אפקט "פרץ בגבול" (boundary burst): צרכן יכול לשלוח 100 בקשות בשנייה האחרונה של דקה אחת, ועוד 100 בשנייה הראשונה של הדקה הבאה — 200 בקשות בפועל בתוך שנייה אחת, למרות שהמכסה "הרשמית" היא 100 לדקה.
Sliding window log פותר את הבעיה על ידי מעקב מדויק אחר timestamp של כל בקשה בפועל, ובדיקת כמה בקשות היו בחלון הזמן הנע האחרון (לא הקבוע) — מדויק אך יקר בזיכרון, כי צריך לשמור timestamp לכל בקשה. Sliding window counter הוא פשרה נפוצה: משקלל בין המונה של החלון הנוכחי לחלון הקודם באופן פרופורציונלי, נותן קירוב טוב מאוד לדיוק המלא בעלות זיכרון נמוכה משמעותית.
// Token bucket: הגישה הגמישה ביותר
{
"capacity": 100, // מקסימום טוקנים בדלי
"refill_rate": 10, // טוקנים נוספים לשנייה
"current_tokens": 45 // מצב נוכחי
}
Token bucket, האלגוריתם הנפוץ ביותר במערכות production בשלות, עובד אחרת: לכל צרכן יש "דלי" עם קיבולת מקסימלית של טוקנים, שמתמלא בקצב קבוע. כל בקשה צורכת טוקן; אם הדלי ריק, הבקשה נדחית. היתרון המרכזי הוא שהוא מאפשר burst מבוקר — צרכן שלא ניצל את המכסה שלו לאחרונה יכול לשלוח פרץ בקשות גדול יחסית בבת אחת (עד לקיבולת הדלי), בעוד קצב ההשלמה (refill rate) מגביל את הקצב הממוצע לאורך זמן. זה מדמה הרבה יותר טוב את הדפוס האמיתי של שימוש לגיטימי, שבו לקוחות לא שולחים בקשות בקצב אחיד מושלם אלא בפרצים טבעיים.
איפה בארכיטקטורה אוכפים את ההגבלה
המקום הטבעי לאכוף rate limiting הוא API Gateway — נקודת הכניסה היחידה שכל הבקשות עוברות דרכה, מה שהופך אותו למקום היעיל ביותר לחסום בקשות עודפות לפני שהן בכלל מגיעות לשירותים הפנימיים. הרחבנו על התפקיד המרכזי הזה במדריך ארכיטקטורת API Gateway. אכיפה ב-Gateway גם מבטיחה עקביות — כל השירותים מאחוריו מוגנים באותה מדיניות, בלי שכל אחד יצטרך לממש הגבלה בעצמו.
במערכות distributed עם כמה instances של Gateway, האתגר הוא שיתוף מצב המכסה בין כל ה-instances — מונה מקומי לכל instance לא מספיק, כי צרכן יכול "לפצל" בקשות בין instances שונים ולעקוף בפועל את המכסה הכוללת. הפתרון הנפוץ הוא store מרכזי ומהיר, בדרך כלל Redis, שכל ה-instances קוראים וכותבים אליו את מצב המכסה, עם פעולות אטומיות (כמו INCR עם TTL) שמונעות race conditions כשכמה בקשות מגיעות בו-זמנית מאותו צרכן.
הגבלה לפי מה: משתמש, IP, מפתח API, או endpoint
rate limiting אפקטיבי דורש בחירת מפתח זיהוי נכון. הגבלה לפי IP פשוטה אך בעייתית — משתמשים רבים מאחורי NAT משותף (כמו רשת ארגונית) חולקים IP אחד, מה שגורם להם "לשתף" מכסה בטעות. הגבלה לפי מפתח API או משתמש מזוהה מדויקת יותר, אבל דורשת אימות שכבר קרה לפני שאפשר להחיל את ההגבלה. הרחבנו על עיצוב מפתחות API כזהות יציבה למטרה הזו במדריך אבטחת מפתחות API.
עיצוב בשל בדרך כלל משלב כמה שכבות במקביל: הגבלה גסה לפי IP כהגנה בסיסית נגד התקפות DDoS גם לפני אימות, והגבלה מדויקת יותר לפי משתמש או מפתח API אחרי אימות, שמותאמת לתוכנית השימוש הספציפית שלו. חשוב גם להבדיל בין הגבלה גלובלית (לכל ה-API) להגבלה ספציפית ל-endpoint — endpoint יקר חישובית (כמו חיפוש מורכב או export של דוח גדול) לרוב צריך מכסה נפרדת ומחמירה יותר מ-endpoint פשוט של קריאת רשומה בודדת.
תקשורת עם הצרכן: headers ותשובת שגיאה ברורה
rate limiting טוב לא רק חוסם — הוא מתקשר בבירור. תקן נפוץ הוא headers כמו X-RateLimit-Limit (המכסה הכוללת), X-RateLimit-Remaining (כמה נשאר בחלון הנוכחי), ו-X-RateLimit-Reset (מתי המכסה מתאפסת), שמאפשרים ל-client חכם להתאים את קצב הבקשות שלו מראש ולהימנע מחריגה בכלל. כשכן קורית חריגה, קוד הסטטוס הנכון הוא 429 Too Many Requests, יחד עם header Retry-After שמנחה בדיוק כמה זמן לחכות לפני ניסיון חוזר.
ה-headers האלה חשובים במיוחד לצרכנים שבונים אינטגרציה ארוכת-טווח — SDK טוב שצרכן משתמש בו יכול לממש backoff אוטומטי לפי המידע הזה, במקום לנחש או להציף את השרת בניסיונות חוזרים תכופים מדי שרק מחמירים את המצב.
Distributed Rate Limiting: כשהמערכת רצה על כמה שרתים
ברגע שיש יותר מ-instance אחד של השירות שמבצע את ההגבלה, נדרש מנגנון תיאום כדי שהמכסה תיאכף נכון על כל התעבורה של אותו צרכן, לא רק על החלק שמגיע לכל instance ספציפי. הפתרון הנפוץ הוא Redis כ-store מרכזי, עם סקריפט Lua אטומי שמבצע את הבדיקה והעדכון של המונה בפעולה יחידה בלתי ניתנת לפיצול, כדי למנוע race condition שבו שני threads בודקים את המכסה בו-זמנית ושניהם "רואים" מקום פנוי לפני שהעדכון נכתב בחזרה.
העלות של distributed rate limiting היא latency נוסף — כל בקשה דורשת round trip ל-Redis לפני שאפשר להחליט אם לאשר אותה. במערכות עם רגישות קיצונית ל-latency, אפשר לשקול גישה משוערבת: כל instance שומר cache מקומי קצר-טווח של המכסה ומסתנכרן עם ה-store המרכזי בפרקי זמן קצרים, מה שמקריב מעט מדיוק ההגבלה (יכול לאפשר חריגה קלה זמנית) תמורת ביצועים משמעותית טובים יותר. ההחלטה בין דיוק מוחלט לביצועים היא בדיוק סוג הטרייד-אוף שכדאי לקבל במודע, לא בטעות.
Rate Limiting ב-GraphQL: מעבר לספירת בקשות
ב-REST, ספירת בקשות היא לרוב מדד סביר לעומס, כי כל בקשה עושה בערך את אותה כמות עבודה. ב-GraphQL, המצב שונה לחלוטין — query בודד יכול להיות זול (בקשת שדה אחד) או יקר מאוד (query מקונן עמוק עם המון nested resolvers). הגבלה לפי מספר בקשות בלבד לא מגנה מפני client שמריץ מעט queries יקרים במיוחד. הפתרון הוא query complexity analysis — חישוב "עלות" משוערת לכל query לפי מבנה ה-nesting ומספר השדות המבוקשים, והגבלה לפי סך העלות המצטברת ולא לפי מספר הבקשות הגולמי, כפי שהרחבנו במדריך ארכיטקטורת GraphQL.
Rate Limiting מול Throttling: הבחנה שקל לפספס
המונחים משמשים לעיתים בערבוביה, אבל יש הבדל מושגי חשוב: rate limiting בדרך כלל דוחה בקשות עודפות בצורה קשיחה עם שגיאה (429), בעוד throttling נוטה להאט את קצב העיבוד באופן הדרגתי — לעיתים על ידי עיכוב מכוון (delay) של תשובות במקום דחייה מוחלטת, מה שנותן חוויה חלקה יותר לצרכן שחורג מעט מהמכסה. הרחבנו על ההבדל הזה לעומק ועל מתי כל גישה מתאימה יותר במדריך הייעודי Throttling ב-API.
טעויות נפוצות בפרודקשן
הטעות הראשונה היא מכסה גלובלית אחידה לכל הצרכנים בלי הבדלה בין תוכניות שימוש — לקוח enterprise ולקוח free tier מקבלים אותה הגבלה, מה שלא משקף את המודל העסקי בפועל ומונע שדרוג טבעי. טעות שנייה היא אכיפת rate limiting רק ב-Gateway בלי הגנה נוספת ברמת ה-DB או שירותים כבדים במיוחד — אם endpoint בודד יקר מספיק חישובית, גם מספר קטן יחסית של בקשות יכול להכריע אותו, ונדרשת הגנה ממוקדת נוספת מעבר למכסה הכללית.
טעות שלישית היא היעדר בדיקה מוקדמת של איך המערכת מתנהגת תחת עומס אמיתי לפני הפעלת המכסות בפרודקשן — סף שנקבע "בניחוש" בלי load testing עלול להיות נמוך מדי (חוסם שימוש לגיטימי) או גבוה מדי (לא מגן בפועל). טעות רביעית היא אי-תיעוד המכסות בבירור בתיעוד ה-API — צרכנים שלא יודעים מראש מה המכסה שלהם נתקלים בחסימות בלתי צפויות בפרודקשן, ולומדים על קיום ההגבלה רק כשהיא כבר פוגעת בהם. טעות חמישית היא הגבלה סימטרית לכל סוגי הבקשות בלי הבחנה בין קריאה לכתיבה — endpoint שכותב לרוב יקר משמעותית יותר (עדכון DB, אינדוקס מחדש, שליחת webhook) מ-endpoint שרק קורא, ולכן ראוי למכסה נפרדת ומחמירה יותר, לא לאותה מכסה גורפת שחלה על כל שיטות ה-HTTP באותה מידה.
מתי כדאי, ומתי הגבלה קשיחה מוגזמת
rate limiting נחוץ כמעט בכל API שנחשף לצרכנים חיצוניים או פנימיים מרובים, ובמיוחד ל-API-ים ציבוריים או multi-tenant. הוא פחות קריטי (אם כי עדיין מומלץ כרשת ביטחון בסיסית) עבור API פנימי לחלוטין עם מספר צרכנים קטן וידוע, ששם ניטור ותקשורת ישירה בין צוותים לרוב מספיקים למנוע בעיות לפני שהן קורות. הרחבנו על השכבה הארגונית שמנהלת תוכניות שימוש ומכסות במדריך ארכיטקטורת API Management.
מדידה ואיטרציה: rate limiting הוא לא הגדרה חד-פעמית
מכסות שנקבעו בהשקה הראשונית לא בהכרח נשארות נכונות לאורך זמן — דפוסי שימוש משתנים, בסיס הצרכנים גדל, ותוכניות שימוש חדשות נוספות. מערכת בשלה עוקבת באופן שוטף אחר שיעור הבקשות שנדחות בפועל, ומבחינה בין דחייה לגיטימית (הגנה על המערכת) לבין דחייה שפוגעת בשימוש תקין ומעידה שהמכסה פשוט קטנה מדי ביחס לצורך האמיתי. סקירה תקופתית של המדדים האלה, יחד עם משוב ישיר מצרכנים שנתקלים בחסימות תכופות, מאפשרת לכוונן את המכסות בהדרגה במקום להשאיר אותן קבועות מרגע ההגדרה הראשונית ועד שמישהו מתלונן בקול רם מספיק.
סיכום
Rate limiting טוב הוא לא רק מגן — הוא תקשורת ברורה עם הצרכנים על גבולות המערכת. הבחירה באלגוריתם (token bucket לרוב עדיף), מיקום האכיפה (Gateway מרכזי), ומפתח הזיהוי (משתמש או מפתח API, לא IP בלבד) קובעים יחד אם ההגבלה מרגישה כמו מדיניות הוגנת ושקופה, או כמו מכשול בלתי צפוי שמפריע לשימוש לגיטימי.
תגיות: rate limiting · token bucket · API throttling · 429 Too Many Requests · distributed rate limiting · Redis