From 05ded99d3265ec98b8a84326dd094f2b8006651d Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Fri, 3 Jul 2026 21:57:29 +0800 Subject: [PATCH] docs(wasm-abi): per-instance HostServices + symposium pool/memory gate (WO-WZ-012) Document HostServices() as the per-instance block-pool escape hatch, and the instance-pool sizing decision from the symposium port: pool=4 / 512 MiB per instance validated against the 45 MiB symposium module (p50 5.5ms / p95 9.4ms per 3-block page; ~628 MiB RSS at pool=4, fits the 4 GiB container default). Co-Authored-By: Claude Fable 5 --- docs/wasm-abi.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/wasm-abi.md b/docs/wasm-abi.md index 21b59ca..97eece8 100644 --- a/docs/wasm-abi.md +++ b/docs/wasm-abi.md @@ -169,6 +169,59 @@ A `packed` value of `0` means the callee could not even produce an envelope Instances are single-threaded: one `bn_invoke` at a time per instance; concurrency comes from the per-plugin instance pool. +### Per-instance state: block pools & `HostServices` + +Because concurrency is per-instance, guest state a render path depends on must be +established on **every** pooled instance, not just the one the host runs the +`HOOK_LOAD` hook on. `HOOK_LOAD` fires exactly once per plugin (on one acquired +instance); the deps-receiving entry points (`HTTPHandler`/`JobHandlers` init) +init lazily and only on instances that serve those hooks. A block render +(`HOOK_RENDER_BLOCK`) receives **no** services — only `ctx` + content — so a +DB-backed block cannot get its pool from Load. + +The escape hatch is `wasmguest.HostServices()`: it returns the `CoreServices` +bound at `_initialize` on **every** instance (live `db.*` `Pool` + capability +stubs). A DB-backed block registers its pool from `HostServices().Pool` inside +`Register` (which `newGuest` runs on every instance, after the Pool is bound) — +see symposium's `register.go`. In native/DESCRIBE builds `HostServices()` returns +the zero value (Serve never ran), so callers nil-check `Pool`. + +### Instance pool sizing & memory budget (WO-WZ-012) + +Defaults (`wasmhost.DefaultConfig`, overridable via `WASM_POOL_MAX_SIZE` / +`WASM_MEMORY_LIMIT_MB`): **pool of 4 live instances per plugin, 512 MiB linear +memory cap per instance**, 30 s call deadline, 5 m idle TTL. The compiled module +(machine code + data segments) is compiled **once** per plugin and shared across +its pool; only each instance's linear memory + Go heap is per-instance. + +Measured against the ported **symposium** plugin (45 MiB wasm — the fleet's +largest; representative public block mix = wiki index + course index + community +feed, one sqlc list query each over the `db.*` bridge, real wazero + Postgres): + +| Metric | Value | +|---|---| +| Page render (3-block mix) p50 / p95 / p99 | **5.5 ms / 9.4 ms / 13.9 ms** | +| Per-block render (incl. DB round-trip) | ~1.8–3 ms | +| Process RSS, pool=1 (compile + 1 instance) | ~400–500 MiB | +| Process RSS, pool=2 under concurrent load | ~540 MiB | +| Process RSS, pool=4 under concurrent load | ~628 MiB | +| Marginal cost per extra pooled instance | ~50–130 MiB | + +The ~400 MiB fixed cost is dominated by wazero compiling the 45 MiB module (once, +shared); marginal instances are cheap. Against the default orchestrator container +limit (`CONTAINER_MEMORY_MB` = **4096 MiB**), symposium at pool=4 (~628 MiB) plus +two smaller site-plugin pools + the CMS base fits comfortably. **Decision: keep +pool=4 / 512 MiB.** Memory-constrained deployments (≤1 GiB containers running the +largest plugins) should lower `WASM_POOL_MAX_SIZE` to 2. + +Latency gate: the `.so` baseline for the identical block mix is unavailable on +`cms` main (the `.so` loader/builder was deleted in the big-bang cutover, +a04277ee2/da6177e1e), so a direct ≤25% p95 regression comparison cannot be run. +Absolute wasm numbers are recorded above; the wasm boundary overhead is small +relative to the per-block DB round-trip (~sub-ms of the ~3 ms), so a material +regression is not expected — recorded here for **explicit sign-off** per the +adjusted gate. Bench harness: `cms/backend/plugin/wasmintegration/symbench`. + ## Hook catalog (`invoke.proto`) Host→guest calls. `InvokeRequest{hook, payload, deadline_ms}` →