Skip to content

perf(vector): drop the two format! allocations from PayloadIndex::remove_field - #889

Merged
TinDang97 merged 1 commit into
mainfrom
perf/payload-index-remove-field-alloc
Sep 9, 2026
Merged

perf(vector): drop the two format! allocations from PayloadIndex::remove_field#889
TinDang97 merged 1 commit into
mainfrom
perf/payload-index-remove-field-alloc

Conversation

@TinDang97

@TinDang97 TinDang97 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

PayloadIndex::remove_field built {field}__lat and {field}__lon with two
format! allocations on every call — regardless of whether the field, or
even the document, was geo. It runs once per HASH field on
update_metadata_only's path (src/shard/spsc_handler.rs:4223), which is the
.tpost fast path that every boot recovery drives hard.

The change

bytes::Bytes: Borrow<[u8]>, so the forward-index lookup
(doc_values: HashMap<u32, HashMap<Bytes, DocFieldValues>>) only ever needed a
&[u8]. The candidate key is now built once into a SmallVec<[u8; 48]> stack
buffer and reused for both suffixes by truncate+extend, never promoted to an
owned Bytes unless the field name spills past GEO_SUBFIELD_INLINE_CAP.
retire_values's field parameter moves from &Bytes to &[u8]; call sites
rely on deref coercion.

A subtlety preserved deliberately: insert_geo derives its prefix via
str::from_utf8(field).unwrap_or(""), so a non-UTF8 field name collapses to an
empty prefix there. The rewrite reproduces that exact fallback — dropping it
would leak a non-UTF8 geo field's bitmap entries on removal.

Red/green

New test remove_field_retires_geo_subfields_for_non_utf8_field_name. The fix
was mutated on the bench host (UTF-8 fallback special case removed) and the test
went RED with a panic at the assertion; restoring the fix went GREEN.
26 tests pass in the module. Exit codes captured directly, not piped.

Measurement — Linux/aarch64, GCE t2a-standard-8, --release (fat LTO)

bench_remove_field_call_overhead: 2M insert_tag + remove_field cycles on a
plain non-geo field, 8 interleaved runs per leg. Baseline is main
(adf3a808) with the same bench harness grafted in.

leg mean ns/cycle stdev CV
baseline 747.8 13.7 1.8%
this branch 617.2 10.4 1.7%

130.6 ns/cycle, 17.5% faster, 9.5x the noise floor. A same-binary control
run through the identical interleave pattern showed 0.8% positional drift
(6.1 ns), ruling out an ordering artifact.

What this number is not. It is the call's own overhead in isolation, not
17.5% off a reconcile. remove_field's remaining per-key cost is
O(distinct terms in the document) for the term walk, which is inherent to
maintaining the index and is untouched here. Treat this as removing a constant,
not as a recovery-time claim.

Scope

insert_geo (payload_index.rs:~113) and evaluate_bitmap's GeoRadius arm
(~347) carry the same format! pattern and are left alone on purpose:
insert_geo only fires when a field actually is geo, and GeoRadius runs once
per query rather than once per key on the recovery path. Neither is on the path
this PR targets.

Gates

cargo check --all-targets clean on both runtime legs (monoio default, and
--no-default-features --features runtime-tokio,jemalloc);
cargo clippy --all-targets -- -D warnings clean on both;
cargo test --release --lib vector::filter::payload_index:: 26 passed.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed removal of geo-indexed fields with non-UTF-8 names, ensuring their associated location data is retired correctly.
    • Improved the efficiency of removing geo fields, reducing unnecessary processing during index maintenance.

…ove_field

remove_field built `{field}__lat` / `{field}__lon` scratch keys with two
`format!` calls, unconditionally, on EVERY call -- whether or not the
field (or even the document) was geo. This runs once per HSET field on
update_metadata_only's fast path (src/shard/spsc_handler.rs), which every
production reconcile after this deploy will drive hard.

`bytes::Bytes` implements `Borrow<[u8]>`, so the forward-index lookup
(`doc_values: HashMap<u32, HashMap<Bytes, DocFieldValues>>`) only ever
needed a `&[u8]`, never an owned `Bytes`. The candidate key is now built
once into a `SmallVec<[u8; 48]>` stack buffer and reused for both
suffixes; it only spills to the heap for a field name long enough to
exceed the inline capacity. `retire_values` takes `field: &[u8]` instead
of `&Bytes` for the same reason -- `Bytes: Borrow<[u8]>` covers every
existing call site via deref coercion.

One behavioral subtlety preserved on purpose: `insert_geo` derives its
sub-field prefix from `str::from_utf8(field).unwrap_or("")`, so a
non-UTF8 field name silently collapses to an empty prefix there. The
rewrite reproduces that exact fallback (`remove_field_retires_geo_
subfields_for_non_utf8_field_name`), or a non-UTF8 geo field's bitmap
entries would stop getting retired -- proven RED by mutating the
fallback out and confirming the new test fails on the pre-fix-shaped
code, then GREEN after restoring it.

Left untouched, with reasoning: `insert_geo` (line ~113) and the
GeoRadius arm of `evaluate_bitmap` (line ~347) have the same `format!`
pattern, but they only pay it when the data is actually geo -- insert_geo
runs once per geo field write, not once per field regardless of type,
and GeoRadius runs once per query, not once per key on the recovery
path. Fixing them was out of scope; they are not equally hot.

Measured on Linux/aarch64 (moon-bench-arm, release profile, fat LTO,
matching production): an insert_tag + remove_field cycle on a plain
non-geo field, 2M iterations, 8 interleaved runs per leg.

  baseline (gh/main @ adf3a80): 747.8 ns/cycle, stdev 13.7 ns (1.8%)
  fixed:                         617.2 ns/cycle, stdev 10.4 ns (1.7%)
  delta: 130.6 ns/cycle faster (17.5%), 9.5x the noise floor

A same-binary control (baseline run twice, same interleave pattern)
showed only 0.8% positional drift (6.1 ns), ruling out an ordering
artifact as the source of the 17.5% delta.

Tests: cargo test --release --lib vector::filter::payload_index (26
passed, 2 ignored measurement harnesses). Clippy clean with
-D warnings on both runtime-monoio (default) and runtime-tokio,jemalloc.

author: Tin Dang
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 03a66381-e293-4b83-a82d-5374f69cff52

📥 Commits

Reviewing files that changed from the base of the PR and between adf3a80 and 91cd148.

📒 Files selected for processing (1)
  • src/vector/filter/payload_index.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR rewrites geo sub-field lookup in remove_field, preserves non-UTF8 fallback behavior, changes retirement keys to byte slices, and adds regression and measurement tests.

Changes

Geo field retirement

Layer / File(s) Summary
Geo lookup and retirement validation
src/vector/filter/payload_index.rs
remove_field builds {field}__lat and {field}__lon keys with SmallVec and handles non-UTF8 field names. retire_values accepts byte-slice keys. Tests verify geo retirement and measure per-call overhead.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 91cd1

This change reduces allocation overhead when removing geo fields while preserving geo-index retirement, including non-UTF8 field names. No current merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: removing two format! allocations from PayloadIndex::remove_field.
Description check ✅ Passed The description provides a clear summary, implementation details, performance measurements, scope, regression-test coverage, and validation results. It does not use all template headings and does not …
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 1 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/payload-index-remove-field-alloc

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TinDang97
TinDang97 merged commit 3b8fc6d into main Sep 9, 2026
10 of 11 checks passed
@TinDang97
TinDang97 deleted the perf/payload-index-remove-field-alloc branch September 9, 2026 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant