ci(prod): record deployments again, and the sglake flag that starves the small indexes - #196
Conversation
The repo's Environments panel has shown `production — failure` since 2026-06-23. Nothing failed. deploy-prod run on 57ba003 named the `production` environment, that environment requires a reviewer, nobody approved, and GitHub fails a deployment left waiting for 30 days — so it turned red on 2026-07-23, a month after the release it was for had come and gone. No step ever ran. #192 then removed `environment: production` along with the approval it carried, on the reasoning that cutting a tag IS the approval and release.yml already refuses to build a commit that is not both `staging-soaked` and `ebpf-soaked`. That reasoning holds. What it missed is that naming an environment is also the only thing that makes GitHub log a deployment at all — so since #192 prod has shipped four releases without recording one, and the panel has been frozen on the stale June entry, reporting a failure for a commit two versions behind what is actually running. So: name the environment again, for the record and not for a gate. This only works with the reviewer rule off the environment — with it on, the next release parks for 30 days and then goes red exactly as before. ORDERING: the `production` environment must have its required-reviewers rule removed BEFORE this merges. Merging first re-arms the bug.
`--max-hot-raw-mib 2048` was documented as the bucket-sizing answer. It is one, for `heron_bodies` — ~200 KB events reach it every few hours, so that index is almost entirely warm and carries a tsidx. `heron_spans` carries scalars. A production instance accumulated 135 MB of raw spans in three days, so it reaches 2048 MiB in about seven weeks, and `--max-hot-span-hours` defaults to 90 days. The metadata index is therefore the one that never seals and never gets an on-disk inverted index — the opposite of what the bucket-count analysis predicts. A hot bucket has no tsidx, so sglogd builds one in memory per search, and Heron appends every flush interval, which invalidates it about as fast. The cost tracks how much journal that build covers, and nothing else: heron_traces 3.4 MB hot point lookup 65 ms, no outliers count 82 ms heron_spans 22 MB hot point lookup 12 ms → 2.2 s spikes count 4.2 s Same code path, same absent tsidx; the size is the only variable. At 22 MB it was the dominant cost of opening an agent turn — polling once a second, ~40% of lookups paid ~2 s. It is invariant in every way that misleads: same for a 1-call turn as an 85-call one, and same whether the window is the turn's own span, ±24 h, or unbounded — which is why it reads as "the server is fine" if you only ever measure a repeated request. Document `--max-hot-span-hours 6` as the third required flag, with the measurements and the reason the size knob alone cannot cover both shapes.
There was a problem hiding this comment.
Both files read, cross-references checked. This is a small PR: one workflow line re-adding environment: production for deployment bookkeeping, and a substantial sglake doc section documenting a new recommended sglogd flag. No console, no Rust, no SQL, no schema touched — most of the classic risk categories don't apply.
Summary
Re-adds environment: production to the deploy job (for the GitHub Environments panel record, not as a gate) and documents --max-hot-span-hours 6 as a third required sglogd flag in the sglake design doc, with behind-the-scenes measurements. The workflow change is correct and well-reasoned: re-naming the environment only matters for the record, and the comment correctly warns it re-arms the 30-day-red bug if a reviewer rule is re-added. The doc section is technically sound and the flag logic checks out. My take: APPROVE.
Suggestions
- server/config/default.toml:137-150 — The design doc now calls
--max-hot-span-hours 6a "third required flag" (10-sglake.md:305,310), butdefault.tomlstill lists only three items under "Three things about the sglogd side that Heron cannot set for you" and none is--max-hot-span-hours. This is the other operator-facing surface for the same daemon config, and staying silent on the one flag whose omission is now documented as "produces a slow console" leaves a reader who configures from default.toml without the design doc unknowingly mis-tuned. Add it there (and toscripts/staging/soakdoc references if ether reference it).
Questions
- docs/design/10-sglake.md:310 —
--max-hot-span-hours(default 2160 h) is asserted but never appears anywhere in the repo, and I can't verify the flag name/default against sglogd's own flag surface. Was the exact flag name and its 90-day default confirmed from sglogd herbinsdocs/shuj_init? A wrong name here becomes a trustworthy-but-wrong operator instruction. - docs/design/10-sglake.md:347 — "only
--max-hot-span-hoursdoes that for an index whose events are small." Is there nomax-hot-buckets-style count cap that would act more uniformly than a per-index hours tuner, or was a per-daemon size tie confirmed unreachable for the metadata indexes (the 2048 MiB ≈ 7 weeks math)? - docs/design/10-sglake.md:331 — the measurements are single-instance ("against that instance"), and the "four buckets a day"@6 h, "120 buckets over 30 days" guidance drifts if retention is not 30 days. Intended that the numbers are only illustrative?
Verified
- No leakage: the diff and the full working tree contain no private IPs, credentials, key material, or internal hostnames (grep over the diff and the doc for RFC1918/CGNAT ranges and
-----BEGIN/PRIVATE KEY— empty). The HERON_PROD_* vars referenced at deploy-prod.yml:107–109 are configurable env, not secrets-in-repo. environment: productionat deploy-prod.yml:71 introduces the only environment-scoped resource in this job; it wraps no env-scoped secrets (only the always-presentGITHUB_TOKEN), so re-adding the environment does not break secret access — consistent with its stated "for the record, not for a gate" purpose.- Commit 1's claim (#192 dropped the line; panel frozen on a stale failure) is consistent with the workflow's current diff —
environment: productionis the only line added, and the file documents #192 as having removed the approval. - Doc internals: the
heron_bodies2048 MiB /heron_spans7-week math is internally consistent (135 MB/3 days × ~51 days ≈ 2048 MiB,2×= time-over-Jan then reach to scale). No body-scan or schema-drift concerns — this PR touches no SQL and no schemas. - Cross-references to
--max-hot-raw-mibnumbers indefault.tomlandit.rsremain accurate; the new section copies the same 124k-span/12-warm-bucket figures already used at line 309.
🤖 Reviewed by the review bot • workflow run
Two unrelated fixes, both fallout from chasing why opening an agent turn felt
slow.
1.
deploy-prodrecords a deployment againThe Environments panel has read
production — failuresince 2026-06-23, andnothing failed. The deploy-prod run on
57ba003named theproductionenvironment, that environment required a reviewer, nobody approved, and GitHub
fails a deployment left waiting for 30 days — so it turned red on 2026-07-23, a
month after the release it was for had come and gone. No step ever ran.
#192 then dropped
environment: productionalong with the approval it carried,on the reasoning that cutting a tag is the approval and
release.ymlalready refuses to build a commit that is not both
staging-soakedandebpf-soaked. That reasoning holds. What it missed is that naming anenvironment is also the only thing that makes GitHub log a deployment at all —
so prod has deployed twice since (v0.7.2, v0.7.3) without recording either, and
the panel stayed frozen on the June entry, reporting a failure for a commit that
predates both.
This names the environment again, for the record and not for a gate.
Prerequisite, already applied: the
productionenvironment'srequired-reviewers rule has been removed —
protection_rules: []. It had to gobefore this merges, not after: with the rule still on, the next release parks
for 30 days and then goes red exactly as before. The comment on the new line
says so, so nobody re-adds it.
Verification is the next release — deploy-prod should leave a
successdeployment behind and the panel should move off the June entry. Until then it
stays red, which is expected.
2.
--max-hot-span-hours— the size knob is global, the indexes are not10-sglake.mdsaid to raise--max-hot-raw-mibto 2048 and stopped there.That is right for
heron_bodies, which carries ~200 KB events and seals everyfew hours on size alone. It is wrong for
heron_spans, which carries scalars:135 MB of raw spans in three days reaches 2048 MiB in about seven weeks, and the
--max-hot-span-hoursdefault is 90 days. So the metadata index is the onerunning with no on-disk
index.tsidx— the opposite of what the bucket-countanalysis in that same section leads you to expect.
A hot bucket has no inverted index, so sglogd builds one in memory per search,
and Heron appends every
flush_interval_ms, invalidating it about as fast as itis built. Measured with both indexes hot, under live ingest:
journal.sgjstats countheron_tracesheron_spansSame code path and the same absent tsidx in both rows; the only variable is how
much journal the in-memory build has to cover. Polling once a second, ~40% of
lookups paid it, and at that point it is the dominant cost of opening an agent
turn.
The doc now names
--max-hot-span-hours 6as a third required flag, explainswhy the size knob alone cannot cover an index whose events are small, and
records what makes this easy to misdiagnose: the cost is invariant in every way
that suggests the query is fine — the same for a 1-call turn as an 85-call one,
and the same whether the search window is the turn's own span, ±24 h, or
unbounded.
Docs and CI only; no Rust or console code changes.