perf(memory_sources): fold per-source status into one mem_tree_chunks scan - #5243
perf(memory_sources): fold per-source status into one mem_tree_chunks scan#5243mysma-9403 wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughSource status now resolves pending chunks using active embedding signatures and lifecycle markers. Status listing adds bounded batch aggregation with per-source fallback. Tests cover resolution, ordering, empty sources, and aggregate null handling. UI documentation reflects the updated embedding status semantics. ChangesMemory source status
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant status_list
participant SQLite
participant source_status
status_list->>SQLite: aggregate source statuses in bounded batches
SQLite-->>status_list: return status aggregates
status_list->>source_status: calculate fallback status if aggregation fails
source_status-->>status_list: return per-source status
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| src/openhuman/memory_sources/status.rs | Core change: adds batch_status/aggregate_prefixes fast path, retains per-source fallback, adds three integration tests; SQL semantics and NULL handling are correct. |
Sequence Diagram
sequenceDiagram
participant UI as MemorySourcesRegistry (every ~5s)
participant SL as status_list
participant BS as batch_status
participant AP as aggregate_prefixes
participant DB as mem_tree_chunks (SQLite)
participant FB as status_list_per_source
UI->>SL: poll status
SL->>BS: batch_status(config, sources)
BS->>DB: acquire connection mutex (once)
loop N/128 batches
BS->>AP: aggregate_prefixes(conn, batch)
AP->>DB: SELECT SUM/MAX CASE x N FROM mem_tree_chunks WHERE prefix1 OR prefix2
DB-->>AP: 1 row x 3N columns
AP-->>BS: Vec of triples
end
BS-->>DB: release mutex
BS-->>SL: "Ok(Vec<SourceStatus>)"
SL-->>UI: "Vec<SourceStatus>"
alt batch query fails
BS-->>SL: Err(e)
SL->>FB: status_list_per_source
FB-->>SL: "Ok(Vec<SourceStatus>)"
SL-->>UI: "Vec<SourceStatus>"
end
Reviews (2): Last reviewed commit: "chore(memory_sources): add batched-statu..." | Re-trigger Greptile
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/memory_sources/status.rs (1)
119-249: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd entry/branch diagnostics for the new batched status flow.
status_list,batch_status, andaggregate_prefixesare a new/changed hot-path flow (polled every ~5s per the doc comment), but the only diagnostic emitted istracing::warn!on the fallback/error branch (Line 127). There's no entry log, no log marking that the batched fast path succeeded (vs. fell back), and no log around the actual DB call inaggregate_prefixes/batch_status. This makes it hard to grep for whether polls are taking the fast or slow path in production.🔎 Proposed diagnostics
pub async fn status_list(config: &Config) -> Result<Vec<SourceStatus>, String> { let sources = crate::openhuman::memory_sources::registry::list_sources().await?; if sources.is_empty() { return Ok(Vec::new()); } + tracing::debug!( + source_count = sources.len(), + "[memory_sources:status] status_list: entry" + ); match batch_status(config, &sources).await { - Ok(statuses) => Ok(statuses), + Ok(statuses) => { + tracing::debug!( + source_count = statuses.len(), + "[memory_sources:status] status_list: batched path ok" + ); + Ok(statuses) + } Err(e) => { tracing::warn!(Based on coding guidelines, "New or changed flows must include verbose, grep-friendly diagnostics for entry/exit, branches, external calls, retries/timeouts, transitions, and errors" for
**/*.{rs,ts,tsx}.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/memory_sources/status.rs` around lines 119 - 249, Add verbose, grep-friendly tracing diagnostics throughout the batched status flow: log entry and completion in status_list, explicitly log whether batch_status succeeds or falls back to status_list_per_source, and log entry/exit plus database-query errors around batch_status and aggregate_prefixes. Include useful context such as source and batch counts without changing status behavior.Source: Coding guidelines
🧹 Nitpick comments (1)
src/openhuman/memory_sources/status.rs (1)
194-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate triple →
SourceStatusconversion logic.This block duplicates the
(synced, pending, last_ts)→SourceStatusmapping (including the.max(0) as u64clamps andFreshnessLabel::from_age_mscall) already present insource_status(Lines 85-93). Extracting a small shared helper avoids the two implementations silently diverging on a future change.♻️ Proposed extraction
+fn triple_to_status( + source_id: String, + (synced, pending, last_ms): (i64, i64, Option<i64>), + now_ms: i64, +) -> SourceStatus { + SourceStatus { + source_id, + chunks_synced: synced.max(0) as u64, + chunks_pending: pending.max(0) as u64, + last_chunk_at_ms: last_ms, + freshness: FreshnessLabel::from_age_ms(last_ms, now_ms), + } +}Then both
source_statusandbatch_statuscan calltriple_to_status(...)instead of repeating the field mapping.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/memory_sources/status.rs` around lines 194 - 205, Extract the shared `(synced, pending, last_ms)` to `SourceStatus` conversion into a helper such as `triple_to_status`, preserving the non-negative clamps and `FreshnessLabel::from_age_ms` calculation. Update both `source_status` and `batch_status` to call this helper instead of duplicating the field mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/openhuman/memory_sources/status.rs`:
- Around line 119-249: Add verbose, grep-friendly tracing diagnostics throughout
the batched status flow: log entry and completion in status_list, explicitly log
whether batch_status succeeds or falls back to status_list_per_source, and log
entry/exit plus database-query errors around batch_status and
aggregate_prefixes. Include useful context such as source and batch counts
without changing status behavior.
---
Nitpick comments:
In `@src/openhuman/memory_sources/status.rs`:
- Around line 194-205: Extract the shared `(synced, pending, last_ms)` to
`SourceStatus` conversion into a helper such as `triple_to_status`, preserving
the non-negative clamps and `FreshnessLabel::from_age_ms` calculation. Update
both `source_status` and `batch_status` to call this helper instead of
duplicating the field mapping.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f7405964-a98f-494e-b3ac-98bf3fa96af1
📒 Files selected for processing (1)
src/openhuman/memory_sources/status.rs
|
Addressed the review feedback in 86338c0:
No behavior change; tests still green ( |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat --oneline --decorate --no-renames 86338c094
git show --format=fuller --no-ext-diff --unified=30 86338c094 -- src/openhuman/memory_sources/status.rs
printf '\n--- Relevant tracing and formatting calls in current file ---\n'
ast-grep outline src/openhuman/memory_sources/status.rs --items all --type function --match 'status_list|batch_status|aggregate_prefixes'
rg -n -C 3 'tracing::|trace!|debug!|info!|write!\(' src/openhuman/memory_sources/status.rsLength of output: 13011
Thanks for addressing both items. 🐇 If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/openhuman/memory_sources/status.rs (1)
124-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLog the empty-source branch before returning.
The entry diagnostic is emitted after the early return, so empty polls produce neither an entry nor a terminal branch trace.
Proposed fix
pub async fn status_list(config: &Config) -> Result<Vec<SourceStatus>, String> { + tracing::debug!("[memory_sources:status] status_list: entry"); let sources = crate::openhuman::memory_sources::registry::list_sources().await?; if sources.is_empty() { + tracing::debug!( + "[memory_sources:status] status_list: no registered sources; returning empty" + ); return Ok(Vec::new()); }As per coding guidelines, new or changed flows require grep-friendly diagnostics for entry/exit and branches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/memory_sources/status.rs` around lines 124 - 143, Update the status-list flow around the empty-source early return so the entry diagnostic is emitted before checking for an empty sources collection. Preserve the existing empty-result return, and ensure the empty branch also emits a grep-friendly terminal trace before returning.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/openhuman/memory_sources/status.rs`:
- Around line 124-143: Update the status-list flow around the empty-source early
return so the entry diagnostic is emitted before checking for an empty sources
collection. Preserve the existing empty-result return, and ensure the empty
branch also emits a grep-friendly terminal trace before returning.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a833aeca-247b-4855-a2c5-56c6e5a93f0a
📒 Files selected for processing (1)
src/openhuman/memory_sources/status.rs
`source_status` derived `chunks_pending` from `mem_tree_chunks.embedding`, a vestigial column that is not in the canonical tinycortex schema and is only added at runtime by `add_column_if_missing`. No production writer ever populates it — embeddings live in the `mem_tree_chunk_embeddings` sidecar keyed by `(chunk_id, model_signature)` — so the column is NULL for every row and `chunks_pending` always equalled `chunks_synced`. Resolve a chunk when it has a vector under the active signature, has a re-embed tombstone for that signature, or was dropped by the admission gate, mirroring `list_sync_statuses` and `has_uncovered_reembed_work`. Refs tinyhumansai#5329
`migrate_legacy_embeddings_to_sidecar` preserves `mem_tree_chunks.embedding` after copying it into the sidecar, so a pre-sidecar vault still carries a usable signal there. Treat a non-NULL legacy blob as resolved alongside the sidecar/tombstone/dropped terms so vaults that predate the sidecar do not regress to "everything pending", and cover it with a dedicated test. Refs tinyhumansai#5329
The doc comments described `chunks_pending` as counting chunks whose `embedding IS NULL` and pointed at the pre-refactor `memory_sources/` path. Both are stale: the counter now resolves chunks against the `mem_tree_chunk_embeddings` sidecar under the active model signature. Comment-only; no behaviour change. Refs tinyhumansai#5329
… scan Re-authored onto the post-tinyhumansai#5328 tree (memory_sources → memory/sources) and rebased on the sidecar-based `chunks_pending` fix (tinyhumansai#5344, cherry-picked as the three preceding commits by @Mustaqeem66) so the batch and the per-source path share one pending definition. `status_list` (polled every 5s by the Memory Sources panel) previously ran one full `mem_tree_chunks` scan per source. It now folds every source's counts into `ceil(N / 128)` scans: one SELECT whose per-source columns are gated by `source_id LIKE ?n`, with the pending column embedding tinyhumansai#5344's exact resolved-predicate (active-signature sidecar vector / re-embed tombstone / dropped lifecycle / legacy blob) inside the gate's `THEN` branch. All reads share the one process-wide chunk-DB connection mutex, so fewer scans — not concurrency — is the only available lever. The nested `CASE` (rather than `LIKE ?n AND NOT(<pred>)`) is deliberate: it reproduces `source_status`'s NULL-predicate → pending behaviour exactly, and because SQL evaluates only the taken branch the `EXISTS` sub-selects run only for a row's own source. If the batched query ever fails, `status_list` falls back to the per-source path, so a query regression degrades latency, never correctness. Equivalence is pinned by a test asserting the batched result matches per-source `source_status` across all five resolution states. Claude-Session: https://claude.ai/code/session_01ACB4Ugi5pJMQqoCbZnVo6f
86338c0 to
55ffffe
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
|
Force-pushed — re-authored after the #5328 restructure. This branch was 687 commits behind; it's been reset onto current Stacked on #5344: the first three commits are @Mustaqeem66's sidecar- |
There was a problem hiding this comment.
Pull request overview
This PR reduces the cost of openhuman.memory_sources_status_list (polled every 5s by the Memory Sources panel) by batching per-source status computation so that multiple sources are aggregated in a single mem_tree_chunks scan, while preserving the existing per-source semantics (including the updated “pending” predicate from #5344) and falling back to the original per-source path on query failure.
Changes:
- Add a batched fast path that aggregates
(chunks_synced, chunks_pending, last_chunk_at_ms)for up to 128 sources per query, reducing scans from N per poll to ceil(N/128). - Preserve correctness by embedding the “resolved vs pending” predicate inside a nested
CASEsoNULLpredicate results still count as pending (matching the per-source logic). - Update frontend documentation/comments describing what
chunks_pendingrepresents.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/openhuman/memory/sources/status.rs |
Implements batched aggregation of per-source chunk status with a per-source fallback and adds comprehensive tests for equivalence and edge cases. |
app/src/components/intelligence/sourcePipelineStatus.ts |
Updates comments/documentation to reflect the new definition of chunks_pending based on sidecar embeddings and terminal states. |
Suppressed comments (1)
app/src/components/intelligence/sourcePipelineStatus.ts:85
- This inline comment says pending means “chunks with no vector for the active embedding signature”, but the core also treats tombstones/dropped chunks and legacy
embeddingblobs as resolved. Updating the wording will keep frontend docs aligned with the core semantics.
// Layer 1 — embeddings. Precise per-source signal (chunks with no vector for
// the active embedding signature) OR the global "semantic recall degraded"
// latch (no usable embeddings provider). Either means this source's chunks
// aren't vector-searchable.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| //! pre-sidecar `embedding` blob. This mirrors the resolution rule the | ||
| //! provider-level sibling (`tinycortex::memory::sync::list_sync_statuses`) and | ||
| //! `has_uncovered_reembed_work` already use, so a settled store reports zero | ||
| //! instead of reporting every ingested chunk as pending forever. |
| * of this source's chunks that have no vector in the | ||
| * `mem_tree_chunk_embeddings` sidecar under the active model signature, and | ||
| * no terminal marker (re-embed tombstone / dropped) explaining the absence | ||
| * (see `memory/sources/status.rs`). `> 0` in a settled state means those | ||
| * chunks were stored WITHOUT vectors → semantic search can't reach them. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/openhuman/memory/sources/status.rs (1)
179-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd diagnostics for successful fallback scans.
The warning at Lines 166-172 records the batch failure. It does not show that the fallback started or which per-source scans completed successfully. Add a debug event at fallback entry and a trace event before each
source_statuscall. Include safe correlation fields. Do not log configuration values or chunk content.Proposed diagnostic events
async fn status_list_per_source( config: &Config, sources: &[MemorySourceEntry], ) -> Result<Vec<SourceStatus>, String> { + tracing::debug!( + source_count = sources.len(), + "[memory_sources:status] status_list: per-source fallback started" + ); let mut out = Vec::with_capacity(sources.len()); for source in sources { + tracing::trace!( + source_id = %source.id, + "[memory_sources:status] status_list: fallback source scan" + ); match source_status(config, source).await {As per coding guidelines, “Add verbose, grep-friendly Rust diagnostics for new or changed flows, including branches, external calls, retries, state transitions, and errors; never log secrets or full PII.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/memory/sources/status.rs` around lines 179 - 185, Add a debug diagnostic at the fallback entry before iterating in status_list_per_source, then add a trace diagnostic immediately before each source_status call. Include only safe correlation fields such as source identity or index and batch/request context; do not log configuration values or chunk contents.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/openhuman/memory/sources/status.rs`:
- Around line 179-185: Add a debug diagnostic at the fallback entry before
iterating in status_list_per_source, then add a trace diagnostic immediately
before each source_status call. Include only safe correlation fields such as
source identity or index and batch/request context; do not log configuration
values or chunk contents.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e571da40-3b80-4a37-b053-f7e67e3d48e6
📒 Files selected for processing (2)
app/src/components/intelligence/sourcePipelineStatus.tssrc/openhuman/memory/sources/status.rs
|
Superseded by the memory-subsystem extraction on |
What
memory_sources::status::status_listpowers the Memory Sources panel, whichpolls
openhuman.memory_sources_status_listevery 5s (MemorySourcesRegistry.tsx).It ran one full
mem_tree_chunksscan per source — N sources ⇒ N scans perpoll — and
mem_tree_chunksis indexed on(source_kind, source_id), which asource_id LIKE 'prefix%'predicate can't use, so each is a full table scan.Change
Fold every source's counts into
ceil(N / 128)scans. One SELECT emits threeaggregate columns per source, each gated by
source_id LIKE ?n:chunks_synced—COUNTof the source's chunkschunks_pending— the source's chunks not resolved for the activeembedding signature, using fix(memory-sources): count chunks_pending from the embedding sidecar, not a dead column #5344's exact predicate (sidecar vector under the
active signature / re-embed tombstone / dropped lifecycle / legacy blob)
last_chunk_at_ms—MAX(timestamp_ms)128 sources/batch keeps the statement under SQLite's 2000-column / 999-param caps
(128×3 = 384 cols, 130 binds). All reads share the one process-wide chunk-DB
connection mutex, so fewer scans, not concurrency, is the only lever (batching
also shortens how long the lock is held).
Why the nested
CASEThe pending column is
CASE WHEN source_id LIKE ?n THEN (CASE WHEN <resolved> THEN 0 ELSE 1 END) ELSE 0 END,not a flat
LIKE ?n AND NOT(<resolved>), for two reasons:source_statuscounts a row pending whenever the predicateis false or NULL (
CASE WHEN <pred> THEN 0 ELSE 1). A bareNOT(<pred>)maps NULL → NULL → not-counted, silently under-counting NULL-
lifecycle_statusrows. Nesting the identical inner
CASEreproduces the NULL-→-pendingbehaviour exactly.
CASEbranch, so theEXISTSsub-selects run only for a row's own source — never once per source per row —
without relying on
ANDshort-circuiting.Correctness / fallback
If the batched query fails for any reason,
status_listfalls back to theoriginal per-source path (
status_list_per_source), so a query regressiondegrades latency, never correctness.
Tests
batch_status_matches_per_source_counts— asserts the batched result equalsper-source
source_statusfield-for-field across all five resolution states(active-signature embed, superseded-signature embed, re-embed tombstone,
dropped lifecycle, legacy blob) plus genuinely-pending and empty, and pins the
absolute counts so a bug corrupting both paths identically still fails.
batch_status_spans_multiple_query_batches— 133 sources, verifies theindex→source mapping across a batch boundary.
batch_status_empty_db_is_zeroed_not_errored— theSUM/MAXNULLs of azero-row scan decode to 0 / None.
Verified locally:
cargo test --lib --features memory-git memory::sources::status— green.Note — pushed over pre-existing
mainbreakagemaincurrently fails any--no-default-featurescompile (memory-gitisdefault-OFF) because of a
memory/diffcfg mismatch unrelated to this PR:So the pre-push
pnpm rust:check(shell build, gates off) fails onmain's bug,and this push used
--no-verify. The sameRust Feature-Gate Smoke/Rust Core Coveragelanes are red on other current-mainPRs (e.g. #5486) and green on apre-regression branch (#5344's base).
Rust Quality (fmt, clippy)compiles thischange clean; the feature-flagged local test above covers it.
Summary by CodeRabbit