Relationships
#2446 serve: make the datastore poll interval configurable (--datastore-poll-interval)
Opened by stack72 · 9/23/2026· Shipped 9/23/2026
Repo: swamp (checked against 379bb5f0). Scope: swamp serve only.
Problem
swamp serve runs three background pollers that pull from the remote datastore:
ConfigPoller(src/serve/config_poller.ts) — managedConfig definitions + pulled extensionsAccessDataPoller(src/serve/access_data_poller.ts) — grants + groupsRuntimeDataPoller(src/serve/runtime_data_poller.ts) — runtimedata/
All three default to 30 s (DEFAULT_*_POLL_INTERVAL_MS = 30_000), and all three
already accept pollIntervalMs in their options. But serve.ts never passes it
(serve.ts:1973, :2719, :2728), so operators can't tune how quickly peers
converge. Tests that exercise peer replication also can't run fast: swamp-uat's
managedConfig serve lane and tests/cli/serve/access/ha_test.ts each wait up to
90 s per test.
Change
Add one setting that controls all three pollers:
| Surface | Name |
|---|---|
| CLI flag | --datastore-poll-interval <duration:string> |
| Env var | SWAMP_DATASTORE_POLL_INTERVAL |
serve.yaml key |
datastore-poll-interval |
Default: 30 s (unchanged behaviour when unset). Precedence: CLI > env > serve.yaml, the same as every other serve option.
One setting rather than three: the pollers do the same job (periodic scoped pull
from the datastore), and operators reason about it as "how stale can a peer be".
The name says datastore to separate it from GrantsDirectoryPoller (a local
directory, not in scope) and --group-refresh-interval (IdP, not in scope).
Follow --heartbeat-interval / --reconciliation-interval exactly. Every step
below has a sibling to copy.
1. Option definitions — src/cli/commands/serve.ts
servecommand (~line 1314, next to--heartbeat-interval): add--datastore-poll-interval <duration:string>. Help text in the same style: "How often to pull config, access data and runtime data from the remote datastore. Accepts seconds (30), explicit units (30s, 1m). Default: 30s. Minimum: 1s. Only effective with a remote datastore (env: SWAMP_DATASTORE_POLL_INTERVAL)".serve daemon enable(daemonEnableCommand, ~line 896): add the same option with short help text ("Datastore poll interval (default: 30s, env: SWAMP_DATASTORE_POLL_INTERVAL)").collectServeExtraArgs(~line 605): forward it, i.e.if (options.datastorePollInterval) args.push("--datastore-poll-interval", …).
2. Config merge — src/serve/serve_config.ts
- Env map (~line 52):
datastorePollInterval: "SWAMP_DATASTORE_POLL_INTERVAL". - Options type (~line 784):
datastorePollInterval?: string;. - Merge (~line 1076):
resolveString("datastore-poll-interval", cliOptions.datastorePollInterval, config?.["datastore-poll-interval"], undefined). - Include it in the returned merged object (~line 1212).
- If
serve.yamlkeys are validated against a schema or known-key list, add the key there too.
3. Parse and validate — src/cli/commands/serve.ts
- Next to
heartbeatIntervalMs(~line 1485):datastorePollIntervalMs = raw !== undefined ? parseTimeout(raw, "--datastore-poll-interval") : undefined.parseTimeout(src/cli/duration_parser.ts) already rejects ≤ 0 and bad formats. - Add a floor: below 1000 ms →
UserError:--datastore-poll-interval must be at least 1s; got <raw>. Every tick is a remote list call, so sub-second values would hammer the bucket. - "No effect" warning: the access and runtime pollers start whenever
syncServiceexists (serve.ts:2718); the config poller also needs a remote control plane +managedConfig. So warn only when there's nosyncService(filesystem datastore):logger.warn("--datastore-poll-interval has no effect without a remote datastore"). Don't copy the heartbeat condition (!hasRemoteControlPlane) — it's a different gate.
4. Wire into the pollers — src/cli/commands/serve.ts
Pass pollIntervalMs: datastorePollIntervalMs (spreading only when defined, so
each poller keeps its default) to:
new ConfigPoller({...})(~line 1973)new AccessDataPoller({...})(~line 2719)new RuntimeDataPoller({...})(~line 2728)
No poller changes are needed. Each already logs its interval at start (e.g.
Config poller started (interval: ${intervalSec}s)), and each has a #pulling
guard, so a pull slower than the interval (up to POLLER_PULL_TIMEOUT_MS =
120 s) skips ticks and never overlaps.
5. Tests (unit, in swamp)
src/serve/serve_config_test.ts: CLI > env > yaml precedence fordatastore-poll-interval; unset →undefined. Copy theheartbeat-intervalcases (~line 92).src/cli/commands/serve_daemon_test.ts:collectServeExtraArgs({ datastorePollInterval: "5s" })→["--datastore-poll-interval", "5s"]. Copy the test at ~line 111.- Validation:
"500ms"→ UserError mentioning "at least 1s";"0"/"abc"→parseTimeouterror;"2s"→ 2000.- If validation is inline in the serve action and hard to reach from a unit
test, extract a small
parseDatastorePollInterval(raw): number | undefinedhelper next to the other parsing and test that.
- If validation is inline in the serve action and hard to reach from a unit
test, extract a small
- Poller wiring: if there's an existing seam that builds pollers in tests, assert the interval arrives. Otherwise the log-line check in the acceptance criteria below covers it.
6. Docs
design/primitives/serve.md:- Options table (~line 84): add a row for
--datastore-poll-interval/SWAMP_DATASTORE_POLL_INTERVAL/ 30 s / "config, access and runtime pollers; min 1s; no effect without a remote datastore". - Update the fixed "30 s" wording at ~lines 362, 369 and 429 to say
"every
--datastore-poll-interval(default 30 s)".
- Options table (~line 84): add a row for
design/enablers/datastores.md~line 444 ("All three run every 30 seconds by default…"): name the setting.- Check
.claude/skills/for any mention of the 30 s poll. If a skill file changes, the repo's CLAUDE.md requires loading theskill-creatorskill first and runningdeno fmt.
Acceptance criteria
swamp serve --helplists--datastore-poll-intervalwith the env name.- With an S3 datastore +
managedConfig: trueand--datastore-poll-interval 2s, the log shows:Config poller started (interval: 2s);- the matching start lines for the access and runtime pollers with the same interval.
- With the same setup, a model created on instance A shows up on instance B in about 2 s, not 30 s.
SWAMP_DATASTORE_POLL_INTERVAL=2sandserve.yamldatastore-poll-interval: 2sbehave the same as the flag. CLI beats env, and env beats yaml.--datastore-poll-interval 500ms→ exit 1 with "must be at least 1s".0andabc→ exit 1 viaparseTimeout.- Filesystem datastore + the flag → server starts and logs the "no effect without a remote datastore" warning.
- Unset → every poller still logs
interval: 30s(no behaviour change). serve daemon enable --datastore-poll-interval 10sforwards the flag into the generated service definition.deno check,deno lint,deno fmtand the unit tests pass.
Out of scope
GrantsDirectoryPoller(local--grants-dirpoll, 30 s) and--group-refresh-interval.- Separate per-poller settings.
- Changing the 30 s default.
Follow-up in swamp-uat (not part of this change)
- The planned managedConfig UAT serve lane sets
SWAMP_DATASTORE_POLL_INTERVAL=2sin the serve env; the plannedSWAMP_UAT_POLL_INTERVALindirection goes away. tests/cli/serve/access/ha_test.ts:233can drop its 90 s deadline.- Add a UAT for acceptance criteria 2–5 in
tests/cli/serve/none/config_test.ts(flag parsing) and in the managedConfig serve lane (replication speed).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.