🏫 The School›🏢 APIs›⏱️ धडा 10 — Rate limits आणि caching: काउंटरवरची रांग
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

⏱️ धडा 10 — Rate limits आणि caching: काउंटरवरची रांग

📍 तुम्ही इथे आहात: 12 पैकी धडा 10 · मागे: lesson-09-versioning · पुढे: lesson-11-beyond-rest


📦 या ब्रँचमध्ये काय आहे

धडे 01–09, आणि त्यासोबत सगळे एकदम आले की काय होते: rate limits (429, Retry-After), caching (ETag, If-None-Match, Cache-Control), timeouts आणि jitter सह backoff — आणि हे सगळे करणारा एक सभ्य client. खरी file:

🧒 5 वर्षांच्या मुलाला समजावल्यासारखे

गर्दीच्या दिवशी तीन गोष्टी फ्रंट ऑफिस शांत ठेवतात:

  1. रांग ⏱️ — प्रत्येक पाहुणा 10 सेकंदात 20 slips देऊ शकतो. 21 व्या slip ला 429 Too Many Requests शिक्का आणि एक टीप मिळते: Retry-After: 10. ओरडून कोणीही office रिकामे करू शकत नाही.
  2. प्रत 🧾 — तुम्ही एखादा विद्यार्थी आणता तेव्हा शिक्क्यावर एक ETag असतो, उत्तराचा ठसा (fingerprint). पुढच्या वेळी तुम्ही "बदलले असेल तरच" (If-None-Match: "0e9a…") जोडता आणि, ते बदलले नसेल तर, clerk body शिवाय 304 Not Modified उत्तर देतो: तुमची प्रत अजून चांगली आहे. Cache-Control: max-age=30 याच्याही पुढे जाते: 30 सेकंद, विचारूसुद्धा नका.
  3. सभ्य पाहुणा 😌 — कधीच कायम वाट पाहत नाही (timeout 3 s); 429 सांगितल्यावर किंवा office बिघडलेले असताना (5xx), दार ठोकत बसण्याऐवजी दर वेळी जास्त, थोड्या randomness सह थांबतो (backoff + jitter: 1 s, 2 s, 4 s …); काही प्रयत्नांनंतर हार मानतो.

🗺️ आकृती

sequenceDiagram
    participant C as 😌 polite client
    participant S as 🏢 school-api
    C->>S: 1 GET /v1/students/2
    S-->>C: 2 200 · ETag "0e9a…" · Cache-Control: max-age=30
    C->>S: 3 GET /v1/students/2  If-None-Match: "0e9a…"
    S-->>C: 4 304 Not Modified (no body — your copy is good)
    C->>S: 5 slip #21 within 10 s
    S-->>C: 6 429 rate_limited · Retry-After: 10
    Note over C: sleep Retry-After (+ jitter), retry, give up after 4 tries

❓ काय

🤔 का

कारण मर्यादा नसलेले API सगळ्यांसाठी बंद पडण्यापासून फक्त एका bug च्या अंतरावर असते, आणि timeouts व backoff नसलेला client हाच तो bug असतो. Caching हाच धडा दुसऱ्या बाजूने: सर्वात स्वस्त slip म्हणजे जी कधी दिलीच जात नाही. या तिन्ही सवयी मिळून counter वर "resilient" चा बहुतेक अर्थ होतो.

🔧 कसे (या repo मध्ये)

rate_limited() (प्रत्येक client IP साठी bucket, 10 s मध्ये RATE_LIMIT, 429 + retry_after_seconds), etag_of() + do_GET मधली If-None-Match तपासणी, आणि प्रत्येक read वर Cache-Control. api/client.py client चा अर्धा भाग दाखवते: timeout, स्वतःच्या cache मधून If-None-Match, 429 आणि 5xx वर backoff, 4 चा retry budget.

🧪 करून पाहा

B=http://127.0.0.1:8080/v1
T=$(curl -si $B/students/2 | tr -d '\r' | awk -F': ' '/^ETag/{print $2}'); echo "etag $T"
curl -si $B/students/2 -H "If-None-Match: $T" | sed -n '1p'                 # 304
for i in $(seq 1 25); do curl -s -o /dev/null -w '%{http_code} ' $B/health; done; echo   # 200 ×20 then 429
curl -s $B/health                                                            # the 429 body: retry_after_seconds
sleep 10; python3 api/client.py                                              # 200, then 304 from cache, then 200s
RATE_LIMIT=3 python3 api/school_api.py 8081 & sleep 1; for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code} ' http://127.0.0.1:8081/v1/health; done; echo

✅ तपासा — तुम्हाला काय दिसायला हवे

Body शिवाय 304; वीस 200 मग 429; retry_after_seconds: 10 असलेला JSON error. client.py एक 200, मग स्वतःच्या cache मधून दिलेला 304, मग तीन 200 छापते — आणि bucket रिकामी असताना तुम्ही तो चालवला तर वाढत जाणाऱ्या आणि थांबणाऱ्या 429 → sleeping 1.7s (attempt 1) ओळी. Port 8081 वर RATE_LIMIT=3 सह, चौथा call 429 असतो.

🏁 तुम्ही आत्ताच काय सिद्ध केले

गर्दीच्या लोंढ्यातही counter न्याय्य राहतो, न बदललेल्या उत्तराला body चा खर्च लागत नाही, आणि सभ्य client जाणूनबुजून थांबतो, मागे सरकतो (back off) आणि हार मानतो.

⚠️ नेहमीच्या चुका

🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: AWS API Gateway, Kubernetes Ingress controllers आणि प्रत्येक CDN नेमके हेच headers आणि codes लागू करतात; सभ्य-client चे नियम प्रत्येक SDK मध्ये आधीच बांधलेले असतात. ते इथे एकदा शिका, सगळीकडे ओळखा.

⏭️ पुढे

REST हा एकमेव आकार नाही. GraphQL, gRPC, webhooks आणि events — आणि जेव्हा counter तुम्हाला परत call करतो.

git checkout lesson-11-beyond-rest

⏱️ Lesson 10 — Rate limits & caching: the queue at the counter

📍 You are here: Lesson 10 of 12 · Previous: lesson-09-versioning · Next: lesson-11-beyond-rest


📦 What's in this branch

Lessons 01–09, plus what happens when everyone arrives at once: rate limits (429, Retry-After), caching (ETag, If-None-Match, Cache-Control), timeouts and backoff with jitter — and a polite client that does all of it. Real file:

🧒 Explain like I'm 5

Three things keep the front office calm on a busy day:

  1. The queue ⏱️ — each visitor may hand in 20 slips per 10 seconds. Slip 21 gets a 429 Too Many Requests stamp with a note: Retry-After: 10. Nobody can empty the office by shouting.
  2. The copy 🧾 — when you fetch a student, the stamp carries an ETag, a fingerprint of the answer. Next time you add "only if it changed" (If-None-Match: "0e9a…") and, if it has not, the clerk answers 304 Not Modified with no body: your copy is still good. Cache-Control: max-age=30 goes further: for 30 seconds, don't even ask.
  3. The polite visitor 😌 — never waits forever (timeout 3 s); when told 429 or when the office is broken (5xx), waits longer each time with a little randomness (backoff + jitter: 1 s, 2 s, 4 s …) instead of hammering the door; gives up after a few tries.

🗺️ Diagram

sequenceDiagram
    participant C as 😌 polite client
    participant S as 🏢 school-api
    C->>S: 1 GET /v1/students/2
    S-->>C: 2 200 · ETag "0e9a…" · Cache-Control: max-age=30
    C->>S: 3 GET /v1/students/2  If-None-Match: "0e9a…"
    S-->>C: 4 304 Not Modified (no body — your copy is good)
    C->>S: 5 slip #21 within 10 s
    S-->>C: 6 429 rate_limited · Retry-After: 10
    Note over C: sleep Retry-After (+ jitter), retry, give up after 4 tries

❓ What

🤔 Why

Because an API without limits is one bug away from being down for everyone, and a client without timeouts and backoff is the bug. Caching is the same lesson from the other side: the cheapest slip is the one never handed in. All three habits together are most of what "resilient" means at the counter.

🔧 How (in this repo)

rate_limited() (bucket per client IP, RATE_LIMIT per 10 s, 429 + retry_after_seconds), etag_of() + the If-None-Match check in do_GET, and Cache-Control on every read. api/client.py shows the client half: timeout, If-None-Match from its cache, backoff on 429 and 5xx, a retry budget of 4.

🧪 Try it

B=http://127.0.0.1:8080/v1
T=$(curl -si $B/students/2 | tr -d '\r' | awk -F': ' '/^ETag/{print $2}'); echo "etag $T"
curl -si $B/students/2 -H "If-None-Match: $T" | sed -n '1p'                 # 304
for i in $(seq 1 25); do curl -s -o /dev/null -w '%{http_code} ' $B/health; done; echo   # 200 ×20 then 429
curl -s $B/health                                                            # the 429 body: retry_after_seconds
sleep 10; python3 api/client.py                                              # 200, then 304 from cache, then 200s
RATE_LIMIT=3 python3 api/school_api.py 8081 & sleep 1; for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code} ' http://127.0.0.1:8081/v1/health; done; echo

✅ Verify — what you should see

A 304 with no body; twenty 200s then 429s; a JSON error with retry_after_seconds: 10. client.py prints a 200, then a 304 served from its own cache, then three 200s — and if you run it while the bucket is empty, 429 → sleeping 1.7s (attempt 1) lines that grow and stop. On port 8081 with RATE_LIMIT=3, the fourth call is 429.

🏁 What you just proved

The counter stays fair under a flood, an unchanged answer costs no body, and a polite client waits, backs off and gives up on purpose.

⚠️ Common mistakes

🏭 Why this matters in production: the AWS API Gateway, Kubernetes Ingress controllers and every CDN implement exactly these headers and codes; the polite-client rules are the ones every SDK bakes in. Learn them once here, recognise them everywhere.

⏭️ Next

REST is not the only shape. GraphQL, gRPC, webhooks and events — and when the counter calls you back.

git checkout lesson-11-beyond-rest
← PreviousversioningNext →beyond rest

This page is the lesson's README from the lesson-10-rate-limits-caching branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.