Skip to content

perf(api/config): use count_sessions() on status + snapshot (audit: list-sessions-decode-on-poll)#5326

Merged
houko merged 8 commits into
mainfrom
fix/audit-list-sessions-count
May 22, 2026
Merged

perf(api/config): use count_sessions() on status + snapshot (audit: list-sessions-decode-on-poll)#5326
houko merged 8 commits into
mainfrom
fix/audit-list-sessions-count

Conversation

@houko

@houko houko commented May 20, 2026

Copy link
Copy Markdown
Contributor

Closes #5331


Summary

Audit: docs/issues/list-sessions-decode-on-poll.md (Severity: Critical, Performance).

Two routes — /api/status and /api/dashboard/snapshot — computed
session_count via:

substrate.list_sessions().map(|s| s.len()).unwrap_or(0)

list_sessions() returns Vec<serde_json::Value> with each row's
full rmp-encoded message history decoded. Calling .len() on
the vec therefore decoded every session blob just to count rows.

The dashboard polls /api/dashboard/snapshot every 5 s with
refetchIntervalInBackground: false, so every foreground operator
hits it continuously. At 100 sessions × 200 KB each, the daemon
burnt ~20 MB/s of needless decode + rmp allocator pressure for
what is morphologically a SELECT COUNT(*).

Fix

MemorySubstrate::count_sessions() (indexed SELECT COUNT(*))
already exists in
crates/librefang-memory/src/substrate.rs:391. Both call sites in
crates/librefang-api/src/routes/config.rs (lines 235 + 3009) now
use it:

-let session_count = state.kernel.memory_substrate()
-    .list_sessions()
-    .map(|s| s.len())
-    .unwrap_or(0);
+let session_count = state.kernel.memory_substrate()
+    .count_sessions()
+    .unwrap_or(0);

Verification

cargo check -p librefang-api --lib                     # clean
cargo clippy -p librefang-api --all-targets -- -D warnings   # clean

Out-of-scope follow-ups

The audit doc suggests a criterion bench demonstrating the
delta. I've deferred that — the change is mechanically equivalent
to swapping a SELECT * for a SELECT COUNT(*), and the existing
session.rs SQL is already an indexed count over sessions. A
bench infrastructure scaffold belongs in a separate PR.

Test plan

  • CI green
  • After deploy, observe the dashboard 5 s poll CPU delta on a
    workspace with ≥50 sessions
  • curl /api/status and /api/dashboard/snapshot still return
    correct session_count (compare against SELECT COUNT(*) FROM sessions directly)

…() on status + snapshot (audit: list-sessions-decode-on-poll)

Two routes — /api/status and /api/dashboard/snapshot — computed
session_count via substrate.list_sessions().map(|s| s.len()).
list_sessions() returns Vec<serde_json::Value> with each row's
full rmp-encoded message history decoded, so calling .len() on
the vec required decoding every blob just to count rows.

The dashboard polls /api/dashboard/snapshot every 5s with
refetchIntervalInBackground=false, so every foreground operator
hits it continuously. At 100 sessions × 200 KB each, the daemon
burnt ~20 MB/s of needless decode + rmp alloc on the hot path —
for what is morphologically a SELECT COUNT(*).

MemorySubstrate::count_sessions() (indexed SELECT COUNT(*)) already
exists in librefang-memory/src/substrate.rs:391; both call sites
now use it.
@github-actions github-actions Bot added has-conflicts PR has merge conflicts that need resolution area/docs Documentation and guides size/S 10-49 lines changed labels May 20, 2026
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the list-sessions-decode-on-poll leg
from Pass 1 priority #4 (paired with audit-export-401; the other
leg is addressed by a separate PR so this PR only removes its own
half).
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the list-sessions-decode-on-poll leg
from Pass 1 priority #4 (paired with audit-export-401; the other
leg is addressed by a separate PR so this PR only removes its own
half).
@houko
houko force-pushed the fix/audit-list-sessions-count branch from 8beb7c7 to 825524b Compare May 20, 2026 13:45
Two #[tokio::test] cases against TestServer that seed N sessions via
the memory substrate and assert the wire field reflects N — catches
the .unwrap_or(0) silent-pin regression class that the dashboard's
existing smoke test (only checks status='running') would miss.
Required by CLAUDE.md route-test policy and the audit doc.

Also: clarify the comment + CHANGELOG figure to '~20 MB per poll
(≈ 4 MB/s)' — the original ~20 MB/s number was off by the 5 s poll
period.
@github-actions github-actions Bot added size/M 50-249 lines changed and removed size/S 10-49 lines changed labels May 20, 2026
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 21, 2026
@github-actions github-actions Bot added has-conflicts PR has merge conflicts that need resolution and removed ready-for-review PR is ready for maintainer review labels May 21, 2026
…ns-count

# Conflicts:
#	openapi.json
#	xtask/baselines/openapi.sha256
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review area/sdk JavaScript and Python SDKs size/XL 1000+ lines changed and removed has-conflicts PR has merge conflicts that need resolution size/M 50-249 lines changed labels May 21, 2026
@houko
houko merged commit 12a6296 into main May 22, 2026
7 checks passed
@houko
houko deleted the fix/audit-list-sessions-count branch May 22, 2026 01:32
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions Bot added size/M 50-249 lines changed and removed area/sdk JavaScript and Python SDKs size/XL 1000+ lines changed labels May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides ready-for-review PR is ready for maintainer review size/M 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(api/config): Dashboard 5s list_sessions() poll decodes every session's full rmp blob to count rows

1 participant