Add tickets and ideas

This commit is contained in:
Hugh Kaznowski
2026-09-07 12:16:36 +01:00
parent 0c41048e0f
commit 49bfd6b020
15 changed files with 1188 additions and 12 deletions
@@ -0,0 +1,142 @@
# Idea: profile switching triggers a full synchronous re-index per metric
## Original note
"Metrics and dashboards are tied with the profile. However, they are
indexed on the engine. Switching profiles means re-indexing." — a
description of a real coupling; unclear whether it's being flagged as
a performance problem to fix or just an architectural note to be aware
of.
## Current state (confirmed in code)
- `Profile` (`src/config/profile.rs:309-318`) is pure config — params,
metrics, dashboards — with no live engine state of its own.
`Settings::set_active_profile` (`src/config/profile.rs:489-502`)
only flips which profile is active and persists to disk; it doesn't
touch the engine.
- The actual switch side effect lives in the API layer:
`activate_profile` (`src/api/mod.rs:746-768`) calls
`reload_engine_declarations` (`src/api/mod.rs:712-744`), which:
1. Removes every current param/metric declaration from the engine
(`EngineMessage::RemoveParamDeclaration`/`RemoveMetricDeclaration`,
skipping auto-derived params).
2. Calls `profile.load_into_engine(engine)`
(`src/config/profile.rs:332-384`), re-declaring every param/metric
in the newly-activated profile.
- There is no dedicated `EngineMessage` for "switch profile" — it's
entirely remove-everything-then-redeclare-everything via the
existing declare/remove messages
(`src/engine/message.rs:30-63` has no such variant).
- Each metric redeclare triggers a full rebuild: `declare_metric`
(`src/engine/snapshot/mod.rs:330-335`) →
`resolve_backfill_and_store_metric` → `backfill_metric`
(`:481-509`), which replays **all retained log history**
(`self.logs_range()`) through `apply_metric_to_log` for that metric.
Raw ingested lines themselves are never cleared on switch — only
metric/param declarations and their derived buckets are dropped and
rebuilt.
- This all happens synchronously, one metric at a time, inside the
engine's single-writer actor loop
(`src/engine/handle.rs:303-338`) — so switching profiles blocks that
loop for a duration that scales with retained log volume × number of
metrics in the newly-activated profile.
## Why this is an idea, not a ticket
Whether this is worth fixing depends on facts not available from
reading code alone: typical retained log volume in real usage, typical
metric count per profile, and how often users actually switch profiles
mid-session. Without knowing the current switch latency is actually
noticeable, a fix would be speculative optimization.
## Options if this turns out to matter
- Diff-based reload: only remove/re-add metrics whose *definition*
actually changed between the old and new profile, instead of
removing everything and redeclaring everything (a metric present
unchanged in both profiles currently still gets a full remove +
backfill for no reason).
- Make the removal/backfill sequence async relative to the HTTP
response, with the UI showing a "reindexing…" state rather than
blocking the request (and the engine loop) until complete.
- Lazily backfill only the time range currently being viewed, deferring
the rest — a bigger change, and only worth it if the switch cost is
dominated by backfill work the user doesn't immediately look at
anyway.
## Suggested next step
Before scoping a ticket: confirm with a profile that has a realistic
number of metrics and log volume whether switch latency is actually
perceptible. If it is, the diff-based reload (first option) is the
cheapest fix and a reasonable first ticket.
## Implications and things to consider
### Performance
- Cost today is O(retained lines × metrics in new profile), done
synchronously in the single-writer engine actor
(`src/engine/handle.rs:303-338`) — every other engine message
(ingest, queries, alert evaluation) queues up behind a profile switch
until it finishes, so the blast radius isn't just "the switch feels
slow", it's "the whole app appears frozen" for that duration,
including live ingestion of new lines arriving mid-switch.
- The diff-based reload option only helps when profiles share metrics
— if switching to a profile with a mostly-disjoint metric set (the
common case for genuinely different apps/profiles, which is the
whole point of profiles), it doesn't reduce the backfill cost much.
It's a bigger win for "tweak one metric in the currently active
profile and reactivate" workflows than for switching between
unrelated profiles.
- If [[0001-actor-split]] happens, this problem's shape changes
significantly — a per-profile worker rebuilding its own perspective
from a shared primary index could do that rebuild without blocking
ingestion or query-serving on the primary at all, which addresses the
"whole app freezes" part of this problem as a side effect, even if
the total backfill work stays the same. Worth reading that idea
alongside this one.
### Code structure
- The remove-then-readd flow lives entirely in the API layer
(`src/api/mod.rs:712-768`), not the engine — a diff-based reload
would need `reload_engine_declarations` to compute a diff (metrics
present in old profile but not new → remove; new but not old →
declare; present in both — compare full declaration equality, not
just name, to decide if a changed regex/threshold needs a rebuild)
before sending any engine messages. That diff logic is new code, not
a variant of existing code.
- Async/non-blocking reload would need a new `EngineMessage` variant
(today there is none for "switch profile" — it's just existing
declare/remove messages) if the goal is to let the engine
acknowledge the switch request immediately and do backfill work
in the background while still serving other queries — this is a
bigger structural change than diff-based reload, since the engine's
single-actor model currently has no notion of "background work
interleaved with message handling."
### Functionality
- If reload becomes async/background, the UI needs a defined
"reindexing in progress" state — what does a dashboard panel show
for a metric that's declared but not yet backfilled? An empty chart
is misleading (looks like "no data") versus a genuinely no-data
metric.
- Partial-failure semantics: if backfill for metric 3 of 5 fails or is
interrupted (e.g. app restart mid-switch), what state is left behind?
Today's synchronous approach has no partial-state problem because it
either completes or the whole process is still running; async reload
introduces a new failure mode that doesn't exist today.
### Not yet considered
- No data gathered yet on real profile sizes/log volumes in actual
usage (flagged already in "Why this is an idea") — this entire idea
is speculative until that's known.
- No consideration of whether profile switching happens automatically
(e.g. auto-detected based on the piped command) or is always a
manual user action — if it's always manual and rare, the UX cost of
a blocking-but-rare operation may be perfectly acceptable and not
worth any of the above complexity.