fix(renderer): pre-render the static legal pages at boot
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m13s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 2m28s

Serving /eula/ re-parsed the large (~122 KB) EULA markdown on every request. Under the rolling deploy's host contention (the renderer is capped at 1 CPU / 192 MB) that was slow enough that the contour /eula/ probe failed for its whole 60s retry window, while the smaller /privacy/ squeaked through; both serve fine once the host is idle. Render privacy + eula once at boot and serve the cached HTML (now ~1.5 ms, a memcpy). The offer stays per-request for its live price splice; the probe retry stays as a readiness backstop.
This commit is contained in:
Ilia Denisov
2026-07-13 12:36:33 +02:00
parent 807edff327
commit 5a4c460268
2 changed files with 9 additions and 3 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ signed off) and the public **legal pages** (the offer, the privacy policy and th
here; a backend outage yields a 502, never a stale price list.
- `GET /privacy/` — the privacy policy, and `GET /eula/` the end-user licence agreement, both as
`text/html`: the owner-edited `ui/legal/privacy_ru.md` / `ui/legal/eula_ru.md` (baked in, read at
boot), rendered by the same shared `renderLegalHtml`. **Static** — no backend fetch. `GET /privacy`
boot), rendered once at boot by the same shared `renderLegalHtml` and served from cache. **Static** — no backend fetch. `GET /privacy`
/ `GET /eula` → 301 to the trailing-slash form. Caddy routes these here too.
- `GET /healthz` — liveness (the compose healthcheck the backend's `depends_on` gates on).
+8 -2
View File
@@ -33,6 +33,12 @@ const OFFER_MD = readFileSync(process.env.RENDERER_OFFER_MD || new URL('../legal
// boot, no dynamic data. RENDERER_PRIVACY_MD / RENDERER_EULA_MD override the baked path for a local run.
const PRIVACY_MD = readFileSync(process.env.RENDERER_PRIVACY_MD || new URL('../legal/privacy_ru.md', import.meta.url), 'utf8');
const EULA_MD = readFileSync(process.env.RENDERER_EULA_MD || new URL('../legal/eula_ru.md', import.meta.url), 'utf8');
// Render the static legal pages once at boot and serve the cached HTML: they carry no dynamic data,
// and re-parsing the large EULA markdown on every request is slow enough under deploy-time host
// contention (the renderer is CPU/memory-capped) to flake the contour probe. The offer stays
// per-request because it splices in the live price list.
const PRIVACY_HTML = renderPrivacy(PRIVACY_MD);
const EULA_HTML = renderEula(EULA_MD);
const OFFER_PRICING_URL =
(process.env.RENDERER_BACKEND_URL || 'http://backend:8080') + '/api/v1/internal/offer/pricing';
@@ -89,13 +95,13 @@ const server = createServer(async (req, res) => {
if (req.method === 'GET' && path === '/privacy/') {
res
.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' })
.end(renderPrivacy(PRIVACY_MD));
.end(PRIVACY_HTML);
return;
}
if (req.method === 'GET' && path === '/eula/') {
res
.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' })
.end(renderEula(EULA_MD));
.end(EULA_HTML);
return;
}
if (req.method === 'POST' && path === '/render') {