app.yaml sets no default_expiration and no per-handler expiration:, so GAE applies its default of 10 minutes to every static file:
$ curl -sI https://www.glowscript.org/package/glow.3.2.min.js | grep -i cache-control
cache-control: public, max-age=600
glow.3.2.min.js is 4.3 MB. A student in a two-hour lab re-downloads it roughly a dozen times.
What the traffic looks like
A 2000-request sample of the last 24h of request_log. 99% of bytes are static assets:
| URL |
bytes |
requests |
/package/glow.3.2.min.js |
158.1 MB |
43 |
/package/RScompiler.3.2.min.js |
45.1 MB |
41 |
/lib/ace/ace.js |
10.1 MB |
20 |
/lib/jquery/2.1/jquery-ui.custom.min.js |
7.9 MB |
42 |
/lib/plotly.js |
6.0 MB |
1 |
/ (the app itself) |
2.2 MB |
323 |
(A sample, not a daily total — but the ratio is the point.)
⚠️ The obvious fix is unsafe for the file that matters most
default_expiration: 365d looks like a one-line win. It is not, and the reason is worth recording.
The version in the filename is the release line, not the content. glow.3.2.min.js has 65 commits and last changed 2025-03-29 — it keeps being modified under a fixed URL. Pinning it for a year would leave a stale copy in students' browsers after a fix ships, with no way to bust it.
The rule is: a long TTL is safe only if the URL changes when the bytes change.
Older lines are effectively frozen:
| file |
size |
last changed |
commits |
package/glow.3.2.min.js |
4308 KB |
2025-03-29 |
65 |
package/glow.3.1.min.js |
4296 KB |
2021-10-22 |
10 |
package/glow.3.0.min.js |
4295 KB |
2021-03-25 |
21 |
package/glow.2.9.min.js |
4293 KB |
2020-05-01 |
19 |
package/glow.2.8.min.js |
5222 KB |
2019-06-20 |
1 |
package/glow.2.7.min.js |
4398 KB |
2019-06-19 |
73 |
package/RScompiler.3.2.min.js |
1357 KB |
2024-01-09 |
28 |
package/RSrun.3.2.min.js |
107 KB |
2022-08-04 |
4 |
lib/plotly.js |
5821 KB |
2018-08-09 |
3 |
lib/ace/ace.js |
529 KB |
2022-03-18 |
8 |
Proposal — three tiers
1. Frozen release lines — expiration: 365d per handler, today. glow.3.1 and older have not changed in 4+ years; a new release gets a new filename. Zero risk.
2. Vendored third-party — lib/ace, lib/jquery, lib/plotly.js. Versioned by directory or frozen since 2018, and not ours to modify. Same treatment.
3. The current dev line (glow.3.2.min.js, RScompiler.3.2.min.js) — where the 158 MB actually is, and the one that cannot take a long TTL as-is. Needs a content-derived URL so it changes when the bytes do. More work, and worth doing separately.
Tiers 1 and 2 are a few lines of YAML and take a real bite out of egress on their own.
Not a CDN problem
Worth stating so nobody reaches for the wrong tool: GAE already serves these from Google's edge. Unlike the Cloud Run deploys, no CDN needs adding — the only thing missing is the cache policy.
Found while investigating unrelated /google/auth 500s (#210 / #211).
app.yamlsets nodefault_expirationand no per-handlerexpiration:, so GAE applies its default of 10 minutes to every static file:glow.3.2.min.jsis 4.3 MB. A student in a two-hour lab re-downloads it roughly a dozen times.What the traffic looks like
A 2000-request sample of the last 24h of
request_log. 99% of bytes are static assets:/package/glow.3.2.min.js/package/RScompiler.3.2.min.js/lib/ace/ace.js/lib/jquery/2.1/jquery-ui.custom.min.js/lib/plotly.js/(the app itself)(A sample, not a daily total — but the ratio is the point.)
default_expiration: 365dlooks like a one-line win. It is not, and the reason is worth recording.The version in the filename is the release line, not the content.
glow.3.2.min.jshas 65 commits and last changed 2025-03-29 — it keeps being modified under a fixed URL. Pinning it for a year would leave a stale copy in students' browsers after a fix ships, with no way to bust it.The rule is: a long TTL is safe only if the URL changes when the bytes change.
Older lines are effectively frozen:
package/glow.3.2.min.jspackage/glow.3.1.min.jspackage/glow.3.0.min.jspackage/glow.2.9.min.jspackage/glow.2.8.min.jspackage/glow.2.7.min.jspackage/RScompiler.3.2.min.jspackage/RSrun.3.2.min.jslib/plotly.jslib/ace/ace.jsProposal — three tiers
1. Frozen release lines —
expiration: 365dper handler, today.glow.3.1and older have not changed in 4+ years; a new release gets a new filename. Zero risk.2. Vendored third-party —
lib/ace,lib/jquery,lib/plotly.js. Versioned by directory or frozen since 2018, and not ours to modify. Same treatment.3. The current dev line (
glow.3.2.min.js,RScompiler.3.2.min.js) — where the 158 MB actually is, and the one that cannot take a long TTL as-is. Needs a content-derived URL so it changes when the bytes do. More work, and worth doing separately.Tiers 1 and 2 are a few lines of YAML and take a real bite out of egress on their own.
Not a CDN problem
Worth stating so nobody reaches for the wrong tool: GAE already serves these from Google's edge. Unlike the Cloud Run deploys, no CDN needs adding — the only thing missing is the cache policy.
Found while investigating unrelated
/google/auth500s (#210 / #211).