Repository navigation
Consume the placed source items through one iterator, not a front drain - #7476
mysma-9403 wants to merge 1 commit into
Conversation
…ont drain Raised in review: `placed.drain(..take)` removes from the front of a `Vec`, so every chunk shifts all still-pending entries left — O(N^2 / STORE_CHUNK) of copying across a sync, paid precisely on the large syncs this batching exists to speed up, which makes it worse than a wash on the case that matters. `into_iter()` with `by_ref().take(STORE_CHUNK)` moves each entry exactly once. The chunk still has to be owned, because the item-by-item retry needs the items a rejected batch does not hand back, so `chunks()` over a slice would not avoid the allocation — only the repeated memmove, which is the part that scaled. The comment stays next to it: `drain(..n)` reads as the obvious way to write this, so the reason it is not needs to be visible.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Tiny Sweeper reviewThis pull request is a single-purpose internal performance refactor in `pub(crate) async fn store_all(`: the per-chunk front drain of `placed` is replaced by consuming the same vector through one iterator in `STORE_CHUNK`-sized batches. Every review lane (critique, security, tests, commits, description) reported no findings. The critique lane verified boundary behaviour (batches of exactly `STORE_CHUNK` while items remain, an empty batch once exhausted, and the `chunk.is_empty()` break covering empty input), and confirmed the chunk is moved into the `touched` loop or the per-item retry loop without re-reading `placed`, so nothing is skipped or double-counted and the early return on account-wide errors preserves pre-existing intent. Security found no new input paths, sinks, or permission changes; commits found nothing sensitive. The description lane states the PR body's problem statement, solution, and no-behaviour-change claim match the change. No findings were raised, softened, or deprioritized. Each lane noted that the code index is behind this pull request (indexed at `98e870e9bdad`), so retrieved context may be out of date. State: Ready for maintainer review Review snapshot
Completeness: Complete What changed`store_all` no longer drains the front of `placed` in a `while !placed.is_empty()` loop. Instead it converts `placed` into a consuming iterator and loops, taking up to `STORE_CHUNK` items per iteration and breaking when the collected chunk is empty. This changes only how pending items are pulled: chunk size, chunk order, the number of batches, the per-chunk clone for the item-by-item retry path, and the accounting of successful versus rejected items are all unchanged. The stated motivation is that draining the front of a `Vec` shifts every still-pending entry left, costing copying work proportional to the pending backlog across the sync, which is exactly the workload the batching is meant to accelerate. Reviewer lanes describe this as a pure performance refactor preserving the same chunk size, retry, and accounting semantics. Features
TestsNo supported feature-to-test mapping was produced. Test execution is not inferred. FindingsNo active actionable findings. Before mergeNone. How this fits togetherflowchart LR
n0["store_all"]:::impacted
n1["sync_one"]:::impacted
n2["start_sync"]:::impacted
n3["reader_entry"]:::impacted
n4["sync_due"]:::impacted
n5["...ling_sync_lands_in_error_with_the_message"]:::impacted
n1 -->|calls| n0
n1 -->|calls| n3
n4 -->|calls| n2
n5 -->|calls| n2
n5 -->|tests| n2
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
There was a problem hiding this comment.
tinysweeper found nothing blocking. Approving.
$0.0012 · 98,900 in / 5,625 out · 9,472 cached (10%) · ladder/vectors-oai3, deepseek-v4.1-flash, glm-5.3-flash · 78 embedded
critique: $0.0004 · 30,151 in / 489 out · 4,096 cached (14%) · deepseek-v4.1-flash
security: $0.0000 · 30,315 in / 346 out · 3,584 cached (12%) · deepseek-v4.1-flash
tests: $0.0000 · 13,344 in / 171 out · 1,792 cached (13%) · deepseek-v4.1-flash
description: $0.0006 · 23,241 in / 2,872 out · 0 cached (0%) · deepseek-v4.1-flash, glm-5.3-flash
Summary
store_alldrains the front of aVecper chunk, which is O(N² / STORE_CHUNK) of copying over a sync.Problem
#7253 added batching with:
Vec::drain(..n)removes from the front, so every chunk shifts all still-pending entries left. Over a sync of N items that is O(N² / STORE_CHUNK) ofmemmove, paid on the tokio worker — and paid precisely on the large syncs the batching exists to speed up, which makes it worst where the change was supposed to help most. Raised by Codex review on #7253 (P2); the PR merged before the fix was pushed, so it is here.Solution
Each entry is moved once, into its chunk.
chunks()over a slice would avoid the per-chunkVecas well, but it cannot be used here: a rejected batch hands nothing back, so the item-by-item retry needs ownedStoreItems and the chunk has to be owned either way. What this removes is the repeated memmove, which is the part that scaled with N.The comment stays next to it, because
drain(..n)reads as the obvious way to write this loop and the next person needs to know why it is not.Submission Checklist
store_all_spans_chunk_boundaries_without_losing_items(250 items over three chunks, plus a bad item in the middle chunk) already pins exactly what a mistake here would break — a dropped, duplicated or misordered entry.store_all_skips_a_bad_item_but_fails_when_nothing_storedcovers the failure path unchanged.store_alltest;cargo test -p openhuman --lib memory::sources::→ 26 passed, 0 failed.## Related— N/A: as above.Closes #NNNin the## Relatedsection — N/A: follow-up to merged Store memory source items in batches, as backfill already does #7253 (its origin issue Memory source sync stores one item per request while backfill already batches #7252 is closed); filing a fresh issue for a ten-line loop rewrite would be noise. Referenced below instead.Impact
Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
perf/sources-store-single-passgit log upstream/main..HEADValidation Run
pnpm --filter openhuman-app format:check—cargo fmtapplied; no frontend file changed.pnpm typecheck— N/A: no TypeScript changed.cargo test -p openhuman --lib memory::sources::(26 passed, 0 failed).cargo fmt,GGML_NATIVE=OFF cargo check -p openhuman,cargo clippy -p openhuman -- -D warnings(exit 0).crates/openhuman-app/not touched.Validation Blocked
command:pnpm rust:clippy, the pre-push hook's clippy gate, so this branch was pushed with--no-verify.error:clippy::result_large_erratcrates/openhuman-rpc/src/server/saas_gateway.rs:102.impact:pre-existing onmainand unrelated — this PR changes one loop incrates/openhuman-core/src/memory/sources/sync.rs. Not a toolchain difference:rust-toolchain.tomlpins1.96.1and the run usedrustc 1.96.1/clippy 0.1.96. Clippy over the package this touches is clean.Behavior Changes
Parity Contract
take(STORE_CHUNK)yields the same chunks in the same order asdrain(..take), and the empty-iterator break replaces the empty-vector condition, so an empty input still enters no batch. Everything inside the loop is untouched.is_account_wide()early return and thetouched/stored/failedaccounting are unchanged, and the chunk-boundary test exercises all of them across three chunks.Duplicate / Superseded PR Handling
Summary by CodeRabbit