ЁЯЪж рдзрдбрд╛ 12 тАФ Rate limiting: рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рдЦрд┐рдбрдХреАрд╡рд░реАрд▓ рдмрд░рдгреАрддрд▓реЗ tokens
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 12 ┬╖ рдкреБрдвреАрд▓: lesson-13-resilience
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ11, рдЖрдгрд┐ рд▓реЛрдВрдвреНрдпрд╛рдВрдкрд╛рд╕реВрди рд╕рдВрд░рдХреНрд╖рдг: loop рдордзреНрдпреЗ рдЕрдбрдХрд▓реЗрд▓реЗ рд╕рджреЛрд╖ app, scraper, login рд╣рд▓реНрд▓рд╛, рдХрд┐рдВрд╡рд╛ рдлрдХреНрдд рдЦреВрдк рдЬрд╛рд╕реНрдд рдкрд╛рдард╡рдгрд╛рд░реА рдПрдЦрд╛рджреА рд╢рд╛рд│рд╛. рддреБрдореНрд╣реА рд╢рд┐рдХрддрд╛ token bucket, leaky bucket, fixed рдЖрдгрд┐ sliding windows, limit рдХреБрдареЗ рд▓рд╛рдЧреВ рдХрд░рд╛рдпрдЪрд╛ (edge, gateway, API, рдкреНрд░рддрд┐ user, рдкреНрд░рддрд┐ key, global), 429 + Retry-After, рдЖрдгрд┐ Redis рдордзреАрд▓ counters.
- design/blocks.py тАФ
TokenBucket(rate, burst)рдЖрдгрд┐SlidingWindow(limit, window) - design/demo.py тАФ
python3 design/demo.py ratelimit - design/test_design.py тАФ check L12
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдирд┐рдпреЛрдЬрди рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд рдкреНрд░рд╢реНрдирд╛рдВрд╕рд╛рдареА рдПрдХ рдЦрд┐рдбрдХреА рдЖрд╣реЗ. рдПрдХреЗ рджрд┐рд╡рд╢реА рдПрдХрд╛ рдкрд╛рд▓рдХрд╛рдЪреНрдпрд╛ рдлреЛрдирдордзреНрдпреЗ bug рдпреЗрддреЛ. рддреЛ рддреЛрдЪ рдкреНрд░рд╢реНрди рдкреБрдиреНрд╣рд╛ рдкреБрдиреНрд╣рд╛, рд╢реЗрдХрдбреЛ рд╡реЗрд│рд╛ рд╡рд┐рдЪрд╛рд░рддреЛ. рдЗрддрд░ рдкрд╛рд▓рдХ рдЦрд┐рдбрдХреАрдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪреВрдЪ рд╢рдХрдд рдирд╛рд╣реАрдд.
рдРрд╢реНрд╡рд░реНрдпрд╛ рдЦрд┐рдбрдХреАрд╡рд░ tokens рдЪреА рдПрдХ рдмрд░рдгреА рдареЗрд╡рддреЗ. рдмрд░рдгреАрдд рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд 10 tokens рдорд╛рд╡рддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрдирд╛рд▓рд╛ рдПрдХ token рд▓рд╛рдЧрддреЛ. рдПрдХ рдорджрддрдиреАрд╕ рдмрд░рдгреАрдд рд╕реЗрдХрдВрджрд╛рд▓рд╛ 5 рд╡реЗрд│рд╛ рдПрдХ рдирд╡рд╛ token рдЯрд╛рдХрддреЛ. рдмрд░рдгреА рд░рд┐рдХрд╛рдореА рдЭрд╛рд▓реА рдХреА рдЙрддреНрддрд░ рдЕрд╕рддреЗ "рдХреГрдкрдпрд╛ рдереЛрдбреЗ рдерд╛рдВрдмрд╛" тАФ error рдирд╛рд╣реА, рдПрдХ рд╕рднреНрдп рдерд╛рдВрдмрд╛.
рдореНрд╣рдгрдЬреЗ рдкрд╛рд▓рдХ рдПрдХрджрдо 10 рдЭрдЯрдкрдЯ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░реВ рд╢рдХрддреЛ (рдмрд░рдгреАрддрд▓реЗ tokens), рдЖрдгрд┐ рдордЧ рддреНрдпрд╛рдирдВрддрд░ рд╕реБрдорд╛рд░реЗ рд╕реЗрдХрдВрджрд╛рд▓рд╛ 5. рд╕рд╛рдорд╛рдиреНрдп рдкрд╛рд▓рдХрд╛рд▓рд╛ рдмрд░рдгреА рдХрдзреА рдЬрд╛рдгрд╡рддрд╣реА рдирд╛рд╣реА. рдмрд┐рдШрдбрд▓реЗрд▓рд╛ рдлреЛрди рдордВрдж рдХреЗрд▓рд╛ рдЬрд╛рддреЛ, рдЖрдгрд┐ рдмрд╛рдХреА рд╕рдЧрд│реНрдпрд╛рдВрдЪреЗ рдХрд╛рдо рд╣реЛрддреЗ.
рдХрддрд░рд┐рдирд╛ рд╡рд┐рдЪрд╛рд░рддреЗ: "рдШрдбреНрдпрд╛рд│рд╛рдкреНрд░рдорд╛рдгреЗ рд╕реЗрдХрдВрджрд╛рд▓рд╛ 10 рдЕрд╕реЗ рдХрд╛ рдореЛрдЬрдд рдирд╛рд╣реА?" рдРрд╢реНрд╡рд░реНрдпрд╛ рддрд┐рд▓рд╛ рджрд╛рдЦрд╡рддреЗ: 0.9 рд╕реЗрдХрдВрджрд╛рд▓рд╛ 10 рдкреНрд░рд╢реНрди рдЖрдгрд┐ 1.1 рд╕реЗрдХрдВрджрд╛рд▓рд╛ рдЖрдгрдЦреА 10. рдШрдбреНрдпрд╛рд│рд╛рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рд╕реЗрдХрдВрджрд╛рдд рдлрдХреНрдд 10 рдЖрд╣реЗрдд. рдкрдг 0.2 рд╕реЗрдХрдВрджрд╛рдВрдд 20 рдЖрд▓реЗ. рд╡реЗрд│реЗрд╕реЛрдмрдд рд╕рд░рдХрдгрд╛рд▒реНрдпрд╛ рдореЛрдЬрдгреА-рдЦрд┐рдбрдХреАрдд рд╣реЗ рдЫрд┐рджреНрд░ рдирд╕рддреЗ.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
app["ЁЯУ▒ parent's app<br/>12 requests at 0 s<br/>5 more at 0.5 s"]
subgraph tb["ЁЯлЩ token bucket: 5 per s, burst 10"]
jar["10 tokens at start<br/>+2.5 by 0.5 s"]
end
subgraph sw["ЁЯкЯ sliding window: 10 per 1 s"]
log["log of the last 1 s<br/>still 10 at 0.5 s"]
end
app --> jar
app --> log
jar -->|"12 of 17 allowed"| api["ЁЯзй notice API"]
log -->|"10 of 17 allowed"| api
jar -.->|"empty"| no["ЁЯЪж 429 Too Many Requests<br/>Retry-After: 1"]
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/system-design/lesson-diagrams.html#l12
тЭУ рдХрд╛рдп
- Rate limit тАФ рдард░рд╛рд╡рд┐рдХ рд╡реЗрд│реЗрдд caller рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд рдХрд┐рддреА requests рдкрд╛рдард╡реВ рд╢рдХрддреЛ. Limit рдУрд▓рд╛рдВрдбрд▓реА рдХреА server рдХреЛрдгрддреЗрд╣реА рдЦрд░реЗ рдХрд╛рдо рдХрд░рдгреНрдпрд╛рдЖрдзреА рдкрдЯрдХрди рдЖрдгрд┐ рд╕реНрд╡рд╕реНрддрд╛рдд рдирдХрд╛рд░ рджреЗрддреЛ.
- Token bucket тАФ bucket рдордзреНрдпреЗ рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд burst tokens рдорд╛рд╡рддрд╛рдд рдЖрдгрд┐ рддреЗ рд╕реЗрдХрдВрджрд╛рд▓рд╛ rate tokens рдиреЗ рднрд░рддреЗ. рдкреНрд░рддреНрдпреЗрдХ request рдПрдХ token рдШреЗрддреЗ. рд╣реЗ рдЖрдзреА рдЫреЛрдЯрд╛ burst, рдордЧ рд╕реНрдерд┐рд░ rate рдЪрд╛рд▓реВ рджреЗрддреЗ. рдмрд╣реБрддреЗрдХ API gateways рд╣реЗрдЪ рд╡рд╛рдкрд░рддрд╛рдд.
- Leaky bucket тАФ requests рдПрдХрд╛ queue рдордзреНрдпреЗ рдерд╛рдВрдмрддрд╛рдд рдЬреА рдард░рд╛рд╡рд┐рдХ рд╡реЗрдЧрд╛рдиреЗ рд░рд┐рдХрд╛рдореА рд╣реЛрддреЗ; queue рднрд░рд▓реА рдХреА рдирд╡реНрдпрд╛ requests рдЯрд╛рдХреВрди рджрд┐рд▓реНрдпрд╛ рдЬрд╛рддрд╛рдд. рдмрд╛рд╣реЗрд░ рдпреЗрдгрд╛рд░рд╛ рдкреНрд░рд╡рд╛рд╣ рдЕрдЧрджреА рдПрдХрд╕рд╛рд░рдЦрд╛ рдЕрд╕рддреЛ, рдЬреЛ рд╣рд│реВ downstream рд▓рд╛ рд╡рд╛рдЪрд╡рддреЛ (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде рд╕реЗрдХрдВрджрд╛рд▓рд╛ 50 messages рд╕реНрд╡реАрдХрд╛рд░рдгрд╛рд░рд╛ SMS provider).
- Fixed window тАФ рдШрдбреНрдпрд╛рд│рд╛рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рд▓рд╛рд╡рдзреАрдд requests рдореЛрдЬрд╛ (рдкреНрд░рддрд┐ рдорд┐рдирд┐рдЯ, :00 рддреЗ :59). рдПрдХ counter, рдЦреВрдк рд╕реНрд╡рд╕реНрдд. рддреНрдпрд╛рдЪреЗ рдЫрд┐рджреНрд░: рдПрдХрд╛ window рдЪреНрдпрд╛ рд╢реЗрд╡рдЯреА рдЖрдгрд┐ рдкреБрдврдЪреНрдпрд╛ window рдЪреНрдпрд╛ рд╕реБрд░реБрд╡рд╛рддреАрд▓рд╛ рдЖрд▓реЗрд▓рд╛ burst limit рдЪреНрдпрд╛ рджреБрдкреНрдкрдЯ рдЕрд╕реВ рд╢рдХрддреЛ.
- Sliding window тАФ рдкреНрд░рддреНрдпреЗрдХ рдХреНрд╖рдгреА рдорд╛рдЧрдЪреНрдпрд╛ window seconds рдордзрд▓реНрдпрд╛ requests рдореЛрдЬрд╛. Log (рдкреНрд░рддреНрдпреЗрдХ request рд╕рд╛рдареА рдПрдХ рдиреЛрдВрдж, рдпрд╛ repo рдкреНрд░рдорд╛рдгреЗ) рдЕрдЪреВрдХ рдЕрд╕рддреЛ рдкрдг рдкреНрд░рддреНрдпреЗрдХ request рд╕рд╛рдареА memory рд╡рд╛рдкрд░рддреЛ. Sliding window counter рджреЛрди fixed windows рд╡рдЬрдирд╛рдиреБрд╕рд╛рд░ рдорд┐рд╕рд│рддреЛ тАФ рдЬрд╡рд│рдЬрд╡рд│ рдЕрдЪреВрдХ, рдлрдХреНрдд рджреЛрди counters.
- рдХреБрдареЗ рд▓рд╛рдЧреВ рдХрд░рд╛рдпрдЪреЗ тАФ рдерд░рд╛рдВрдордзреНрдпреЗ, рд╕рд░реНрд╡рд╛рдд рд╕реНрд╡рд╕реНрдд рдЖрдзреА:
- edge / WAF тАФ рдкреНрд░рддрд┐ IP address, рд▓реЛрдВрдвреЗ рдЖрдгрд┐ bots рд╡рд┐рд░реБрджреНрдз;
- API gateway тАФ рдкреНрд░рддрд┐ API key рдХрд┐рдВрд╡рд╛ client (рджрд┐рд╡рд╕рд╛рдЪреНрдпрд╛ quota рд╕рд╣ usage plan);
- API тАФ рдкреНрд░рддрд┐ user (parent id), рдЖрдгрд┐ рдкреНрд░рддрд┐ endpoint рдЕрдзрд┐рдХ рдХрдбрдХ (login: рдорд┐рдирд┐рдЯрд╛рд▓рд╛ 5 рдкреНрд░рдпрддреНрди; рддрд╛рддрдбреАрдЪреА рд╕реВрдЪрдирд╛ post рдХрд░рдгреЗ: рджрд┐рд╡рд╕рд╛рд▓рд╛ рдХрд╛рд╣реА);
- global тАФ рд╕рд╛рдорд╛рдИрдХ resource рд╡рд╛рдЪрд╡рд╛: database, рдХрд┐рдВрд╡рд╛ SMS provider рдЪрд╛ quota.
- 429 Too Many Requests тАФ "рд╣рд│реВ" рд╕рд╛рдареАрдЪрд╛ status code.
Retry-AfterрдЬреЛрдбрд╛ (seconds, рдХрд┐рдВрд╡рд╛ рддрд╛рд░реАрдЦ) рдореНрд╣рдгрдЬреЗ рдЪрд╛рдВрдЧрд▓реНрдпрд╛ clients рдирд╛ рдкреБрдиреНрд╣рд╛ рдХрдзреА рдкреНрд░рдпрддреНрди рдХрд░рд╛рдпрдЪрд╛ рддреЗ рдХрд│рддреЗ. рдЕрдиреЗрдХ APIs рдЙрд░рд▓реЗрд▓реЗ budget рдкрдг рдкрд╛рдард╡рддрд╛рдд (RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset, рдХрд┐рдВрд╡рд╛ рдЬреБрдиреАX-RateLimit-*рдирд╛рд╡реЗ). - Redis рдордзреАрд▓ counters тАФ 12 API servers (рдзрдбрд╛ 02) рдЕрд╕рддрд╛рдирд╛, рдкреНрд░рддреНрдпреЗрдХ server рдордзреАрд▓ counter рдореБрд│реЗ
caller рд▓рд╛ limit рдЪреНрдпрд╛ 12 рдкрдЯ рдорд┐рд│рддреЗ. Counters рдПрдХрд╛рдЪ рд╕рд╛рдорд╛рдИрдХ рдард┐рдХрд╛рдгреА рдареЗрд╡рд╛ тАФ Redis тАФ рдЖрдгрд┐
рддреЗ atomically update рдХрд░рд╛ (
INCR, рдХрд┐рдВрд╡рд╛ рдЫреЛрдЯреА Lua script). - Fail open рдХреА fail closed тАФ Redis рдмрдВрдж рдЕрд╕реЗрд▓ рддрд░ рд╕рдЧрд│реНрдпрд╛ requests рдирд╛ рдкрд░рд╡рд╛рдирдЧреА рджреНрдпрд╛рдпрдЪреА (open) рдХреА рд╕рдЧрд│реНрдпрд╛ рдирд╛рдХрд╛рд░рд╛рдпрдЪреНрдпрд╛ (closed)? рдмрд╣реБрддреЗрдХ APIs рд╕рд╛рдареА: local, рдереЛрдбреА рд╕реИрд▓ limit рдареЗрд╡реВрди open fail рдХрд░рд╛. Login рдЖрдгрд┐ payments рд╕рд╛рдареА: рдЕрдзрд┐рдХ рдХрдбрдХ рд░рд╛рд╣рд╛.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рдПрдХрд╛ caller рдиреЗ рдмрд╛рдХреА рд╕рдЧрд│реНрдпрд╛рдВрдХрдбреВрди service рд╣рд┐рд╕рдХрд╛рд╡реВрди рдШреЗрддрд╛ рдХрд╛рдорд╛ рдирдпреЗ. рдзрдбрд╛ 02 рдиреЗ app рдЪреЗ рдорд╛рдк peak рд▓рд╛ 3,507 req/s рдЕрд╕реЗ рдард░рд╡рд▓реЗ тАФ рд╕рдЧрд│реНрдпрд╛рдВрд╕рд╛рдареА рдорд┐рд│реВрди. Limit рдирд╕реЗрд▓ рддрд░ retry loop рдордзреАрд▓ рдПрдХрдЯреЗ app рдПрд╡рдвреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдкрд╛рдард╡реВ рд╢рдХрддреЗ. Limit рдкреИрд╕реЗ рдкрдг рд╡рд╛рдЪрд╡рддреЗ: 5M рддрд╛рддрдбреАрдЪреНрдпрд╛ SMS рд▓рд╛ рдЦрд░реЗ рдкреИрд╕реЗ рд▓рд╛рдЧрддрд╛рдд, рдЖрдгрд┐ рдЪреЛрд░рд▓реЗрд▓реНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХрд╛-account рдиреЗ рддреЗ рджрд╣рд╛ рд╡реЗрд│рд╛ рдкрд╛рдард╡рддрд╛ рдХрд╛рдорд╛ рдирдпреЗрдд.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
design/blocks.py рдордзреНрдпреЗ:
TokenBucket(rate, burst).allow(t)(t тИТ last) ├Ч ratetokens рдЬреЛрдбрддреЛ (burstрдкрд░реНрдпрдВрдд), рдордЧ token рдЕрд╕реЗрд▓ рддрд░ рдПрдХ рдШреЗрддреЛ.SlidingWindow(limit, window).allow(t)рдкрд░рд╡рд╛рдирдЧреА рджрд┐рд▓реЗрд▓реНрдпрд╛ рд╡реЗрд│рд╛рдВрдЪрд╛ log рдареЗрд╡рддреЛ,t тИТ windowрдкреЗрдХреНрд╖рд╛ рдЬреБрдиреНрдпрд╛ рдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ, рдЖрдгрд┐limitрдкреЗрдХреНрд╖рд╛ рдХрдореА рдЙрд░рд▓реНрдпрд╛ рдЕрд╕рддреАрд▓ рддрд░ рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЛ.
design/demo.py рдордзреАрд▓ ratelimit() t = 0 s рд▓рд╛ 12 requests рдЖрдгрд┐ t = 0.5 s рд▓рд╛ 5
requests рджреЛрдиреНрд╣реАрдВрдордзреВрди рдкрд╛рдард╡рддреЛ. рдЦрд╛рд▓рдЪрд╛ snippet edge рд╡рд░рдЪреЗ рдЫрд┐рджреНрд░ рджрд╛рдЦрд╡рдгреНрдпрд╛рд╕рд╛рдареА, inline рд▓рд┐рд╣рд┐рд▓реЗрд▓рд╛ рдПрдХ рдЫреЛрдЯрд╛
fixed-window counter рдЬреЛрдбрддреЛ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 design/demo.py ratelimit
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from blocks import TokenBucket, SlidingWindow
burst = [0.0] * 12 + [0.5] * 5
for rate, b in ((5, 10), (5, 20), (10, 10), (1, 5)):
tb = TokenBucket(rate, b); print(f"token bucket rate {rate:>2}/s burst {b:>2} тЖТ {sum(tb.allow(t) for t in burst):>2} of 17")
steady = [i * 0.1 for i in range(30)] # one request every 100 ms for 3 s = 10 per second
tb, sw = TokenBucket(5, 10), SlidingWindow(10, 1.0)
print("steady 10/s for 3 s тЖТ bucket", sum(tb.allow(t) for t in steady), "of 30 ┬╖ window", sum(sw.allow(t) for t in steady), "of 30")
edge = [0.9] * 10 + [1.1] * 10 # 10 just before a minute-mark, 10 just after
def fixed(times, limit=10, window=1.0):
counts, ok = {}, 0
for t in times:
k = int(t // window)
if counts.get(k, 0) < limit: counts[k] = counts.get(k, 0) + 1; ok += 1
return ok
sw = SlidingWindow(10, 1.0)
print("20 requests around t=1.0 s тЖТ fixed window lets", fixed(edge), "in 0.2 s ┬╖ sliding window lets", sum(sw.allow(t) for t in edge))
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
ratelimit рд╣реЗ рдЫрд╛рдкрддреЛ:
тФАтФА one parent's app sends 12 requests at t=0 s and 5 more at t=0.5 s
token bucket (5/s, burst 10) тЖТ 12 of 17 allowed ┬╖ sliding window (10 per 1 s) тЖТ 10 of 17
the bucket refilled 2.5 tokens in 0.5 s, so 2 more got in; the window still counts the first 10 until t=1.0 s
token bucket: smooth rate + a burst allowance ┬╖ window: a hard count per period
limit per user, per API key and globally; answer 429 with Retry-After; keep the counters in Redis
рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
token bucket rate 5/s burst 10 тЖТ 12 of 17
token bucket rate 5/s burst 20 тЖТ 17 of 17
token bucket rate 10/s burst 10 тЖТ 15 of 17
token bucket rate 1/s burst 5 тЖТ 5 of 17
steady 10/s for 3 s тЖТ bucket 24 of 30 ┬╖ window 30 of 30
20 requests around t=1.0 s тЖТ fixed window lets 20 in 0.2 s ┬╖ sliding window lets 10
Tests рдордзреНрдпреЗ тЬЕ L12 the token bucket refills, the window still counts рдЕрд╕рддреЗ.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
Burst рдкрд╣рд┐рд▓рд╛ рдХреНрд╖рдг рдард░рд╡рддреЛ (10 tokens тЖТ рдПрдХрджрдо 10; 20 тЖТ рд╕рдЧрд│реНрдпрд╛ 17 рдЬрд╛рддрд╛рдд), рдЖрдгрд┐ rate рдкреБрдврдЪреЗ рдард░рд╡рддреЛ (10/s рд╣рд╛ 0.5 s рдордзреНрдпреЗ 5 tokens рднрд░рддреЛ тЖТ 15). рд╕реЗрдХрдВрджрд╛рд▓рд╛ рд╕реНрдерд┐рд░ 10 рд╣реЗ "рд╕реЗрдХрдВрджрд╛рд▓рд╛ 10" window рд╕рд╛рдареА рдареАрдХ рдЖрд╣реЗ (30 рдкреИрдХреА 30), рдкрдг 5/s рдЪреЗ bucket рдлрдХреНрдд 24 рдЖрдд рд╕реЛрдбрддреЗ: burst рдХреНрд╖рдгрднрд░ рд╣реА рддрдлрд╛рд╡рдд рд▓рдкрд╡рддреЛ, рдордЧ rate рдЪреЗ рд░рд╛рдЬреНрдп рдЪрд╛рд▓рддреЗ. рдЖрдгрд┐ fixed window рдиреЗ рджреЛрди windows рдЪреНрдпрд╛ рд╕реАрдореЗрд╡рд░ 0.2 s рдордзреНрдпреЗ 20 requests рдЖрдд рд╕реЛрдбрд▓реНрдпрд╛ тАФ рддреНрдпрд╛рдЪреНрдпрд╛ limit рдЪреНрдпрд╛ рджреБрдкреНрдкрдЯ; sliding window рдиреЗ 10 рд╕реЛрдбрд▓реНрдпрд╛.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- рд╕рд╛рдорд╛рдИрдХ counter рдРрд╡рдЬреА рдкреНрд░рддреНрдпреЗрдХ server рдЪреА limit тАФ 12 servers, limit рдЪреНрдпрд╛ 12 рдкрдЯ
- рдлрдХреНрдд рдкреНрд░рддрд┐-IP limit тАФ рдПрдХрд╛ NAT address рдорд╛рдЧрдЪреА рд╕рдВрдкреВрд░реНрдг рд╢рд╛рд│рд╛ рддреА рд╡рд╛рдЯреВрди рдШреЗрддреЗ, рдЖрдгрд┐ рдЕрдиреЗрдХ addresses рдЕрд╕рд▓реЗрд▓рд╛ рд╣рд▓реНрд▓реЗрдЦреЛрд░ рд╡рд╛рдЯреВрди рдШреЗрдд рдирд╛рд╣реА
- рдкреНрд░рддреНрдпреЗрдХ endpoint рд╕рд╛рдареА рдПрдХрдЪ limit тАФ login рдЖрдгрд┐ "рддрд╛рддрдбреАрдЪреА рд╕реВрдЪрдирд╛ рдкрд╛рдард╡рд╛" рдпрд╛рдВрдирд╛ рдЦреВрдк рдХрдбрдХ limits рд▓рд╛рдЧрддрд╛рдд
Retry-Afterрд╢рд┐рд╡рд╛рдп429тАФ clients рдЕрдВрджрд╛рдЬ рдХрд░рддрд╛рдд, рдЖрдгрд┐ рдЕрдиреЗрдХрджрд╛ "рд▓рдЧреЗрдЪ" рдЕрд╕рд╛ рдЕрдВрджрд╛рдЬ рдХрд░рддрд╛рдд- backoff рдЖрдгрд┐ jitter рд╢рд┐рд╡рд╛рдп
429рд▓рдЧреЗрдЪ retry рдХрд░рдгрд╛рд░реЗ clients (рдзрдбрд╛ 13) - Redis рдордзреНрдпреЗ рдЖрдзреА рд╡рд╛рдЪрд╛-рдордЧ рд▓рд┐рд╣рд╛ counters (
GET, рдордЧSET) тАФ рджреЛрди servers рд╢рд░реНрдпрдд рд▓рд╛рд╡рддрд╛рдд;INCRрдХрд┐рдВрд╡рд╛ Lua рд╡рд╛рдкрд░рд╛ - paid provider рд▓рд╛ (SMS, email, AI model) рддреБрдореНрд╣реА рдХрд░рдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ calls рд╡рд░ рдХреЛрдгрддреАрд╣реА limit рдирд╕рдгреЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ API Gateway usage plan: token bucket (rate + burst) рдЖрдгрд┐ рдкреНрд░рддрд┐ API key рд░реЛрдЬрдЪрд╛ quota:
aws apigateway create-usage-plan --name parents-app \
--throttle burstLimit=10,rateLimit=5 \
--quota limit=100000,period=DAY \
--api-stages apiId=a1b2c3d4e5,stage=prod
Edge рд╡рд░ AWS WAF rate-based rule тАФ 5 рдорд┐рдирд┐рдЯрд╛рдВрдд 2,000 рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд requests рдкрд╛рдард╡рдгрд╛рд░рд╛ IP address
block рдХрд░рд╛ (web ACL рдЪреНрдпрд╛ Rules рдЪрд╛ рднрд╛рдЧ):
{ "Name": "per-ip-flood", "Priority": 1, "Action": { "Block": {} },
"Statement": { "RateBasedStatement": { "Limit": 2000, "EvaluationWindowSec": 300, "AggregateKeyType": "IP" } },
"VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "per-ip-flood" } }
API рд╕рдореЛрд░ NGINX тАФ рдкреНрд░рддрд┐ client address рдПрдХ leaky bucket, burst рд╕рд╣, 429 рдЙрддреНрддрд░ рджреЗрдгрд╛рд░рд╛:
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
server {
location /api/ {
limit_req zone=perip burst=10 nodelay;
limit_req_status 429;
proxy_pass http://notice_api;
}
}
Redis рдордзреНрдпреЗ рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рд▓рдХрд╛рд╕рд╛рдареА sliding-window log, рдПрдХрд╛рдЪ Lua script рдордзреНрдпреЗ atomic (keys рдЖрдкреЛрдЖрдк expire рд╣реЛрддрд╛рдд):
-- KEYS[1] = "rl:parent:42" ARGV = now_ms, window_ms, limit, request_id
redis.call("ZREMRANGEBYSCORE", KEYS[1], 0, ARGV[1] - ARGV[2])
if redis.call("ZCARD", KEYS[1]) < tonumber(ARGV[3]) then
redis.call("ZADD", KEYS[1], ARGV[1], ARGV[4])
redis.call("PEXPIRE", KEYS[1], ARGV[2])
return 1
end
return 0
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: limits API рдХрд░рд╛рд░рд╛рдд (рдзрдбрд╛ 03) рдПрдХрд╛ table рдЪреНрдпрд╛ рд╕реНрд╡рд░реВрдкрд╛рдд рд▓рд┐рд╣рд╛: рдкреНрд░рддрд┐ IP, рдкреНрд░рддрд┐ key, рдкреНрд░рддрд┐ user, рдкреНрд░рддрд┐ endpoint, global. рдордЧ
429рдЙрддреНрддрд░рд╛рдВрдЪреНрдпрд╛ рд╕рдВрдЦреНрдпреЗрдЪрд╛ graph рдХрд╛рдврд╛. рдЕрдЪрд╛рдирдХ рд╡рд╛рдв рдореНрд╣рдгрдЬреЗ рдПрдХрддрд░ рд╣рд▓реНрд▓рд╛ рдХрд┐рдВрд╡рд╛ рдЦрд▒реНрдпрд╛ рдкрд╛рд▓рдХрд╛рдВрд╕рд╛рдареА рдЦреВрдк рдХрдбрдХ limit тАФ рджреЛрдиреНрд╣реАрдВрдордзреНрдпреЗ рдПрдЦрд╛рджреНрдпрд╛ рдорд╛рдгрд╕рд╛рдиреЗ рдкрд╛рд╣рд╛рдпрд▓рд╛ рд╣рд╡реЗ.
тПня╕П рдкреБрдвреЗ
Limits рдмрд╛рд╣реЗрд░реВрди рдпреЗрдгрд╛рд░реЗ рд▓реЛрдВрдвреЗ рдерд╛рдВрдмрд╡рддрд╛рдд. рдкрдг рдЖрддрд▓рд╛ рдПрдЦрд╛рджрд╛ рднрд╛рдЧ рдЕрдкрдпрд╢реА рдЭрд╛рд▓рд╛ рддрд░ тАФ photo service, database, рд╕рдВрдкреВрд░реНрдг data centre? Design рдордзреВрдирдЪ resilience.
git checkout lesson-13-resilience