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

ЁЯУМ рдзрдбрд╛ 08 тАФ Caching: рд╕реВрдЪрдирд╛ рдлрд▓рдХ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧреЗ: lesson-07-throttling ┬╖ рдкреБрдвреЗ: lesson-09-cors


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

рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ stage cache: TTL рд╕рдВрдкреЗрдкрд░реНрдпрдВрдд office рдкреБрдиреНрд╣рд╛ рдкреБрдиреНрд╣рд╛ рдпреЗрдгрд╛рд▒реНрдпрд╛ GET рдЪреЗ рдЙрддреНрддрд░ рдЖрдкрд▓реНрдпрд╛ рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рд╡рд░реВрди рджреЗрддреЗ, рдЖрдгрд┐ kitchen рд▓рд╛ рдлрдХреНрдд рдПрдХрджрд╛рдЪ рд╡рд┐рдЪрд╛рд░рд▓реЗ рдЬрд╛рддреЗ. рддреБрдореНрд╣рд╛рд▓рд╛ cache key рдХрд╛рдо рдХрд░рддрд╛рдирд╛ рджрд┐рд╕реЗрд▓ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ user рдЪреА рд╡реЗрдЧрд│реА рдЙрддреНрддрд░реЗ рдПрдХрдЪ key рдХрдзреАрдЪ рдХрд╛ рд╡рд╛рдЯреВрди рдШреЗрдК рдирдпреЗрдд рддреЗ рдХрд│реЗрд▓. apigw/demo.py рдордзрд▓реЗ cache() рдкрд╛рдЪ рдорд┐рдирд┐рдЯрд╛рдВрдд рдПрдХрд╛рдЪ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдмрджреНрджрд▓ рдЪрд╛рд░ рд╡реЗрд│рд╛ рд╡рд┐рдЪрд╛рд░рддреЗ.

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

рдЕрдиреЗрдХ рдкрд╛рд▓рдХ clerk рд▓рд╛ рддреЛрдЪ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рддрд╛рдд: "рдРрд╢реНрд╡рд░реНрдпрд╛ рдХреЛрдгрддреНрдпрд╛ рд╡рд░реНрдЧрд╛рдд рдЖрд╣реЗ?" рдкрд╣рд┐рд▓реНрдпрд╛ рд╡реЗрд│реА clerk kitchen рдкрд░реНрдпрдВрдд рдЪрд╛рд▓рдд рдЬрд╛рдКрди рд╡рд┐рдЪрд╛рд░рддреЛ. рдордЧ clerk рдЙрддреНрддрд░ рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рд╡рд░ рд▓рд╛рд╡рддреЛ ЁЯУМ, рд╕реЛрдмрдд рдПрдХ рдЪрд┐рдареНрдареА: "5 рдорд┐рдирд┐рдЯрд╛рдВрд╕рд╛рдареА рд╡реИрдз".

рдкреБрдврдЪрд╛ рдкрд╛рд▓рдХ рд╡рд┐рдЪрд╛рд░рддреЛ тАФ clerk рдлрд▓рдХ рд╡рд╛рдЪрддреЛ. Kitchen рдкрд░реНрдпрдВрдд рдЪрд╛рд▓рдд рдЬрд╛рдпрдЪреА рдЧрд░рдЬ рдирд╛рд╣реА. 5 рдорд┐рдирд┐рдЯрд╛рдВрдирдВрддрд░ рдЪрд┐рдареНрдареА рдЬреБрдиреА рд╣реЛрддреЗ, рдореНрд╣рдгреВрди clerk рддреА рдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛ kitchen рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЛ.

рдлрд▓рдХ рдиреЗрдордХреНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рдиреБрд╕рд╛рд░ рдорд╛рдВрдбрд▓реЗрд▓рд╛ рдЕрд╕рддреЛ. "рдРрд╢реНрд╡рд░реНрдпрд╛рдЪрд╛ рд╡рд░реНрдЧ" рдЖрдгрд┐ "рдРрд╢реНрд╡рд░реНрдпрд╛рдЪрд╛ рд╡рд░реНрдЧ, рдорд░рд╛рдареАрдд" рдпрд╛ рджреЛрди рд╡реЗрдЧрд│реНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ рдЖрд╣реЗрдд. рдЖрдгрд┐ clerk рдиреЗ "рдорд╛рдЭреЗ рдЧреБрдг" рдХрдзреАрдЪ рдлрд▓рдХрд╛рд╡рд░ рд▓рд╛рд╡реВ рдирдпреЗрдд тАФ рдирд╛рд╣реАрддрд░ рдкреБрдврдЪрд╛ рдкрд╛рд▓рдХ рджреБрд╕рд▒реНрдпрд╛рдЪреЗ рдЧреБрдг рд╡рд╛рдЪреЗрд▓.

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

flowchart LR
    q0["+0 s GET /students/7"] -->|"Miss"| k["ЁЯН│ kitchen<br/>calls = 1"]
    k --> board["ЁЯУМ notice board<br/>key: prod GET /students/7<br/>good until +300 s"]
    q1["+10 s"] -->|"Hit"| board
    q2["+210 s"] -->|"Hit"| board
    q3["+511 s тАФ TTL passed"] -->|"Miss"| k2["ЁЯН│ kitchen<br/>calls = 2"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдЕрдиреЗрдХ APIs рддреЗрдЪ рддреЗрдЪ рдкреБрдиреНрд╣рд╛ рдкреБрдиреНрд╣рд╛ рд╡рд╛рдЪрддрд╛рдд тАФ рд╡реЗрд│рд╛рдкрддреНрд░рдХ, рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреЗ рдХрд╛рд░реНрдб, рдПрдЦрд╛рджреА рдХрд┐рдВрдордд-рдпрд╛рджреА. рдкреНрд░рддреНрдпреЗрдХ cache hit рдореНрд╣рдгрдЬреЗ рдПрдХ Lambda run рдХрдореА, рдПрдХ database read рдХрдореА, рдЖрдгрд┐ рдЬрд▓рдж рдЙрддреНрддрд░. рдкрдг TTL рд╕рдВрдкреЗрдкрд░реНрдпрдВрдд cache рдЬреБрдиреА рдЙрддреНрддрд░реЗрд╣реА рджреЗрддреЛ, рдЖрдгрд┐ рдЪреБрдХреАрдЪреА cache key рджреБрд╕рд▒реНрдпрд╛рдЪреЗ рдЙрддреНрддрд░ рджреЗрддреЗ. рджреЛрдиреНрд╣реА рдЦрд░реЛрдЦрд░ рдШрдбрд▓реЗрд▓реНрдпрд╛ incidents рдЖрд╣реЗрдд.

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

Gateway._handle() (apigw/gateway.py) рдордзреНрдпреЗ, TTL рдЕрд╕рд▓реЗрд▓реНрдпрд╛ stage рд╡рд░рдЪрд╛ (prod рд╡рд░ CACHE_TTL = 300, dev рд╡рд░ 0) cache=True рдЕрд╕рд▓реЗрд▓рд╛ route (stage, method, path, sorted query) рдкрд╛рд╕реВрди key рдмрдирд╡рддреЛ. TTL рдкреЗрдХреНрд╖рд╛ рдХрдореА рд╡рдпрд╛рдЪреЗ рд╕рд╛рдард╡рд▓реЗрд▓реЗ рдЙрддреНрддрд░ X-Cache: Hit рд╕рд╣ рдкрд░рдд рджрд┐рд▓реЗ рдЬрд╛рддреЗ рдЖрдгрд┐ integration рд▓рд╛ call рд╣реЛрдд рдирд╛рд╣реА; рдирд╛рд╣реАрддрд░ рддреЛ Miss рдЕрд╕рддреЛ, рдЖрдгрд┐ 200 рдЙрддреНрддрд░ рд╕рд╛рдард╡рд▓реЗ рдЬрд╛рддреЗ. рдлрдХреНрдд 200s рд╕рд╛рдард╡рд▓реЗ рдЬрд╛рддрд╛рдд. apigw/demo.py рдордзрд▓реЗ рдмрдирд╛рд╡рдЯ Clock demo рд▓рд╛ рдХреНрд╖рдгрд╛рдд 301 рд╕реЗрдХрдВрдж рдкреБрдвреЗ рдЙрдбреА рдорд╛рд░реВ рджреЗрддреЗ. рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд╛: рд╣реЗ model рдкреНрд░рддреНрдпреЗрдХ query string key рдордзреНрдпреЗ рдШрд╛рд▓рддреЗ; рдЦрд░реЗ API Gateway рдлрдХреНрдд рддреБрдореНрд╣реА рдирд┐рд╡рдбрд▓реЗрд▓реНрдпрд╛рдЪ рд╡рд╛рдкрд░рддреЗ.

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

python3 apigw/demo.py cache
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office, Clock, calls
c = Clock(); gw, _ = office(c, cache_ttl=60); calls["n"] = 0
for path, q in (("/students/7", {}), ("/students/7", {}), ("/students/7", {"lang": "mr"}), ("/students/9", {}), ("/students/404", {}), ("/students/404", {})):
    st, hd, _ = gw.handle("GET", path, query=q)
    print(f"{path:<14} {str(q):<16} {st} {hd.get('X-Cache')}  kitchen calls: {calls['n']}")
c.t += 61; print("after 61 s:", gw.handle("GET", "/students/7")[1], "kitchen calls:", calls["n"])
print({k: gw.metrics[k] for k in ("CacheHitCount", "CacheMissCount")})
EOF

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

cache рд╣реЗ рдЫрд╛рдкрддреЗ:

   +0   s  200 Miss   kitchen calls so far: 1
   +10  s  200 Hit   kitchen calls so far: 1
   +200 s  200 Hit   kitchen calls so far: 1
   +301 s  200 Miss   kitchen calls so far: 2

(рд╡реЗрд│рд╛ рдмреЗрд░реАрдЬ рд╣реЛрдд рдЬрд╛рддрд╛рдд: рдЪреМрдереА request 511 s рд▓рд╛ рдЖрд╣реЗ, рдореНрд╣рдгрдЬреЗ 0 s рд▓рд╛ рд╕рд╛рдард╡рд▓реЗрд▓реНрдпрд╛ рдЙрддреНрддрд░рд╛рдЪреНрдпрд╛ 300 s TTL рдЪреНрдпрд╛ рдкрд▓реАрдХрдбреЗ).

рддреБрдордЪрд╛ snippet (TTL 60 s) /students/7 рд╕рд╛рдареА рдЖрдзреА Miss рдордЧ Hit рдЫрд╛рдкрддреЛ (1 kitchen call), {'lang': 'mr'} рд╕рд╛рдареА Miss (рдирд╡реА key тЖТ 2 calls), /students/9 рд╕рд╛рдареА Miss (3 calls), рдЖрдгрд┐ /students/404 рд╕рд╛рдареА рджреЛрдирджрд╛ 404 Miss (4 рдЖрдгрд┐ 5 calls тАФ errors рд╕рд╛рдард╡рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реАрдд). рдордЧ after 61 s: {'X-Cache': 'Miss'} kitchen calls: 6 рдЖрдгрд┐ {'CacheHitCount': 1, 'CacheMissCount': 6}.

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

рддреБрдореНрд╣реА рдкреНрд░рддреНрдпреЗрдХ hit рдЖрдгрд┐ miss рдЖрдзреАрдЪ рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛: рддреАрдЪ key рдЖрдгрд┐ TTL рдкреЗрдХреНрд╖рд╛ рдХрдореА рд╡рдп тЖТ рдлрд▓рдХ рдЙрддреНрддрд░ рджреЗрддреЛ; рдирд╡реА query, рдирд╡рд╛ path, рдПрдЦрд╛рджреА error, рдХрд┐рдВрд╡рд╛ рдЬреБрдиреА рдЪрд┐рдареНрдареА тЖТ kitchen рд▓рд╛ рд╡рд┐рдЪрд╛рд░рд▓реЗ рдЬрд╛рддреЗ.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ prod рд╕рд╛рдареА 0.5 GB cache рдЪрд╛рд▓реВ рдХрд░рд╛, рдПрдХ method 300 s рд╕рд╛рдареА cache рдХрд░рд╛, рдЖрдгрд┐ рддреЛ flush рдХрд░рд╛:

aws apigateway update-stage --rest-api-id abc123 --stage-name prod --patch-operations \
    op=replace,path=/cacheClusterEnabled,value=true \
    op=replace,path=/cacheClusterSize,value=0.5 \
    op=replace,path=/~1students~1{id}/GET/caching/enabled,value=true \
    op=replace,path=/~1students~1{id}/GET/caching/ttlInSeconds,value=300
aws apigateway flush-stage-cache --rest-api-id abc123 --stage-name prod

Terraform тАФ рддреНрдпрд╛рдЪ route рд╕рд╛рдареА method settings:

resource "aws_api_gateway_method_settings" "student_cache" {
  rest_api_id = aws_api_gateway_rest_api.school.id
  stage_name  = aws_api_gateway_stage.prod.stage_name
  method_path = "students/{id}/GET"
  settings {
    caching_enabled                         = true
    cache_ttl_in_seconds                    = 300
    require_authorization_for_cache_control = true
  }
}

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рд╕рдЧрд│реНрдпрд╛рдВрд╕рд╛рдареА рд╕рд╛рд░рдЦреЗ рдЕрд╕реЗрд▓ рддреЗрд╡рдвреЗрдЪ cache рдХрд░рд╛, key рд╡рд┐рдЪрд╛рд░рдкреВрд░реНрд╡рдХ рдирд┐рд╡рдбрд╛, hit ratio рдкрд╛рд╣рдд рд░рд╛рд╣рд╛ (CacheHitCount ├╖ рд╕рдЧрд│реНрдпрд╛ cached requests), рдЖрдгрд┐ ratio рдХрдореАрдЪ рд░рд╛рд╣рд┐рд▓рд╛ рддрд░ cache рдХрд╛рдвреВрди рдЯрд╛рдХрд╛ тАФ рддреНрдпрд╛рдЪреЗ рдкреИрд╕реЗ рддреБрдореНрд╣реА рджрд░ рддрд╛рд╕рд╛рд▓рд╛ рднрд░рддрд╛.

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

Office рдЬрд▓рдж рдЙрддреНрддрд░ рджреЗрддреЗ. рдкрдг рджреБрд╕рд▒реНрдпрд╛ site рд╡рд░рдЪреЗ рдПрдХ web page рддреНрдпрд╛рд▓рд╛ call рдХрд░реВ рдЗрдЪреНрдЫрд┐рддреЗ, рдЖрдгрд┐ browser рдЖрдзреА рдкрд░рд╡рд╛рдирдЧреА рд╡рд┐рдЪрд╛рд░рддреЛ тАФ CORS.

git checkout lesson-09-cors

ЁЯУМ Lesson 08 тАФ Caching: the notice board

ЁЯУН You are here: Lesson 08 of 12 ┬╖ Previous: lesson-07-throttling ┬╖ Next: lesson-09-cors


ЁЯУж What's in this branch

Lessons 01тАУ07, plus the stage cache: the office answers a repeated GET from its notice board until the TTL runs out, and the kitchen is asked only once. You will see the cache key at work and why per-user answers must never share one. cache() in apigw/demo.py asks for the same student four times over five minutes.

ЁЯзТ Explain like I'm 5

Many parents ask the clerk the same question: "Which class is Aishwarya in?" The first time, the clerk walks to the kitchen and asks. Then the clerk pins the answer on the notice board ЁЯУМ with a note: "good for 5 minutes".

The next parent asks тАФ the clerk reads the board. No walk to the kitchen. After 5 minutes the note is too old, so the clerk takes it down and asks the kitchen again.

The board is organised by the exact question. "Aishwarya's class" and "Aishwarya's class, in Marathi" are two different notes. And the clerk must never pin "my marks" on the board тАФ the next parent would read someone else's marks.

ЁЯЧ║я╕П Diagram

flowchart LR
    q0["+0 s GET /students/7"] -->|"Miss"| k["ЁЯН│ kitchen<br/>calls = 1"]
    k --> board["ЁЯУМ notice board<br/>key: prod GET /students/7<br/>good until +300 s"]
    q1["+10 s"] -->|"Hit"| board
    q2["+210 s"] -->|"Hit"| board
    q3["+511 s тАФ TTL passed"] -->|"Miss"| k2["ЁЯН│ kitchen<br/>calls = 2"]

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

тЭУ What

ЁЯдФ Why

Because many APIs read the same thing again and again тАФ a timetable, a student card, a price list. Each cache hit is one less Lambda run and one less database read, and a faster answer. But a cache also returns old answers until the TTL passes, and a wrong cache key returns someone else's answer. Both are real incidents.

ЁЯФз How (in this repo)

In Gateway._handle() (apigw/gateway.py) a route with cache=True on a stage with a TTL (CACHE_TTL = 300 on prod, 0 on dev) builds a key from (stage, method, path, sorted query). A stored answer younger than the TTL is returned with X-Cache: Hit and the integration is not called; otherwise it is a Miss, and a 200 answer is stored. Only 200s are stored. The fake Clock in apigw/demo.py lets the demo jump 301 seconds in no time. Note: this model puts every query string into the key; real API Gateway uses only the ones you choose.

ЁЯзк Try it

python3 apigw/demo.py cache
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office, Clock, calls
c = Clock(); gw, _ = office(c, cache_ttl=60); calls["n"] = 0
for path, q in (("/students/7", {}), ("/students/7", {}), ("/students/7", {"lang": "mr"}), ("/students/9", {}), ("/students/404", {}), ("/students/404", {})):
    st, hd, _ = gw.handle("GET", path, query=q)
    print(f"{path:<14} {str(q):<16} {st} {hd.get('X-Cache')}  kitchen calls: {calls['n']}")
c.t += 61; print("after 61 s:", gw.handle("GET", "/students/7")[1], "kitchen calls:", calls["n"])
print({k: gw.metrics[k] for k in ("CacheHitCount", "CacheMissCount")})
EOF

тЬЕ Verify тАФ what you should see

cache prints:

   +0   s  200 Miss   kitchen calls so far: 1
   +10  s  200 Hit   kitchen calls so far: 1
   +200 s  200 Hit   kitchen calls so far: 1
   +301 s  200 Miss   kitchen calls so far: 2

(the times add up: the fourth request is at 511 s, past the 300 s TTL of the answer stored at 0 s).

Your snippet (TTL 60 s) prints Miss then Hit for /students/7 (1 kitchen call), Miss for {'lang': 'mr'} (a new key тЖТ 2 calls), Miss for /students/9 (3 calls), and 404 Miss twice for /students/404 (4 and 5 calls тАФ errors are not stored). Then after 61 s: {'X-Cache': 'Miss'} kitchen calls: 6 and {'CacheHitCount': 1, 'CacheMissCount': 6}.

ЁЯПБ What you just proved

You can predict every hit and miss: same key and younger than the TTL тЖТ the board answers; a new query, a new path, an error, or an old note тЖТ the kitchen is asked.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ turn on a 0.5 GB cache for prod, cache one method for 300 s, and flush it:

aws apigateway update-stage --rest-api-id abc123 --stage-name prod --patch-operations \
    op=replace,path=/cacheClusterEnabled,value=true \
    op=replace,path=/cacheClusterSize,value=0.5 \
    op=replace,path=/~1students~1{id}/GET/caching/enabled,value=true \
    op=replace,path=/~1students~1{id}/GET/caching/ttlInSeconds,value=300
aws apigateway flush-stage-cache --rest-api-id abc123 --stage-name prod

Terraform тАФ method settings for the same route:

resource "aws_api_gateway_method_settings" "student_cache" {
  rest_api_id = aws_api_gateway_rest_api.school.id
  stage_name  = aws_api_gateway_stage.prod.stage_name
  method_path = "students/{id}/GET"
  settings {
    caching_enabled                         = true
    cache_ttl_in_seconds                    = 300
    require_authorization_for_cache_control = true
  }
}

ЁЯПн Why this matters in production: cache only what is the same for everyone, choose the key on purpose, watch the hit ratio (CacheHitCount ├╖ all cached requests), and remove the cache if the ratio stays low тАФ you pay for it every hour.

тПня╕П Next

The office answers fast. But a web page on another site wants to call it, and the browser asks permission first тАФ CORS.

git checkout lesson-09-cors
тЖР PreviousthrottlingNext тЖТcors

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