🏫 The School›📈 Scaling›⚡ धडा 04 — Edge वर dynamic pages: live pages सुद्धा, काही सेकंदांसाठी
🖼️ See the drawing + lab 🏠 Course home 🌿 Branch on GitHub ✏️ View source
🖼️ आकृती आणि labThe drawing + lab पूर्ण पानावर उघडा ↗Open full page ↗

⚡ धडा 04 — Edge वर dynamic pages: live pages सुद्धा, काही सेकंदांसाठी

📍 तुम्ही इथे आहात: 13 पैकी धडा 04 · मागे: lesson-03-cloudfront-s3 · पुढे: lesson-05-stateless-load-balancer


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

धडे 01–03, आणि त्याशिवाय बदलणारी pages cache करणे: प्रत्येक response ने द्यायला हवी अशी तीन Cache-Control उत्तरे (एक वर्ष, काही सेकंद, कधीच नाही), stale-while-revalidate, अनेक edges नी एकदम origin ला विचारणे थांबवणारे origin shield, आणि edge functions (CloudFront Functions आणि Lambda@Edge). scale/demo.py मधले edge() प्रत्येक गोष्ट दाखवते.

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

वर्ग 3A ची निकालाची यादी फक्त शिक्षिकेने grade save केल्यावरच बदलते — तासाला काही वेळा. पण निकालाच्या दिवशी दर सेकंदाला हजारो पालक ती मागतात.

दीपिका फाटकावरच्या टेबलांना सांगते: "तुम्ही निकालाच्या यादीची प्रत काढू शकता, पण 10 सेकंदांनी तुमची प्रत फेकून द्या." आता प्रत्येक फाटक सेकंदाला 2,000 वेळा नव्हे, तर दर 10 सेकंदांनी एकदा office कडे धावते. पालकाला काही सेकंद जुनी यादी दिसू शकते. निकालाच्या दिवशी त्याची कुणालाच हरकत नसते.

फाटके अनेक आहेत. प्रत्येक फाटक स्वतःच office कडे धावले, तर office मध्ये तरीही धावणाऱ्यांची गर्दी होते. म्हणून दीपिका office शेजारी एक मोठे प्रतींचे टेबल ठेवते — origin shield. फाटके त्या टेबलाला विचारतात; फक्त ते टेबल office ला विचारते.

काही कागद खाजगी असतात: कतरिनाचे स्वतःचे प्रगतिपुस्तक. फाटकांनी त्यांची प्रत कधीच काढायची नाही. प्रत्येक कागदावर तो कोणत्या प्रकारचा आहे हे सांगणारे लेबल असते: "एक वर्षासाठी प्रत काढा", "10 सेकंदांसाठी प्रत काढा", किंवा "कधीच प्रत काढू नका".

🗺️ आकृती

flowchart LR
    e1["📄 edge 1"] --> sh["🛡️ origin shield<br/>one regional cache"]
    e2["📄 edge 2"] --> sh
    e3["📄 edges 3–5"] --> sh
    sh -->|"6 requests a minute"| api["🍳 origin: results API"]
    subgraph labels["🏷️ Cache-Control on each answer"]
      a["/app.3f9c.js<br/>public, max-age=31536000, immutable"]
      b["/results/3A<br/>public, s-maxage=10, stale-while-revalidate=30"]
      c["/me<br/>private, no-store"]
    end

🗺️ काढलेली आकृती + एक lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l04

❓ काय

🤔 का

कारण निकालाच्या दिवशी सर्वात गर्दीचे page dynamic असते — निकाल. ते एक वर्षासाठी cache करता येत नाही, पण 10 सेकंदांसाठी करता येते, आणि त्याचा जवळपास सगळा load काढून टाकायला 10 सेकंद पुरेसे आहेत. मग origin shield "प्रत्येक edge एकदम विचारतो" ही गर्दी काढून टाकतो. आणि private, no-store लेबलच एका पालकाला दुसऱ्या मुलाचे प्रगतिपुस्तक पाहण्यापासून रोखते.

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

edge() तीन Cache-Control उत्तरे print करते, मग 5 EdgeCache(10) edges बनवते आणि 60 सेकंद प्रत्येकाला सेकंदाला एक request पाठवते. Shield नसताना, प्रत्येक edge miss origin कडे जातो. Shield असताना, edge miss आधी shield ला (आणखी एक EdgeCache(10)) विचारतो, आणि फक्त shield miss च origin पर्यंत पोहोचतो. Edge functions चे वर्णन केले आहे, ती चालवली जात नाहीत.

🧪 करून पाहा

python3 scale/demo.py edge
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import EdgeCache
def run(n_edges, ttl, shield_on):
    edges, shield, origin = [EdgeCache(ttl) for _ in range(n_edges)], EdgeCache(ttl), 0
    for t in range(60):
        for e in edges:
            if e.get("/results/3A", t) == "MISS":
                if not shield_on or shield.get("/results/3A", t) == "MISS": origin += 1
    return origin
for n in (5, 20):
    for ttl in (1, 10, 30):
        print(f"{n:>2} edges, s-maxage {ttl:>2} s → origin {run(n, ttl, False):>4} without shield, {run(n, ttl, True):>3} with shield")
EOF

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

edge तीन headers print करते (/app.3f9c.js Cache-Control: public, max-age=31536000, immutable, /results/3A Cache-Control: public, s-maxage=10, stale-while-revalidate=30, /me Cache-Control: private, no-store), मग ── 5 edge locations, s-maxage 10 s, 60 s of traffic: origin gets 30 requests; with an origin shield: 6.

तुमचा snippet हे print करतो:

 5 edges, s-maxage  1 s → origin  300 without shield,  60 with shield
 5 edges, s-maxage 10 s → origin   30 without shield,   6 with shield
 5 edges, s-maxage 30 s → origin   10 without shield,   2 with shield
20 edges, s-maxage  1 s → origin 1200 without shield,  60 with shield
20 edges, s-maxage 10 s → origin  120 without shield,   6 with shield
20 edges, s-maxage 30 s → origin   40 without shield,   2 with shield

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

Shield नसताना origin वरचा load edges च्या संख्येबरोबर वाढतो (5 → 20 edges: 30 → 120 requests). Shield असताना तो फक्त TTL वर अवलंबून असतो: 10 s ला मिनिटाला 6, edges कितीही असोत. जास्त लांब s-maxage दोन्ही कमी करतो.

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

🏭 प्रत्यक्ष वापरात

On a real account — results API साठी एक cache policy जी origin चा s-maxage (0 ते 60 सेकंदांमध्ये) मानते आणि key फक्त path आणि class query string वर ठेवते:

aws cloudfront create-cache-policy --cache-policy-config '{
  "Name": "results-micro-cache",
  "MinTTL": 0, "DefaultTTL": 5, "MaxTTL": 60,
  "ParametersInCacheKeyAndForwardedToOrigin": {
    "EnableAcceptEncodingGzip": true, "EnableAcceptEncodingBrotli": true,
    "HeadersConfig": { "HeaderBehavior": "none" },
    "CookiesConfig": { "CookieBehavior": "none" },
    "QueryStringsConfig": { "QueryStringBehavior": "whitelist",
                            "QueryStrings": { "Quantity": 1, "Items": ["class"] } }
  }
}'

Terraform — API origin साठी origin shield चालू करा:

origin {
  domain_name = "api.school.example"
  origin_id   = "results-api"
  custom_origin_config {
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "https-only"
    origin_ssl_protocols   = ["TLSv1.2"]
  }
  origin_shield {
    enabled              = true
    origin_shield_region = "ap-south-1"      # the Region closest to the origin
  }
}

जुन्या links नव्या path कडे पाठवणारे एक CloudFront Function (viewer request):

function handler(event) {
  var req = event.request;
  if (req.uri.indexOf('/old-results/') === 0) {
    return { statusCode: 301, statusDescription: 'Moved Permanently',
             headers: { location: { value: req.uri.replace('/old-results/', '/results/') } } };
  }
  return req;
}

🏭 प्रत्यक्ष वापरात हे का महत्त्वाचे: launch आधी प्रत्येक route चे लेबल ठरवा — "एक वर्ष", "काही सेकंद" किंवा "कधीच नाही" — आणि "कधीच नाही" routes दोन वेगवेगळ्या users सह test करा. वैयक्तिक page वरची caching ची चूक म्हणजे हळू page नव्हे, तर data leak.

⏭️ पुढे

UI फाटकांवरून दिली जाते. तुमच्यापर्यंत पोहोचायलाच हव्यात अशा requests आता API वर येतात. एक खिडकी पुरेशी नाही — पण जास्त खिडक्या तेव्हाच चालतात जेव्हा कोणतीही खिडकी कोणत्याही पालकाला सेवा देऊ शकते.

git checkout lesson-05-stateless-load-balancer

⚡ Lesson 04 — Dynamic pages at the edge: even live pages, for a few seconds

📍 You are here: Lesson 04 of 13 · Previous: lesson-03-cloudfront-s3 · Next: lesson-05-stateless-load-balancer


📦 What's in this branch

Lessons 01–03, plus caching pages that change: the three Cache-Control answers every response should give (a year, a few seconds, never), stale-while-revalidate, the origin shield that stops many edges asking the origin at once, and edge functions (CloudFront Functions and Lambda@Edge). edge() in scale/demo.py shows each one.

🧒 Explain like I'm 5

The results sheet for class 3A changes only when a teacher saves a grade — a few times an hour. But on results day, thousands of parents ask for it every second.

Dipika says to the gate desks: "You may copy the results sheet, but throw your copy away after 10 seconds." Now each gate runs to the office once every 10 seconds, not 2,000 times a second. A parent may see a sheet that is a few seconds old. On results day, nobody minds.

There are many gates. If every gate runs to the office on its own, the office still gets a crowd of runners. So Dipika puts one big copy desk next to the office — the origin shield. The gates ask that desk; only that desk asks the office.

Some papers are private: Katrina's own report card. The gates must never copy those. Each paper carries a label that says which kind it is: "copy for a year", "copy for 10 seconds", or "never copy".

🗺️ Diagram

flowchart LR
    e1["📄 edge 1"] --> sh["🛡️ origin shield<br/>one regional cache"]
    e2["📄 edge 2"] --> sh
    e3["📄 edges 3–5"] --> sh
    sh -->|"6 requests a minute"| api["🍳 origin: results API"]
    subgraph labels["🏷️ Cache-Control on each answer"]
      a["/app.3f9c.js<br/>public, max-age=31536000, immutable"]
      b["/results/3A<br/>public, s-maxage=10, stale-while-revalidate=30"]
      c["/me<br/>private, no-store"]
    end

🗺️ Drawn version + a lab: https://school-edh.pages.dev/scaling/lesson-diagrams.html#l04

❓ What

🤔 Why

Because on results day the busiest page is a dynamic one — the results. It cannot be cached for a year, but it can be cached for 10 seconds, and 10 seconds is enough to remove almost all its load. The origin shield then removes the "every edge asks at once" crowd. And the private, no-store label is what keeps one parent from seeing another child's report card.

🔧 How (in this repo)

edge() prints the three Cache-Control answers, then makes 5 EdgeCache(10) edges and sends one request per second to each for 60 seconds. Without a shield, every edge miss goes to the origin. With a shield, an edge miss asks the shield (another EdgeCache(10)) first, and only a shield miss reaches the origin. The edge functions are described, not run.

🧪 Try it

python3 scale/demo.py edge
python3 - <<'EOF'
import sys; sys.path.insert(0, "scale"); from sim import EdgeCache
def run(n_edges, ttl, shield_on):
    edges, shield, origin = [EdgeCache(ttl) for _ in range(n_edges)], EdgeCache(ttl), 0
    for t in range(60):
        for e in edges:
            if e.get("/results/3A", t) == "MISS":
                if not shield_on or shield.get("/results/3A", t) == "MISS": origin += 1
    return origin
for n in (5, 20):
    for ttl in (1, 10, 30):
        print(f"{n:>2} edges, s-maxage {ttl:>2} s → origin {run(n, ttl, False):>4} without shield, {run(n, ttl, True):>3} with shield")
EOF

✅ Verify — what you should see

edge prints the three headers (/app.3f9c.js Cache-Control: public, max-age=31536000, immutable, /results/3A Cache-Control: public, s-maxage=10, stale-while-revalidate=30, /me Cache-Control: private, no-store), then ── 5 edge locations, s-maxage 10 s, 60 s of traffic: origin gets 30 requests; with an origin shield: 6.

Your snippet prints:

 5 edges, s-maxage  1 s → origin  300 without shield,  60 with shield
 5 edges, s-maxage 10 s → origin   30 without shield,   6 with shield
 5 edges, s-maxage 30 s → origin   10 without shield,   2 with shield
20 edges, s-maxage  1 s → origin 1200 without shield,  60 with shield
20 edges, s-maxage 10 s → origin  120 without shield,   6 with shield
20 edges, s-maxage 30 s → origin   40 without shield,   2 with shield

🏁 What you just proved

Without a shield, the origin's load grows with the number of edges (5 → 20 edges: 30 → 120 requests). With a shield, it depends only on the TTL: 6 a minute at 10 s, however many edges there are. A longer s-maxage cuts both.

⚠️ Common mistakes

🏭 In production

On a real account — a cache policy for the results API that honours the origin's s-maxage (between 0 and 60 seconds) and keys only on the path and the class query string:

aws cloudfront create-cache-policy --cache-policy-config '{
  "Name": "results-micro-cache",
  "MinTTL": 0, "DefaultTTL": 5, "MaxTTL": 60,
  "ParametersInCacheKeyAndForwardedToOrigin": {
    "EnableAcceptEncodingGzip": true, "EnableAcceptEncodingBrotli": true,
    "HeadersConfig": { "HeaderBehavior": "none" },
    "CookiesConfig": { "CookieBehavior": "none" },
    "QueryStringsConfig": { "QueryStringBehavior": "whitelist",
                            "QueryStrings": { "Quantity": 1, "Items": ["class"] } }
  }
}'

Terraform — turn on the origin shield for the API origin:

origin {
  domain_name = "api.school.example"
  origin_id   = "results-api"
  custom_origin_config {
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "https-only"
    origin_ssl_protocols   = ["TLSv1.2"]
  }
  origin_shield {
    enabled              = true
    origin_shield_region = "ap-south-1"      # the Region closest to the origin
  }
}

A CloudFront Function (viewer request) that sends old links to the new path:

function handler(event) {
  var req = event.request;
  if (req.uri.indexOf('/old-results/') === 0) {
    return { statusCode: 301, statusDescription: 'Moved Permanently',
             headers: { location: { value: req.uri.replace('/old-results/', '/results/') } } };
  }
  return req;
}

🏭 Why this matters in production: decide the label for every route before launch — "a year", "a few seconds" or "never" — and test the "never" routes with two different users. A caching mistake on a personal page is a data leak, not a slow page.

⏭️ Next

The UI is served from the gates. The requests that must reach you now land on the API. One counter is not enough — but more counters only work if any counter can serve any parent.

git checkout lesson-05-stateless-load-balancer
← Previouscloudfront s3Next →stateless load balancer

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