ЁЯПл The SchoolтА║ЁЯУИ ScalingтА║ЁЯкД рдзрдбрд╛ 08 тАФ Serverless scaling: рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рд▓рдХрд╛рд╕рд╛рдареА рдПрдХ рдЦрд┐рдбрдХреА
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯкД рдзрдбрд╛ 08 тАФ Serverless scaling: рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рд▓рдХрд╛рд╕рд╛рдареА рдПрдХ рдЦрд┐рдбрдХреА

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 13 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧреЗ: lesson-07-kubernetes-scaling ┬╖ рдкреБрдвреЗ: lesson-09-caching-the-database


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп AWS Lambda: platform рдЪрд╛рд▓реВ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ request рд╕рд╛рдареА рдПрдХ execution environment рд╕реБрд░реВ рдХрд░рддреЛ. рддреБрдореНрд╣реА рд╢рд┐рдХрддрд╛ concurrency тЙИ requests per second ├Ч average duration (рд╕реНрдерд┐рд░ traffic рд╕рд╛рдареА тАФ рдЕрдЪрд╛рдирдХ рдЧрд░реНрджреАрд▓рд╛ headroom рд▓рд╛рдЧрддреЛ), cold starts, scaling rate, рдЖрдгрд┐ рджреЛрди рдХрд│реА тАФ reserved рдЖрдгрд┐ provisioned concurrency. scale/demo.py рдордзрд▓реЗ serverless() рдирд┐рдХрд╛рд▓рд╛рдЪреА рдЧрд░реНрджреА рдЪрд╛рд▓рд╡реВрди рджрд╛рдЦрд╡рддреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдПрдХрд╛ рдЬрд╛рджреВрдЪреНрдпрд╛ рдЬрддреНрд░реЗрдЪреА рдХрд▓реНрдкрдирд╛ рдХрд░рд╛: рдкрд╛рд▓рдХ рдЖрд▓рд╛ рдХреА рддрд┐рдЪреНрдпрд╛рд╕рд╛рдареА рдПрдХ рдирд╡реА рдЦрд┐рдбрдХреА рдЙрдЧрд╡рддреЗ. рддреА рдЧреЗрд▓реА рдХреА рджреБрд╕рд░рд╛ рдкрд╛рд▓рдХ рдЖрд▓рд╛ рддрд░ рдореНрд╣рдгреВрди рдЦрд┐рдбрдХреА рдереЛрдбрд╛ рд╡реЗрд│ рдерд╛рдВрдмрддреЗ.

рдПрдХрд╛ рд╡реЗрд│реА рдХрд┐рддреА рдЦрд┐рдбрдХреНрдпрд╛ рдЙрднреНрдпрд╛ рдЕрд╕рддрд╛рдд? рджрд░ рд╕реЗрдХрдВрджрд╛рд▓рд╛ 2,000 рдкрд╛рд▓рдХ рдЖрд▓реЗ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЬрдг рд╕рд░рд╛рд╕рд░реА 0.2 рд╕реЗрдХрдВрдж рдерд╛рдВрдмрд▓рд╛, рддрд░ рдХреЛрдгрддреНрдпрд╛рд╣реА рдХреНрд╖рдгреА рд╕реБрдорд╛рд░реЗ 400 рдЦрд┐рдбрдХреНрдпрд╛ рд╡реНрдпрд╕реНрдд рдЕрд╕рддрд╛рдд. рд╣рд╛ рд╕реНрдерд┐рд░ рдЖрдХрдбрд╛. рдкрд╛рд▓рдХ рдШреЛрд│рдХреНрдпрд╛рдиреЗ рдЖрд▓реЗ, рдХрд┐рдВрд╡рд╛ рдХрд╛рд╣реА рдЬрдг рдЦреВрдк рдЬрд╛рд╕реНрдд рд╡реЗрд│ рдерд╛рдВрдмрд▓реЗ, рддрд░ рдереЛрдбрд╛ рд╡реЗрд│ рдЬрд╛рд╕реНрдд рдЦрд┐рдбрдХреНрдпрд╛ рд╡реНрдпрд╕реНрдд рдЕрд╕рддрд╛рдд тАФ рдореНрд╣рдгреВрди рджреАрдкрд┐рдХрд╛ 400 рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрддреАрдЪреЗ рдирд┐рдпреЛрдЬрди рдХрд░рддреЗ.

рдкрд╣рд┐рд▓реНрдпрд╛рдВрджрд╛рдЪ рдЙрдЧрд╡рдгрд╛рд▒реНрдпрд╛ рдЦрд┐рдбрдХреАрд▓рд╛ рддрдпрд╛рд░ рд╡реНрд╣рд╛рдпрд▓рд╛ рдереЛрдбрд╛ рд╡реЗрд│ рд▓рд╛рдЧрддреЛ тАФ рд╣рд╛ cold start. рдЖрдзреАрдЪ рдЙрднреА рдЕрд╕рд▓реЗрд▓реА рдЦрд┐рдбрдХреА warm рдЕрд╕рддреЗ рдЖрдгрд┐ рд▓рдЧреЗрдЪ рд╕реЗрд╡рд╛ рджреЗрддреЗ.

рджреАрдкрд┐рдХрд╛ рджреЛрди рдирд┐рдпрдо рдЬреЛрдбрддреЗ:

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    r["ЁЯСк 2,000 req/s"] --> f["ЁЯкД Lambda function<br/>200 ms each"]
    f --> c["ЁЯзо concurrency тЙИ<br/>2,000 ├Ч 0.2 s = 400<br/>steady traffic; plan headroom"]
    c --> res["ЁЯФТ reserved 300<br/>runs 300 ┬╖ throttles 500 req/s (429)"]
    c --> pro["ЁЯФе provisioned 400<br/>pre-warmed ┬╖ 0 cold starts"]
    q["ЁЯПЫя╕П account: 1,000 per Region<br/>by default, raisable"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l08

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг Lambda рдмрд╣реБрддреЗрдХ requests рд╕рд╛рдареА рдзрдбреЗ 06 рдЖрдгрд┐ 07 рдордзрд▓реА warm-up рдЪреА рд╕рдорд╕реНрдпрд╛ рдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ: рдирд╡реА environments instance рд▓рд╛ boot рд╡реНрд╣рд╛рдпрд▓рд╛ рд▓рд╛рдЧрдгрд╛рд▒реНрдпрд╛ рд╡реЗрд│реЗрдкреЗрдХреНрд╖рд╛ рдЦреВрдк рдХрдореА рд╡реЗрд│рд╛рдд рд╕реБрд░реВ рд╣реЛрддрд╛рдд, рдЖрдгрд┐ рддреБрдореНрд╣реА рдлрдХреНрдд рд╡рд╛рдкрд░рд▓реЗрд▓реНрдпрд╛ рд╡реЗрд│реЗрдЪреЗрдЪ рдкреИрд╕реЗ рднрд░рддрд╛. рдкрдг рддреЛ рдорд░реНрдпрд╛рджрд╛ рджреБрд╕рд░реАрдХрдбреЗ рд╣рд▓рд╡рддреЛ: concurrency quota, рдкрд╣рд┐рд▓реНрдпрд╛ рд▓рд╛рдЯреЗрд╡рд░ cold starts, рдЖрдгрд┐ environments рдЪреА рдЧрд░реНрджреА, рдЬреНрдпрд╛рдВрдкреИрдХреА рдкреНрд░рддреНрдпреЗрдХ database connection рдЙрдШрдбреВ рд╢рдХрддреЗ (рдзрдбрд╛ 10).

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

scale/sim.py рдордзрд▓реЗ lambda_concurrency(rps, duration_ms) рдореНрд╣рдгрдЬреЗ rps ├Ч duration_ms / 1000 тАФ рд╕реНрдерд┐рд░ traffic рдЪрд╛ рдЕрдВрджрд╛рдЬ, burst рдирд╛рд╣реА рдЖрдгрд┐ scaling-rate рдЪреА рдорд░реНрдпрд╛рджрд╛ рдирд╛рд╣реА. LambdaFleet(duration_ms, cold_ms, reserved, provisioned) рдПрдХрд╛ рд╡реЗрд│реА рдПрдХ рд╕реЗрдХрдВрдж рдЪрд╛рд▓рд╡рддреЗ: рддреНрдпрд╛рд▓рд╛ ceil(concurrency) environments рд▓рд╛рдЧрддрд╛рдд, рддреНрдпрд╛рдВрдкреИрдХреА рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд reserved рдЪрд╛рд▓рд╡рддреЗ, warm рдЕрд╕рд▓реЗрд▓реНрдпрд╛рдВрдкрд▓реАрдХрдбрдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ environment рд▓рд╛ cold start рдореНрд╣рдгреВрди рдореЛрдЬрддреЗ, рдЖрдгрд┐ рдЪрд╛рд▓рд╡рддрд╛ рди рдЖрд▓реЗрд▓реНрдпрд╛ requests рдЪрд╛ рд╡рд╛рдЯрд╛ throttle рдХрд░рддреЗ. Warm environments рдкреВрд░реНрдг run рднрд░ рдареЗрд╡рд▓реА рдЬрд╛рддрд╛рдд (рдЦрд░рд╛ Lambda рдХрд╛рд╣реА рд╡реЗрд│рд╛рдиреЗ рд░рд┐рдХрд╛рдореА environments рдХрд╛рдврддреЛ). cold_ms рд╕рд╛рдард╡рд▓реЗ рдЬрд╛рддреЗ рдкрдг рд╣рд┐рд╢реЛрдмрд╛рдд рд╡рд╛рдкрд░рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реА тАФ model cold starts рдореЛрдЬрддреЗ; рддреНрдпрд╛рдВрдЪрд╛ рд╡реЗрд│ рдЬреЛрдбрдд рдирд╛рд╣реА.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 scale/demo.py serverless
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import lambda_concurrency, LambdaFleet
print("5,000 req/s ├Ч 200 ms тЖТ", lambda_concurrency(5000, 200), "environments (default Region quota: 1,000)")
for label, f in (("no reserved limit", LambdaFleet(200, 600)), ("reserved 300", LambdaFleet(200, 600, reserved=300)),
                 ("provisioned 200", LambdaFleet(200, 600, provisioned=200)), ("provisioned 400", LambdaFleet(200, 600, provisioned=400))):
    rows = [f.second(r) for r in (100, 2000, 2000, 500)]
    print(f"{label:<18} cold starts {sum(r['cold_starts'] for r in rows):>3} ┬╖ throttled {sum(r['throttled'] for r in rows):>5}")
EOF
python3 scale/test_scale.py

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

serverless рд╣реЗ print рдХрд░рддреЗ:

тФАтФА Lambda: concurrency тЙИ requests per second ├Ч average duration (steady traffic; bursts need more)
     100 req/s ├Ч 200 ms тЖТ    20 environments at once
    2000 req/s ├Ч 200 ms тЖТ   400 environments at once
    2000 req/s ├Ч  50 ms тЖТ   100 environments at once
тФАтФА the results spike, reserved concurrency 300, cold start ~600 ms:
   second 0:   100 req/s тЖТ needs  20, runs  20, cold starts  20, throttled     0
   second 1:  2000 req/s тЖТ needs 400, runs 300, cold starts 280, throttled   500
   second 2:  2000 req/s тЖТ needs 400, runs 300, cold starts   0, throttled   500
   second 3:   500 req/s тЖТ needs 100, runs 100, cold starts   0, throttled     0
тФАтФА with 400 provisioned (pre-warmed) environments: cold starts at 2,000 req/s = 0

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

5,000 req/s ├Ч 200 ms тЖТ 1000.0 environments (default Region quota: 1,000)
no reserved limit  cold starts 400 ┬╖ throttled     0
reserved 300       cold starts 300 ┬╖ throttled  1000
provisioned 200    cold starts 200 ┬╖ throttled     0
provisioned 400    cold starts   0 ┬╖ throttled     0

рдЖрдгрд┐ tests рдордзреНрдпреЗ тЬЕ L08 Lambda: reserved concurrency throttles the rest рдЕрд╕рддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

Duration рдореНрд╣рдгрдЬреЗ bill рдЪрд╛ рдЕрд░реНрдзрд╛ рднрд╛рдЧ рдЖрдгрд┐ concurrency рдЪрд╛ рдЕрд░реНрдзрд╛ рднрд╛рдЧ: 200 ms рдРрд╡рдЬреА 50 ms рд▓рд╛ 400 рдРрд╡рдЬреА 100 environments рд▓рд╛рдЧрддрд╛рдд. 5,000 req/s ├Ч 200 ms рд▓рд╛ рд╣реЗ рдПрдХрдЪ function рд╕рдВрдкреВрд░реНрдг default account quota рд╡рд╛рдкрд░реЗрд▓. Reserved 300 рдЧрд░реНрджреАрдд 1,000 requests throttle рдХрд░рддреЗ; provisioned 400 рдкреНрд░рддреНрдпреЗрдХ cold start рдХрд╛рдвреВрди рдЯрд╛рдХрддреЗ, рдЖрдгрд┐ 200 рдЕрд░реНрдзреЗ рдХрд╛рдврддрд╛рдд.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

On a real account тАФ account quota рдкрд╛рд╣рд╛, рдПрдХрд╛ function рд▓рд╛ рдорд░реНрдпрд╛рджрд╛ рдШрд╛рд▓рд╛, рдЖрдгрд┐ рдирд┐рдХрд╛рд▓рд╛рдЪреНрдпрд╛ рд╕рдХрд╛рд│рд╕рд╛рдареА рдПрдХ alias рдЖрдзреАрдЪ warm рдХрд░рд╛:

aws lambda get-account-settings --query 'AccountLimit.{total:ConcurrentExecutions,unreserved:UnreservedConcurrentExecutions}'
aws lambda put-function-concurrency --function-name results-api --reserved-concurrent-executions 300
aws lambda put-provisioned-concurrency-config --function-name results-api \
    --qualifier live --provisioned-concurrent-executions 400
aws lambda get-provisioned-concurrency-config --function-name results-api --qualifier live

Application Auto Scaling рдиреЗ provisioned concurrency schedule рдХрд░рд╛ (8:30 рд▓рд╛ рдЪрд╛рд▓реВ, 12:00 рд▓рд╛ рдмрдВрдж, IST):

aws application-autoscaling register-scalable-target --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency --min-capacity 0 --max-capacity 400
aws application-autoscaling put-scheduled-action --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --scheduled-action-name results-morning --schedule "cron(0 3 20 5 ? 2026)" \
    --scalable-target-action MinCapacity=400,MaxCapacity=400
aws application-autoscaling put-scheduled-action --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --scheduled-action-name results-morning-over --schedule "cron(30 6 20 5 ? 2026)" \
    --scalable-target-action MinCapacity=0,MaxCapacity=0

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: CloudWatch рдордзреНрдпреЗ function рдЪреЗ ConcurrentExecutions, Throttles рдЖрдгрд┐ Duration (p99) рдкрд╛рд╣рд╛. Traffic рди рд╡рд╛рдврддрд╛ concurrency рд╡рд╛рдврдд рдЕрд╕реЗрд▓ рддрд░ function рдорд╛рдЧрдЪреЗ рдХрд╛рд╣реАрддрд░реА рд╣рд│реВ рдЭрд╛рд▓реЗ рдЖрд╣реЗ.

тПня╕П рдкреБрдвреЗ

рднрд╛рдЧ 2 рдкреВрд░реНрдг: API servers, pods рдХрд┐рдВрд╡рд╛ Lambdas рдиреЗ рд╡рд╛рдвреВ рд╢рдХрддреЛ. рдЖрддрд╛ рддреНрдпрд╛рдВрдкреИрдХреА рдкреНрд░рддреНрдпреЗрдХ рдЬрдг database рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЛ тАФ рдЖрдгрд┐ database рд╕рд╣рдЬ copy рдХрд░рддрд╛ рдпреЗрдд рдирд╛рд╣реА. рдкрд╣рд┐рд▓реА рдпреБрдХреНрддреА: рддреНрдпрд╛рдЪреНрдпрд╛рд╕рдореЛрд░ рдПрдХ рд╕реВрдЪрдирд╛ рдлрд▓рдХ.

git checkout lesson-09-caching-the-database

ЁЯкД Lesson 08 тАФ Serverless scaling: a counter for every parent

ЁЯУН You are here: Lesson 08 of 13 ┬╖ Previous: lesson-07-kubernetes-scaling ┬╖ Next: lesson-09-caching-the-database


ЁЯУж What's in this branch

Lessons 01тАУ07, plus AWS Lambda: the platform starts an execution environment for each request in flight. You learn concurrency тЙИ requests per second ├Ч average duration (for steady traffic тАФ bursts need headroom), cold starts, the scaling rate, and the two dials тАФ reserved and provisioned concurrency. serverless() in scale/demo.py plays the results spike.

ЁЯзТ Explain like I'm 5

Imagine a magic fair: when a parent arrives, a new counter appears for her. When she leaves, the counter waits a while in case another parent comes.

How many counters stand at once? If 2,000 parents arrive every second and each one stays 0.2 seconds on average, then about 400 counters are busy at any moment. That is the steady number. If parents arrive in clumps, or some stay much longer, more counters are busy for a while тАФ so Dipika plans for more than 400.

A counter that appears for the first time needs a moment to set up тАФ the cold start. A counter that is already standing is warm and serves at once.

Dipika adds two rules:

ЁЯЧ║я╕П Diagram

flowchart LR
    r["ЁЯСк 2,000 req/s"] --> f["ЁЯкД Lambda function<br/>200 ms each"]
    f --> c["ЁЯзо concurrency тЙИ<br/>2,000 ├Ч 0.2 s = 400<br/>steady traffic; plan headroom"]
    c --> res["ЁЯФТ reserved 300<br/>runs 300 ┬╖ throttles 500 req/s (429)"]
    c --> pro["ЁЯФе provisioned 400<br/>pre-warmed ┬╖ 0 cold starts"]
    q["ЁЯПЫя╕П account: 1,000 per Region<br/>by default, raisable"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l08

тЭУ What

ЁЯдФ Why

Because Lambda removes the warm-up problem of lessons 06 and 07 for most requests: new environments start in well under the time an instance takes to boot, and you pay only for the time you use. But it moves the limits: a concurrency quota, cold starts on the first wave, and a crowd of environments that can each open a database connection (lesson 10).

ЁЯФз How (in this repo)

lambda_concurrency(rps, duration_ms) in scale/sim.py is rps ├Ч duration_ms / 1000 тАФ the steady-traffic estimate, with no burst and no scaling-rate limit. LambdaFleet(duration_ms, cold_ms, reserved, provisioned) plays one second at a time: it needs ceil(concurrency) environments, runs at most reserved of them, counts every environment beyond the warm ones as a cold start, and throttles the share of requests it could not run. Warm environments are kept for the whole run (real Lambda removes idle environments after a while). cold_ms is stored but not used in the arithmetic тАФ the model counts cold starts; it does not add their time.

ЁЯзк Try it

python3 scale/demo.py serverless
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import lambda_concurrency, LambdaFleet
print("5,000 req/s ├Ч 200 ms тЖТ", lambda_concurrency(5000, 200), "environments (default Region quota: 1,000)")
for label, f in (("no reserved limit", LambdaFleet(200, 600)), ("reserved 300", LambdaFleet(200, 600, reserved=300)),
                 ("provisioned 200", LambdaFleet(200, 600, provisioned=200)), ("provisioned 400", LambdaFleet(200, 600, provisioned=400))):
    rows = [f.second(r) for r in (100, 2000, 2000, 500)]
    print(f"{label:<18} cold starts {sum(r['cold_starts'] for r in rows):>3} ┬╖ throttled {sum(r['throttled'] for r in rows):>5}")
EOF
python3 scale/test_scale.py

тЬЕ Verify тАФ what you should see

serverless prints:

тФАтФА Lambda: concurrency тЙИ requests per second ├Ч average duration (steady traffic; bursts need more)
     100 req/s ├Ч 200 ms тЖТ    20 environments at once
    2000 req/s ├Ч 200 ms тЖТ   400 environments at once
    2000 req/s ├Ч  50 ms тЖТ   100 environments at once
тФАтФА the results spike, reserved concurrency 300, cold start ~600 ms:
   second 0:   100 req/s тЖТ needs  20, runs  20, cold starts  20, throttled     0
   second 1:  2000 req/s тЖТ needs 400, runs 300, cold starts 280, throttled   500
   second 2:  2000 req/s тЖТ needs 400, runs 300, cold starts   0, throttled   500
   second 3:   500 req/s тЖТ needs 100, runs 100, cold starts   0, throttled     0
тФАтФА with 400 provisioned (pre-warmed) environments: cold starts at 2,000 req/s = 0

Your snippet prints:

5,000 req/s ├Ч 200 ms тЖТ 1000.0 environments (default Region quota: 1,000)
no reserved limit  cold starts 400 ┬╖ throttled     0
reserved 300       cold starts 300 ┬╖ throttled  1000
provisioned 200    cold starts 200 ┬╖ throttled     0
provisioned 400    cold starts   0 ┬╖ throttled     0

and the tests include тЬЕ L08 Lambda: reserved concurrency throttles the rest.

ЁЯПБ What you just proved

Duration is half of the bill and half of the concurrency: 50 ms instead of 200 ms needs 100 environments instead of 400. At 5,000 req/s ├Ч 200 ms this one function would use the whole default account quota. Reserved 300 throttles 1,000 requests over the spike; provisioned 400 removes every cold start, and 200 removes half.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ see the account quota, cap a function, and pre-warm an alias for results morning:

aws lambda get-account-settings --query 'AccountLimit.{total:ConcurrentExecutions,unreserved:UnreservedConcurrentExecutions}'
aws lambda put-function-concurrency --function-name results-api --reserved-concurrent-executions 300
aws lambda put-provisioned-concurrency-config --function-name results-api \
    --qualifier live --provisioned-concurrent-executions 400
aws lambda get-provisioned-concurrency-config --function-name results-api --qualifier live

Schedule provisioned concurrency with Application Auto Scaling (on at 8:30, off at 12:00, IST):

aws application-autoscaling register-scalable-target --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency --min-capacity 0 --max-capacity 400
aws application-autoscaling put-scheduled-action --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --scheduled-action-name results-morning --schedule "cron(0 3 20 5 ? 2026)" \
    --scalable-target-action MinCapacity=400,MaxCapacity=400
aws application-autoscaling put-scheduled-action --service-namespace lambda \
    --resource-id function:results-api:live \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --scheduled-action-name results-morning-over --schedule "cron(30 6 20 5 ? 2026)" \
    --scalable-target-action MinCapacity=0,MaxCapacity=0

ЁЯПн Why this matters in production: watch the function's ConcurrentExecutions, Throttles and Duration (p99) in CloudWatch. Concurrency rising with no rise in traffic means something behind the function got slower.

тПня╕П Next

Part 2 is done: the API can grow by servers, pods or Lambdas. Now every one of them asks the database тАФ and the database cannot simply be copied. First trick: a notice board in front of it.

git checkout lesson-09-caching-the-database
тЖР Previouskubernetes scalingNext тЖТcaching the database

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