Problem
Flagship S3 was $211 for October by the 20th and is running at about $16/day (~$480/month). About $2/day in early September. Nearly all of it is request charges from polling, not storage and not writes:
| Oct 1–19 (Cost Explorer) | Requests | Cost | |---|---|---| | ListBucket | 26.8M | $146 | | GetObject | 118.9M | $51 | | Storage (~690 GB-month) | | $16 | | PutObject + multipart | 1.8M | $9 |
The bill steps up each time apps are added (+~0.75M LISTs/day on 2026-10-06 and again on 2026-10-18). It scales with apps × boxes, not with usage: about $3 per app per month in polling. Adding a box or a region raises every app's cost. Plan tiering does not help here; litestream (the carlos-data-ie bucket) is ~$26/month of it.
Evidence
S3 server access logs on the flagship bucket and all eu-west-1 store buckets, 2026-10-20 21:14Z to 2026-10-21 11:00Z (5.5M requests; rates match Cost Explorer). Logging has been turned off and the log bucket deleted. Everything that matters is in the flagship deployment bucket, made by the box roles (carlos-flagship-host[-au|-uk|-us]). Tenant store buckets are negligible. The IE box makes ~3.5x the LISTs of each other box because the console shares its role.
| Family (per day) | Count | ≈ /month | |---|---|---| | LIST apps/<acct>/<app>/instances/ | 1.17M | $175 | | LIST apps/<acct>/ (delimiter, one per account) | 0.49M | $73 | | LIST apps/<acct>/<app>/sidecars/ | 0.34M | $52 | | GET control/certs/* | 1.55M | $19 | | GET accounts/<sqid>/* | 1.41M | $17 | | GET apps/*/*/config-gen.json | 1.23M | $15 | | GET apps/*/*/channels/canary | 0.60M | $7 |
Causes (traced at v0.28.6)
- The walk.
provision.listAppSubtree (internal/provision/reconcile.go:2022) does ListDirs("apps/"), a ListDirs per account, then a List of instances/ or sidecars/ for every app in the bucket, not only the ones the box hosts. knownAccounts (:1760) lists accounts/ and re-GETs every account.json each time. - Three walks per full pass per box:
provision.Reconcile and ReconcileSidecars from reconcileSerializedWith (internal/host/agent.go:1605-1606), plus ListAllRecordsStrict in the moves pass (internal/host/movespass.go:1079). - Full passes fire too often. Any bump of the global
control/serial, by any tenant, makes all four boxes walk everything (fastTick → fullPass, agent.go:2557/2787). There is also the ungated 5–6 min instancesLoop (agent.go:3380-3397). Fitted: ~570 full passes per box per day. - Console run sweep:
StartRunSweep (internal/console/runsweep.go:17, 30s) runs ListAllRecordsTolerant across every app, ~2,880 walks/day on IE. seedsweep.go adds one every 5 min. - Ungated GET loops:
configGenLoop (agent.go:1979, every 3–4s) fully GETs each config-gen.json just to compare ETags. fastMembers (agent.go:2214/2242) GETs every canary channel pointer every 2s tick. The edge cert mirror (internal/edge/certsync.go:410, 30s) GETs every cert (140) on every edge box. telemetryPass reads accounts/<sqid>/telemetry.json per hosted app per pass.
The counts fit roughly 150 apps, 48 accounts with apps and ~140 account manifests. These are fitted, not counted; check against a listing before relying on the per-family split.
Proposed fixes, by saving
- One walk per full pass, and walk only what the box needs (~$100–200/month). Share one
listAppSubtree result across Reconcile, ReconcileSidecars and the moves pass. Then restrict box-side walks to hosted apps where the full set is not needed, or move to per-app change signals so a tenant's console action does not make every box re-list every app. - Console run sweep (~$90/month). List only apps with active runs, or gate it on the serial instead of 30s unconditional.
knownAccounts (~$17/month). Compute once per pass, and use ListMod + reuse-unchanged as in internal/domains/claimcache.go.- GET loops (~$40/month). Conditional GET (
If-None-Match) or ListMod over the prefix with reuse-unchanged, for config-gen, canary channel pointers and the cert mirror. The cert mirror change also shortens the boot refresh that bit the AU box (#118).
All together this should take flagship S3 from ~$430/month to well under $100, and stop each new app adding ~$3/month. (1) and (2) matter before paid plans launch.