Commit Graph
100 Commits
Author SHA1 Message Date
qdrant-cloud-bot 081ada945f Fix flaky shard snapshot API CI readiness race (#10383)
* Fix flaky shard snapshot API CI readiness race

Run the prebuilt binary and poll /readyz instead of cargo run + fixed sleep, which can miss startup when cargo recompiles.

* Move shard snapshot API CI runner into a dedicated script

Keep workflow YAML thin by starting Qdrant, waiting for /readyz, and invoking shard-snapshot-api.sh from tests/shard-snapshot-api-tests.sh.
2026-09-03 12:45:52 +02:00
qdrant-cloud-bot 1d98640411 fix: stabilize segment builder cancellation test on Windows CI (#10346)
Add stop checks during the pre-HNSW build setup phase so cancellation
is observed promptly, and widen timing tolerance for noisy Windows debug
builds where post-stop delays can exceed 1s.
2026-09-03 12:45:50 +02:00
qdrant-cloud-bot 23ca62115c test: wait for receiver Active after snapshot transfer (#10330)
Finish can apply on peer 0 before the destination peer, so asserting
Active on peer 2 right after peer 0 reports all-Active was flaky.
2026-09-03 12:45:49 +02:00
qdrant-cloud-botanddependabot[bot] 2e8d618123 build(deps): bump serde_with to 3.22.0 without default features (#10329)
* build(deps): bump serde_with from 3.21.0 to 3.22.0

Bumps [serde_with](https://github.com/jonasbb/serde_with) from 3.21.0 to 3.22.0.
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.21.0...v3.22.0)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-version: 3.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): disable serde_with default features

Avoid pulling jiff timezone DB features via serde_with's std weak
dependencies; we only need macros and alloc.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 12:42:28 +02:00
qdrant-cloud-bot da78e8f5c4 test: de-flake test_recover_from_snapshot shard placement assertion (#10310)
Mirror the fix from #9938 for test_upload_snapshot: assert every shard
has n_replicas active replicas across local+remote shards instead of
assuming peer 0 always sees exactly 2*n_replicas remote shards.
2026-09-03 12:42:27 +02:00
qdrant-cloud-bot 0479d359c2 fix(grpc): raise http2 max pending accept reset streams on public API (#10306)
Mirror the internal gRPC server setting to avoid GOAWAY/ENHANCE_YOUR_CALM
errors when clients multiplex many short-lived streams (e.g. coach
high_concurrency drill). See #1907.
2026-09-03 12:42:26 +02:00
qdrant-cloud-bot 1cf17c827c fix(collection): stabilize hw metrics cancellation test on fast CI (#10294)
Use a warmup baseline and adaptive timeout so the search is cancelled
mid-flight on both fast macos ARM runners and overloaded hosts, instead
of relying on a fixed 350ms cutoff.
2026-09-03 12:42:25 +02:00
qdrant-cloud-bot 2d576ca7c2 test(segment): skip more heavy tests on Windows CI (#10278)
Extend the Windows-only ignore pattern to the remaining long-pole
unit/integration tests that dominate the Windows nextest phase:

- custom_query_scorer_equivalency::compare_scoring_equivalency
  (~130s from product_x4 cases alone; rstest via test_attr)
- test_appendable_multi_turbo_vector_storage::{congruent_upsert_read_all_distances,
  congruent_random_ops_{dot,cosine}} (~80s)
- test_appendable_turbo_vector_storage::{upsert_flush_reload_in_ram_matches_independent_oracle,
  turbo_model_test_random_ops_{dot,cosine}} (~55s)
- scroll_filtering_test::test_filtering_context_consistency (~28s)

None of these exercise Windows-specific behavior; Linux/macOS keep
full coverage. Roughly ~300s of Windows test time removed on top of
the multivector rstest fix.
2026-09-03 12:42:24 +02:00
qdrant-cloud-bot cbb70cfb69 test(segment): fix rstest ignore attr on Windows for multivector tests (#10276)
`multivector_filtrable_hnsw_test::test_multi_filterable_hnsw` and
`multivector_quantization_test::test_multivector_quantization_hnsw`
were meant to be ignored on Windows, but the

    #[cfg_attr(target_os = "windows", ignore = "...")]
    #[rstest]

pattern places the attribute BEFORE `#[rstest]`, so it never reaches
the per-case functions rstest generates — the cases were still running
on Windows CI (visible in the streaming test log).

Switch to the pattern that already works for
`byte_storage_quantization_test.rs`:

    #[rstest]
    #[cfg_attr(target_os = "windows",
        test_attr(ignore = "..."))]

which uses rstest's `test_attr(...)` forwarding, so the `#[ignore]` is
applied to each generated per-case test.

Removes ~160 s of Windows test time (multivector_filtrable_hnsw ~100 s,
multivector_quantization ~64 s), which with ~3.6× test parallelism
should trim the Windows job wall-clock by ~45 s. Coverage on
Linux/macOS is unchanged.
2026-09-03 12:42:24 +02:00
qdrant-cloud-bot 60650c985a test(model_testing): run unit tests on Linux only (#10271)
These harness smoke tests are too slow for macOS CI; Windows was already
skipped for the same reason.
2026-09-03 12:42:23 +02:00
qdrant-cloud-bot 690347df5d Expose cpu_cores_used in Prometheus /metrics (#10243)
Surface the existing process CPU usage from telemetry as a gauge so
operators can scrape average cores used without polling /telemetry.
2026-09-03 12:41:02 +02:00
qdrant-cloud-bot 1514cc128f ci: include edge-shard-update in debug-tools upload (#10227)
Build and publish the edge-shard-update binary alongside edge-shard-query
to the qdrant-debug GCS bucket.
2026-09-03 12:41:01 +02:00
qdrant-cloud-bot da6751cd64 build(deps): bump qdrant-client from 1.18.0 to 1.19.0 in /tests (#10164)
Raise the minimum test dependency to qdrant-client 1.19.0 and refresh uv.lock.
2026-09-03 12:36:34 +02:00
qdrant-cloud-bot 33ed96135b test: harden flaky consensus rejoin and JWT snapshot upload checks (#10158)
Tolerate not-yet-ready collection upserts in test_rejoin_cluster and give
JWT snapshot uploads more headroom while still bounding auth-rejection hangs.
2026-09-03 12:36:34 +02:00
qdrant-cloud-botanddependabot[bot] 52810cc644 build(deps): bump h2 from 4.3.0 to 4.4.1 in /tests (#10157)
Bumps [h2](https://github.com/python-hyper/h2) from 4.3.0 to 4.4.1.
- [Changelog](https://github.com/python-hyper/h2/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-hyper/h2/compare/v4.3.0...v4.4.1)

---
updated-dependencies:
- dependency-name: h2
  dependency-version: 4.4.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 12:36:33 +02:00
qdrant-cloud-bot e88527492f fix: stop underestimating is_empty / not-null cardinality by 1/3 (re-introduce #10128) (#10141)
* fix: stop underestimating is_empty / not-null cardinality by 1/3

Re-introduce #10128 after its revert so CI can exercise
payload_index_test::test_read_operations / test_is_empty_conditions.

* test: stop requiring is_empty struct exp to beat plain

NullIndex complement estimates use an indexed upper bound (may include
soft-deletes); that is not guaranteed to be closer to truth than plain's
available/2 guess. Assert upper-bound semantics instead.

* test: drop is_empty exp==max assertion

That locked in NullIndex implementation detail. Keep result parity and
min/max bounds only; document why exp-vs-plain is not checked.
2026-09-03 12:36:33 +02:00
qdrant-cloud-bot b6e508a560 Revert "fix: stop underestimating is_empty / not-null cardinality by 1/3 (#10128)" (#10140)
This reverts commit 50fe2e8139.
2026-09-03 12:36:32 +02:00
qdrant-cloud-bot a89d435411 fix: stop underestimating is_empty / not-null cardinality by 1/3 (#10128)
NullIndex used an arbitrary `exp = 2/3 * estimated` heuristic for
complement conditions (`is_empty=true`, `is_null=false`), which caused
steady-state approximate counts to under-report by ~35% even with no
deletes (see #10120). Use the indexed upper bound as the expected count
instead.
2026-09-03 12:36:32 +02:00
qdrant-cloud-bot 78d1e8a218 Remove flaky consensus_lag consensus test (#10133)
The profiler consensus lag integration test has been flaky in CI; drop it while keeping the feature.
2026-09-03 12:36:32 +02:00
6b76d4e714 feat(edge): add query_batch for batched planned queries (#10100)
* feat(edge): add query_batch for batched planned queries

Expose the planned-query batch path as a public API so multiple
independent queries can share one planning pass over leaf searches
and scrolls. Wired through EdgeShardRead, FFI, and Python bindings.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(edge): push batched query vectors down to segments

`query_batch` planned the whole batch at once but then executed every leaf
search on its own: one query context, one fan-out over all segments, and one
single-vector `Segment::search_batch` call per leaf.

Execute the batch as a batch instead:

- `EdgeReadView::search_batch` builds the query context once, visits the
  segments once, and hands each segment the leaves that agree on everything
  but their query vector as a single multi-vector `search_batch` call.
  `search` is now a thin wrapper over a one-element batch.
- Move `SearchType`/`BatchSearchParams` from `collection`'s segments searcher
  into `shard`, next to `CoreSearchRequest`, and add `group_search_batches`
  so both the collection and the edge read path share one grouping
  implementation. Edge computes the grouping once and reuses it per segment.
- `search_matrix` now issues its per-sample nearest queries through
  `query_batch`; they share filter, limit and vector name, so the whole
  sample is scored in one batched search per segment instead of one full
  segment pass per sampled point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 12:36:30 +02:00
qdrant-cloud-botanddependabot[bot] 1eaa42947b build(deps): bump cryptography from 48.0.1 to 50.0.0 in /tests (#10083)
Bumps [cryptography](https://github.com/pyca/cryptography) from 48.0.1 to 50.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.1...50.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 12:06:39 +02:00
qdrant-cloud-botandCursor d5d7011cab build(deps): bump quinn-proto from 0.11.14 to 0.11.16 (#10009)
Same dependency bump as #10006, applied on top of dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:44 +02:00
qdrant-cloud-botandCursor 127cb23bf0 build(deps): bump pyasn1 from 0.6.3 to 0.6.4 in /tests (#9976)
Security release addressing CVE-2026-59884, CVE-2026-59885, and CVE-2026-59886.
Equivalent to #9973 for the dev branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:43 +02:00
qdrant-cloud-botandCursor 34b58b45a3 test: wait for raft leader before collection recovery (#9972)
POST /cluster/recover can return 200 while raft silently drops the
snapshot request when no leader is known yet, leaving the test stuck
on a missing collection until timeout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:43 +02:00
qdrant-cloud-botandCursor d15819a815 test: fix flaky test_partial_snapshot optimizer race (#9951)
Wait for green on write (and read after recover_read) so collection and
partial snapshots are not taken mid-indexing. Otherwise a leftover
appendable segment survives partial merge and breaks manifest equality.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:42 +02:00
qdrant-cloud-botandCursor c6d86fdf1a test: tolerate transfer race in corrupted snapshot recovery (#9947)
#9013 skipped the manual replicate_shard when a transfer was already
visible, but the recovery loop can still start one between that check
and the POST. Accept 400 "already involved in transfer" as success so
the remaining wait assertions still cover recovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:42 +02:00
qdrant-cloud-botandCursor d2946b8508 test: harden test_routing_token_sticky_reads against post-recovery flakiness (#9937)
Right after the no_sync snapshot recovery, the recovered replica serves local
reads immediately, but a remote read to it can transiently fail for a short
window. The read path then falls back to the other replica in hash order on
just the requesting peer, so a single routing token momentarily resolves to
different replicas across peers (observed as {A, B, B}), failing the
determinism assertion.

Wait until token-routed reads are stable across all peers for every token the
test asserts on before measuring, so the transient post-recovery fallback
window is passed. Pure test-side change; routing behaviour is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:42 +02:00
qdrant-cloud-botandCursor 0477976f7d test: de-flake test_upload_snapshot (robust to shard placement) (#9938)
* test: make test_upload_snapshot robust to shard placement balance

The final assertion in recover_from_uploaded_snapshot assumed a perfectly
balanced shard placement (peer 0 having exactly 2*n_replicas remote shards).
Shard placement across peers is not guaranteed to be balanced, so this made
the test flaky (e.g. peer 0 ended up hosting all shards locally, leaving only
3 remote replicas instead of 4).

Instead, verify the full replica layout is healthy: peer 0 observes every
replica through its local + remote shards, so assert that all replicas are
Active and every shard has exactly n_replicas copies across the cluster.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: fetch cluster info once for shard validation

Read local and remote shards from a single /cluster response so both lists
come from the same cluster revision, per review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:42 +02:00
qdrant-cloud-botandCursor a894cd4b3f test(model_testing): cover slice filter in scroll, count, and delete-by-filter (#9905)
* test(model_testing): add slice matcher to generated scroll filter

Extend ScrollFilter with a Slice variant so paginated scroll exercises
Condition::Slice. The generator draws small totals (1/2/3/4/5/8) and a
valid index; the model verifier mirrors membership via Slice::check —
the same hash contract the engine uses — so the existing paged-scroll
id-set assertion covers sliced scroll under soak (optimizer, WAL reload,
multi-shard, mixed UUID/numeric ids).

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(model_testing): add CountBySlice verification op

Exercise Condition::Slice through the exact count API under soak.
Shares the slice generator with ScrollPaged; the model oracle uses
Slice::check so engine and in-memory counts must agree.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(model_testing): compose slice with num on scroll and delete-by-filter

- ScrollFilter::NumAndSlice: indexed num drives candidates; slice is a
  per-candidate check via Filter::merge.
- DeleteByFilter { num, slice: Option<Slice> }: half the deletes also
  restrict by slice so submit-time filter resolution and WAL-replayed
  id lists exercise Condition::Slice.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:18:41 +02:00
qdrant-cloud-botandCursor aac632396a build(deps): bump serde_with from 3.20.0 to 3.21.0 (#9865)
Align the workspace dependency pin with the already-resolved
Cargo.lock version (includes GHSA-7gcf-g7xr-8hxj fix).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:03 +02:00
qdrant-cloud-botandCursor ba7eb719f5 Ignore slow Windows CI tests for turbo multi model and quantization (#9850)
Skip the three slowest Windows rust-tests (>60s each): turbo multi
random-ops model tests and the turbo Manhattan HNSW quantization case.
Use rstest test_attr for the parametrized case since a top-level ignore
does not apply to all generated tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:03 +02:00
qdrant-cloud-botandCursor 88870663a9 Fix flaky WAL replay tests by waiting for update worker idle (#9832)
An empty update channel only means the last operation was received,
not that it finished applying in spawn_blocking. Use plunge_async as
a barrier before asserting point counts.

Fixes #9831

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:02 +02:00
qdrant-cloud-botandCursor 39bb9e07ef Fix flaky test_wait_deferred_does_not_block_update_worker (#9816)
Drain the update worker queue after setup upserts with WaitUntil::Wal.
Wal only waits for the WAL write, so on slow CI (notably Windows) B could
time out while queued behind the setup backlog rather than because the
worker was blocked on A's deferred wait.

Fixes #9814

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:02 +02:00
qdrant-cloud-botandCursor 30c92118c4 Fix edge shard exact count double-counting across segments (#9790)
Collect per-segment point ids into a BTreeSet before counting so points
visible in multiple segments (e.g. during proxy/merge optimization) are
counted once, matching the canonical collection count path.

Fixes #9789

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:00 +02:00
qdrant-cloud-botandCursor d40b3ad0d8 test(consensus): de-flake replace-peer-same-uri tests (#9758)
Wait for the collection metadata to propagate to the newly added extra
peer before querying its collection cluster info. Being online and
present in consensus does not guarantee the peer has already applied the
collection-creation Raft entry locally, so get_collection_cluster_info
could race and return 404.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:17:00 +02:00
qdrant-cloud-bot 557b630e1f Allow snapshot recovery of streamable segments without nested files/ dir (#9732)
Streamable snapshots wrap each segment's files in a nested `files/`
directory. Make recovery tolerate segments where files are placed directly
in the segment directory by only hoisting `files/` when it is present.

Snapshot creation is unchanged; this only relaxes recovery to accept both
layouts (with and without the `files/` wrapper).
2026-08-04 11:16:59 +02:00
qdrant-cloud-botandCursor b89df9bf54 Add timeouts to shard snapshot S3 CI tests to fail fast on hangs. (#9693)
The recover-remote-concurrent step can hang indefinitely with S3 storage;
cap CI steps at 15 minutes and curl requests at 120 seconds so stuck runs
do not consume the full 6-hour job timeout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:57 +02:00
qdrant-cloud-botandCursor 9a263a19f6 model_testing: add prefix keyword index coverage (#9694)
* Add prefix keyword index coverage to model_testing soak harness.

Exercise prefix filters on a new `url` payload field through count/scroll,
paginated scroll, facet, search, and query ops, with CreateIndex/DropIndex
toggling a prefix-enabled keyword index to stress index lifecycle and fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Apply rustfmt to model_testing prefix index changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address review: preserve keyword-equality coverage, reuse Filter::merge_opts.

- Arms 8/9 now randomly toggle either the plain keyword index on `tag`
  (exact-match path) or the prefix-enabled keyword index on `url`, keeping
  coverage of both index paths.
- optional_read_filter now delegates to Filter::merge_opts over the existing
  match_num_filter / match_url_prefix_filter constructors instead of
  hand-building conditions.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:57 +02:00
9edf5408dc Fix optimizer recreation dropping sibling shard restarts on error (#9671)
* Fix optimizer recreation dropping sibling shard restarts on error

Replace try_join_all with join_all in recreate_optimizers so a failing
shard does not cancel non-cancel-safe on_optimizer_config_update futures
that have already stopped their update workers.

Fixes #9670

Co-authored-by: Cursor <cursoragent@cursor.com>

* Apply cargo fmt

Co-authored-by: Cursor <cursoragent@cursor.com>

* Apply suggestion from @timvisee

Co-authored-by: Tim Visée <tim+github@visee.me>

* Move optimizer config update tests into dedicated file

Extract worker restart test hooks and unit tests from updaters.rs into
optimizer_config_update_tests.rs, following the snapshot_tests pattern.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Tim Visée <tim+github@visee.me>
2026-08-04 11:16:57 +02:00
qdrant-cloud-botandCursor 96db4741af Use join_all for multi-shard strict mode rate limiter updates (#9672)
Replace try_join_all so a failing shard does not cancel sibling
on_strict_mode_config_update futures before they apply rate limiters.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:57 +02:00
qdrant-cloud-botandCursor 3dc0fcf4f1 Trim harness_no_restarts op count to stay under CI slow-test threshold. (#9658)
Optimizer-on runs without restarts were the slowest no-restart cell (~98s on
ubuntu CI). Halving OP_NUM matches the existing harness() trimming pattern;
coverage is recovered across many seeded CI runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:57 +02:00
qdrant-cloud-botandCursor c07561335a feat: add serverless_compatible feature flag (#9655)
* feat: add serverless feature flag

Introduce a composite `serverless` feature flag that automatically enables
`write_segment_manifest` and `append_only_mutations` during initialization.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor: rename serverless flag to serverless_compatible

Rename the composite feature flag for clarity and consistency with
serverless deployment terminology.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: satisfy clippy field_reassign_with_default in flag tests

Use struct update syntax instead of mutating Default::default() fields.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:56 +02:00
qdrant-cloud-botandCursor eb3e183029 fix(model_testing): fix flaky harness optimizer wait after restart (#9635)
Track whether optimization ran at any point during the soak run and skip
the post-run wait when the counter was reset by a close+reopen. Also
trigger optimizers explicitly while waiting when the wait is still needed.

Fixes #9626

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:56 +02:00
1d4ca4765c perf(edge): load ReadOnlyEdgeShard segments in parallel (#9594)
* perf(edge): load ReadOnlyEdgeShard segments in parallel

Open each segment on a dedicated thread during initial open and refresh,
reducing follower startup time for shards with many segments.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(edge): resolve clippy type_complexity in parallel segment load

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(edge): run per-segment reads on a configurable thread pool

Replace the per-segment sequential read loops and the spawn-a-thread-per-segment
loader with a single fixed-size rayon thread pool owned by each shard.

- Add EdgeConfig::max_search_threads (Option<usize>, None = CPU-derived default
  matching the core search runtime via common::defaults::search_thread_count).
- Build a long-lived pool in EdgeShard and ReadOnlyEdgeShard; reuse it for
  parallel segment loading on open/refresh instead of std::thread::spawn.
- Add EdgeReadView::par_map_segments as the single seam that runs per-segment
  work on the pool; use it in search, scroll, count, facet and rescore-formula.
  Each task mints its own HardwareCounterCell from the shared accumulator.
- Expose max_search_threads through the builder and the Python binding (+ stub).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(edge): return error instead of panicking on search pool creation

ThreadPoolBuilder::build() can fail (e.g. thread spawn / resource exhaustion).
This runs during EdgeShard open/load and ReadOnlyEdgeShard follower open, so a
transient failure must not abort the process. Propagate it as an OperationError
through the existing OperationResult-returning constructors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor b2c8894895 ci(windows): skip slow facet sampling filter iter test (#9608)
* ci(windows): skip slow facet sampling filter iter test

The 200k-point fixture is too slow for Windows CI runners.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(windows): gate N_FILTER_ITER behind same cfg as test

Avoid dead_code warning when the sampling filter iter test is skipped.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
776dd74ee4 test: add openapi regression test for values_count on missing fields (#9606)
* test: add openapi regression test for values_count on missing fields

Reproduces https://github.com/qdrant/qdrant/issues/9586 where points with
a missing payload field are not matched by values_count filters that should
treat the count as 0.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: use module fixture for values_count missing field test

Follow the standard openapi test structure with setup/teardown fixture
instead of explicit drop_collection calls in the test body.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: treat missing field as count 0 for values_count filter (#9607)

`FieldCondition::check_empty()` ignored the `values_count` condition, so a
point with a missing payload field was never matched by a `values_count`
filter. The desired semantics (and the existing `ValuesCount::check_empty`)
treat a missing field as having a value count of 0.

Forward the empty check to `values_count.check_empty()` so bounds like
`lt: 1`, `gte: 0` and `lte: 0` correctly match points whose field is absent.

Fixes #9586

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 0306584843 ci: build edge debug tools with explicit --bin flags (#9603)
After `--package qdrant`, cargo scopes `--bin` to the qdrant crate only.
Pass `--package edge-shard-query --bin edge-shard-query` so the edge tool
is actually produced for collection/upload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 9781cc4bb4 ci: pass --package qdrant when building debug tools (#9602)
Without an explicit qdrant package, cargo applies --features service_debug
to every -p target including edge-shard-query, which does not define it.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor a98d5850c3 ci: scope service_debug feature to qdrant package only (#9601)
edge-shard-query does not define service_debug; use qdrant/service_debug
when building qdrant bins and edge tools in one cargo invocation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 8bfbc31757 ci: speed up debug-tools workflow build (#9599)
Use the perf profile instead of release, merge qdrant and edge tool builds
into one cargo invocation, and add sccache so shared deps compile once and
rustc outputs are cached across runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 9dcf61ed92 fix: add missing UserData bound on OwnedIoUringPipeline impls (#9596)
The Debug requirement on UserData (#9588) left two impl blocks without
the trait bound, breaking compilation on dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 603b575ecd ci: tag nightly model testing failure issues with model testing label (#9581)
Apply the existing "model testing" label alongside "bug" when the nightly
workflow opens or updates a failure issue.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor 553c426fdd Revert "Enable DeleteByFilter op in model tester" (#9570) (#9576)
DeleteByFilter remains broken: WAL replay can resurrect filter-deleted
points (#9575). Re-mask the op in FORCE_OFF and document the issue.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:55 +02:00
qdrant-cloud-botandCursor edd1b98e3d Enable DeleteByFilter op in model tester (#9570)
The flushing/reload-durability bug that caused post-reload count
mismatches for live filtered deletes has been fixed, so DeleteByFilter
no longer needs to be masked off in the model tester swarm config.

Remove it from FORCE_OFF and update the related doc comments.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:54 +02:00
qdrant-cloud-botandCursor 9855abb194 feat: move segment manifest next to segments/ directory (#9564)
Follow-up to #9530 and #9558.

The shard-level segment manifest was written inside the `segments/`
directory (`segments/manifest.json`). Older versions of Qdrant choke on
an unknown file inside `segments/`, so move it next to the directory as
`segments_manifest.json` instead.

- `SEGMENT_MANIFEST_FILE` is now `segments_manifest.json` and
  `segment_manifest_path()` points at the shard root.
- The manifest is added to `ShardDataFiles` so clear/move handle it.
- Snapshots write the manifest to the snapshot root (next to
  `segments/`), and restore/partial-snapshot loaders no longer need to
  skip it inside `segments/`.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:54 +02:00
qdrant-cloud-botandCursor 10a4224e08 feat: include segment manifest in shard snapshots (#9558)
Follow-up to #9530. When the `write_segment_manifest` feature flag is
enabled, a shard maintains a `segments/manifest.json` listing its
segments so out-of-process readers can discover them without scanning
the filesystem. That manifest was not included in shard snapshots.

Include the segment manifest in the snapshot when the shard maintains
one, capturing it from the live segment holder before proxying (proxies
preserve the wrapped segments' UUIDs, which are the directories written
into the snapshot, so the manifest matches the snapshot contents).

On restore, the snapshot's `segments/manifest.json` is skipped while
restoring segment directories in place; the manifest is regenerated from
the loaded segments when the segment holder is built. The partial
snapshot manifest loader also skips this file.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:54 +02:00
24928ac03c ci: fix and consolidate Rust build caches (#9516)
* ci: fix rust-cache running before checkout in shard-snapshot job

The `test-shard-snapshot-api-s3-minio` job ran `Swatinem/rust-cache`
before `actions/checkout`, so there was no `Cargo.lock`/source present
when the cache action ran. As a result the action no-opped and the job
never actually restored or saved a Rust build cache. Move the cache
step to run after checkout.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: remove dead rust-cache steps from docker-only jobs

These jobs only build Docker images via `docker buildx`/docker build
and never invoke cargo directly, so the `Swatinem/rust-cache` step
never populates or restores anything useful:

- integration-tests.yml: `test-consistency` (docker buildx + shell
  consistency checks only)
- docker-image.yml: `build` and `build-gpu` (docker buildx only)

Drop the unused cache steps.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: drop rust-cache from coverage and release workflows

These workflows run on rare triggers (coverage: nightly schedule;
release-artifacts: on release publish), so by the time they run again
their cache is almost always already evicted from the 10 GB Actions
cache budget. They mostly just consume cache space that would otherwise
be useful to the frequently-run PR builds, so remove the rust-cache
steps entirely:

- coverage.yml: unit-coverage and integration-coverage
- release-artifacts.yml: build-windows-binaries

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: drop explicit clippy cache key in rust-lint

The lint job has no compatible cache peer to share with: clippy
produces clippy-driver metadata that cargo fingerprints separately
from normal build/test artifacts, and its `--all-features` invocation
pulls in the GPU dependency crates, so it matches neither the
rust-tests nor the gpu group. With nothing to share, the explicit
`key: clippy` is equivalent to the default per-job key, so drop it for
consistency with the rest of the workflows.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci: dedicated rust-cache keys with single writer per group

Give related jobs explicit cache keys and let only the broadest build
in each group save the cache (`save-if: "false"` on the others), so a
narrower build can't overwrite it with fewer compiled dependencies
first.

- `rust-tests` (rust.yml): explicit `shared-key: rust-tests`. It builds
  `--workspace --tests`, which pulls in dev-dependency features that the
  integration-tests builds (plain `cargo build`) do not, so it is kept
  as its own dependency cache rather than merged with integration-tests.
- `integration-tests`: writer = integration-tests; readers =
  integration-tests-consensus, test-shard-snapshot-api-s3-minio.
- `edge`: writer = edge-test (clippy + examples build); reader =
  edge-rust-check. Both build the amalgamated qdrant-edge `examples`
  package.

rust-gpu keeps its own default cache (the `gpu` feature adds the
ash/gpu-allocator/shaderc dependency crates).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Reuse `integration-tests` build cache for `io-bridge-object-store-tests` workflow

* Cleanup Docker cache in GHA workflows

* ci: only save rust-cache on the integration branch

Under the 10 GB Actions cache budget there's no practical benefit to
saving feature-branch build caches: they'd be evicted before being
reused. Scope every rust-cache *writer* to save only on `dev` (the
branch PRs target and restore from) via `save-if`:

- rust.yml, rust-lint.yml, edge-test.yml, integration-tests.yml

Reader jobs already have `save-if: "false"` and are unchanged.

Also drop rust-cache from rust-gpu.yml entirely: it only runs on
`master` pushes (releases, a few times a month at most), so any cache
would always be evicted between runs.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>
2026-08-04 11:16:53 +02:00
08a73b55ee feat(bm25): explicit Disabled stemmer; deprecate language: "none" hack (#9376)
* feat(bm25): add explicit Disabled stemmer; deprecate language hack

Adds a `Disabled` variant to `StemmingAlgorithm` (`stemmer: {"type": "none"}`)
so stemming can be turned off explicitly in both the main engine and Edge,
instead of relying on the undocumented `language: "none"` footgun that
silently disabled both stemming and stopwords.

For language-neutral text processing the supported setup is now:
1. set the stemmer to disabled, and
2. configure an empty stopword set.

The main engine still tolerates unsupported languages (so existing
`language: "none"` configs keep working on upgrade) but now logs a
deprecation warning pointing users to the explicit setup. Edge continues
to reject unsupported languages, and now has a real way to disable stemming.

Refs: https://github.com/qdrant/qdrant/issues/9289
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(edge-py): handle Disabled stemmer in python bindings; fix openapi schema

- Handle the new StemmingAlgorithm::Disabled variant in the qdrant-edge-py
  bindings (FromPyObject/IntoPyObject/Repr) and add a DisabledStemmer pyclass
  plus its .pyi stub entry.
- Match generator output for the StemmingAlgorithm OpenAPI schema (plain $ref
  in anyOf) so docs/redoc/master/openapi.json stays consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(openapi): regenerate StemmingAlgorithm schema with generator output

Ran tools/generate_openapi_models.sh so docs/redoc/master/openapi.json
exactly matches generator output: DisabledStemmerParams/NoStemmer are placed
after SnowballLanguage, and the StemmingAlgorithm anyOf entry is a plain $ref
(the schema2openapi step flattens the allOf+description wrapper).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(test): avoid wildcard enum match arm in bm25 sparse_len helper

clippy --all-targets flags `other => panic!()` as wildcard_enum_match_arm;
match the Dense/MultiDense variants explicitly instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: issues

* fix: log::warn as call once

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Daniel Boros <dancixx@gmail.com>
2026-08-04 11:16:53 +02:00
qdrant-cloud-botandCursor eb67b98cec test(model_testing): add basic Query API coverage (#9511)
* test(model_testing): add basic Query API coverage

Add an Op::Query variant that exercises the unified Query API with a
plain Nearest scoring query (ScoringQuery::Vector(QueryEnum::Nearest)),
routed through collection.query. Verification mirrors the existing
Search op: exact dense / multi-dense scans must return exactly the
top-k, sparse and approximate paths are an upper bound, and every
returned id must exist in the model with the queried vector populated
and matching the optional num filter.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(model_testing): unify Search/Query invariant check

Extract the shared candidate set, strict-vs-upper-bound decision,
per-result membership checks, and the rich diagnostic probes
(retrieve/count/scroll/retry) into helpers reused by both apply_search
and apply_query. apply_query now gets the same strict top-k check plus
failure diagnostics it previously lacked.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:53 +02:00
qdrant-cloud-botandCursor 331f4b1330 test(model_testing): add has_vector matcher to generated scroll filter (#9510)
Extend ScrollFilter with a HasVector variant so paginated scroll
exercises Condition::HasVector. The matcher targets an active vector
name, and the model verifier checks it against each point's populated
vector set (which varies via DeleteVectors / partial UpdateVectors),
restricting results to a known, model-checkable subset.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:53 +02:00
qdrant-cloud-botandCursor ae322c131a docs: clarify purpose of /healthz, /livez and /readyz endpoints (#9509)
Previously all three Kubernetes health endpoints shared the same generic
description ("An endpoint for health checking used in Kubernetes."), which did
not convey what each one actually guarantees.

- /healthz and /livez: clarify they are pure liveness checks (200 once the HTTP
  API is up), do not inspect data/shards/consensus, and are identical to each
  other.
- /readyz: clarify it is a readiness probe that waits out pending data
  operations (consensus catch-up + shard health in distributed mode) before
  reporting ready, and document the real 200 ("all shards are ready") and 503
  ("some shards are not ready") responses.

Documentation-only change (OpenAPI descriptions/examples + added 503 response
doc). No runtime behavior changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:53 +02:00
qdrant-cloud-botandCursor 10777867e1 test(model_testing): add has_id matcher to generated scroll filter (#9499)
* test(model_testing): add has_id matcher to generated scroll filter

Extend the ScrollPaged filter selector with a HasId variant so the
generated filter exercises a has_id matcher (restrict to an explicit
point-id set), in addition to the existing none / num / tag filters.

The id set mixes ids present in the model with ids drawn from the id pool
that may not be, so the matcher meaningfully restricts. The model mirrors
it with a set-membership predicate, and the existing paged-scroll
id-set assertion validates the engine result.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: fix clippy lints (wildcard_enum_match_arm, from_iter_instead_of_collect)

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(model_testing): clamp has_id sample count to id_pool

Addresses CodeRabbit review: random_distinct_ids could spin forever when
the requested distinct count (up to 15) exceeds id_pool. Clamp to
id_pool.min(15). Only relevant for tiny --id-pool values; the default
pool (500) is far above 15.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor 13203e13d9 test(model_testing): cover paginated scroll (offset + limit) (#9497)
Add a ScrollPaged verification op that scrolls the (optionally filtered)
collection in pages of a small limit, following next_page_offset until
exhausted. The other scroll ops always read everything in one page
(offset: None, limit: usize::MAX), so the offset cursor and a real
(non-MAX) limit had no coverage.

Asserts each page holds at most `limit` points, no id repeats across
pages, and the union of all pages equals the model's expected id set for
the chosen filter (none / num == X / tag == X). Includes a page-count
guard against a stuck cursor.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor 4207f6875a test(model_testing): cover with_payload/with_vector selectors (#9495)
Add a RetrieveSelective verification op that exercises the
with_payload/with_vector selector forms, which the soak test previously
never used (every read passed Bool only).

- with_payload covers Bool, Fields, Selector::Include, Selector::Exclude.
- with_vector covers both Bool forms and Selector(names) over a subset of
  active vector names.

The verifier asserts the engine's returned payload equals the model entry
filtered by the engine's own PayloadSelector::process, and the returned
vectors equal the requested name subset that the point actually has
populated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor 6f9f3c8882 test(model_testing): cover SetPayloadOp.key (#9494)
Add a SetPayloadByKey op to the model_testing soak test so the keyed
set-payload path (SetPayloadOp.key) is exercised. Previously every
set-payload op passed key: None, leaving the key-scoped assignment path
(merge_by_key / JsonPath::value_set) uncovered.

The op samples existing point ids, a random payload, and a single
top-level schema field as the key. The model mirrors the engine via
Payload::merge_by_key (the same JsonPath::value_set the engine's
set_by_key uses), keeping model and engine in lockstep.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor 5bcc420073 test: fix flaky test_partial_snapshot_empty (#9493)
The test asserts that creating a partial snapshot between two in-sync peers
returns 304 (empty diff). It only waited for the write peer to become green,
but read the read peer's manifest for the comparison. An async optimization
reshaping the read peer's segments after collection-snapshot recovery makes
its manifest diverge from the write peer's files, producing 200 instead of
304 (assert 200 == 304).

Wait for the read peer to become green as well before comparing manifests,
mirroring the earlier flaky-test fixes (#7358, #7360).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botanddependabot[bot] 80d378b896 build(deps): bump cryptography from 48.0.0 to 48.0.1 in /tests (#9492)
Bumps [cryptography](https://github.com/pyca/cryptography) from 48.0.0 to 48.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.0...48.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 48.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor fa07065c36 test(model_testing): add dense vector with inline_storage + scalar quantization (#9484)
Add a new named dense vector "i" to the model_testing fixture configured
with HNSW inline_storage backed by scalar quantization, so the soak harness
exercises the inline-storage index layout. It behaves like a normal dense
vector to the model; only the on-disk HNSW layout differs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor d7f33cb7d5 test(turbo): reduce randomized scenarios on Windows to fix SLOW tests (#9483)
The 6 turbo vector storage model tests are flagged SLOW by nextest on
Windows CI (>60s, some >240s):
  - vector_storage::turbo::tests::turbo_model_test_random_ops_{dot,cosine}
  - vector_storage::turbo::multi::tests::turbo_multi_model_test_random_ops_{dot,cosine}
  - vector_storage::turbo::test::congruent_random_ops_{dot,cosine}

These are dim x seed x ops sweeps; Windows runners are several times slower.
Lower SEEDS_PER_CELL on Windows only via #[cfg(windows)] so the runs stay
within the slow-timeout, while keeping full coverage on Linux/macOS.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botandCursor 3cbb926c0f build(deps): bump pyjwt from 2.12.1 to 2.13.0 in /tests (#9482)
Ports https://github.com/qdrant/qdrant/pull/9480 to dev.

Bumps pyjwt from 2.12.1 to 2.13.0 (security release).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:52 +02:00
qdrant-cloud-botanddependabot[bot] 344447d4b8 build(deps): bump pyo3 from 0.28.3 to 0.29.0 (#9462)
Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.28.3 to 0.29.0.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.28.3...v0.29.0)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-version: 0.29.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 11:16:51 +02:00
qdrant-cloud-botandCursor 3841b3c841 Lower default update queue length from 1M to 200 (#9448)
Reduce the default in-RAM update worker queue size to limit memory
held by pending operations and provide faster backpressure.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:51 +02:00
qdrant-cloud-botandCursor 7e4092f67e fix(shard): read deferred source point in copy-on-write move (#9377)
A plain upsert could fail with a spurious "No point with id ... found"
(HTTP 404) when it raced a `prevent_unoptimized` optimization. This
surfaced as flaky CI failures of
`test_shard_transfer_includes_deferred_points[snapshot]`.

Root cause: `apply_points_with_conditional_move` reads the source point's
vectors and payload before relocating it into an appendable segment. Those
reads used the default `DeferredBehavior::VisibleOnly` accessors
(`all_vectors`/`payload`). When the source point is deferred — invisible to
ordinary reads, e.g. a point whose internal id is beyond the deferred
threshold under `prevent_unoptimized` while an optimization wraps its
segment in a proxy — `VisibleOnly` cannot resolve it: `payload`/`vector`
raise `PointIdError`, which propagates out as the user-facing 404 on a plain
upsert that internally takes the copy-on-write move path.

Fix: add deferred-aware read accessors (`vector_with_behavior`,
`all_vectors_with_behavior`, `payload_with_behavior`) to `ReadSegmentEntry`,
implemented on both `Segment` and `ProxySegment` (the proxy forwards the
behavior to its wrapped segment). The existing accessors delegate with
`VisibleOnly`, preserving current behavior. The CoW move path now reads with
`WithDeferred`, so deferred points are relocated with their real data
instead of failing.

Adds a deterministic regression test asserting `VisibleOnly` hides a
deferred point while `WithDeferred` resolves its real vectors/payload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:50 +02:00
qdrant-cloud-botandCursor 170e2a99e9 Fix CI: add missing imports in TurboVectorStorage (#9363)
`TurboVectorStorage::update_from` references `AtomicBool` and `Range`
without importing them, breaking `cargo clippy --workspace` on the `dev`
branch (introduced in #9346). Import both types to restore the build.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 11:16:48 +02:00
qdrant-cloud-botandCursor 71574f8d89 test(consensus): de-flake replicate_points_stream_transfer_updates (#9263)
The test relied on concurrent background upserts adding *new* matching
points to the destination shard during the streamed transfer, asserting
the destination count was strictly greater than the original snapshot.

Whether new matching points land in the destination before the transfer
completes is timing-dependent (especially under pytest-xdist load), so
the strict `>` assertion is racy and occasionally fails with equal
counts (e.g. `assert 5031 > 5031`).

Relax to `>=` and keep the strict `dest == src` consistency check, which
is the actual invariant being verified.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:57:10 +02:00
qdrant-cloud-botandCursor 90ac5e8589 test: reproduce MatchAny(any=[]) strict-mode rejection on integer index (#9260)
* test: reproduce MatchAny(any=[]) strict-mode rejection on integer index

Adds an openapi test that demonstrates the strict-mode bug where
`match: {"any": []}` on an integer-indexed payload field is rejected with:

    Bad request: Index required but not found for "<field>" of one of
    the following types: [keyword, uuid]

even though the field is indexed as integer. Root cause is that an empty
`any` list deserializes as `AnyVariants::Strings(empty)` (untagged enum;
Strings variant listed first), and strict mode infers a keyword/uuid
index requirement from the variant tag — ignoring that the list is
empty (i.e. a no-op condition).

The test covers:
- Baseline no-op semantics with and without indexes (passes today).
- Reproduction under strict mode for `must`, `must_not`, and a
  FormulaQuery FieldCondition (currently failing; will pass once
  empty `any`/`except` no longer requires an index).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: treat empty MatchAny/MatchExcept as no-op in strict-mode index check

An empty `match: {"any": []}` (or `{"except": []}`) is a no-op: `any: []`
matches nothing and `except: []` excludes nothing, regardless of the
field's data type. Because an empty list cannot carry type information it
deserializes as the keyword `AnyVariants::Strings(empty)` variant, which
previously caused strict mode to demand a keyword/uuid index and reject
the request with:

    Index required but not found for "<field>" of one of the following
    types: [keyword, uuid]

even on a field indexed as integer.

Fix:
- `infer_index_from_any_variants` returns no required index for an empty
  variant set.
- `Extractor::update_from_condition` skips a condition whose required
  index set is empty, so a no-op condition is never reported as needing
  an index (this also covers the FormulaQuery condition path).

This makes the openapi reproduction in test_match_any_empty.py pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:57:09 +02:00
qdrant-cloud-botandCursor 0476a28c02 Bump edge packages (Python + Rust) to 0.7.2 (#9252)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:55:05 +02:00
qdrant-cloud-botandCursor d75d805533 Strict mode: add max_disk_usage_percent (#9212)
* Strict mode: add `max_disk_usage_percent`

Mirrors `max_resident_memory_percent`: rejects disk-consuming update ops
(upsert, set/overwrite payload, update vectors) when the filesystem hosting
Qdrant storage is filled above the configured percentage. Delete-style ops
remain allowed so callers can free disk.

Disk usage is sampled via `statvfs` and TTL-cached for 5s (same cadence as
the resident-memory reader) so high-RPS request paths don't hammer the
syscall. Reader is keyed by path in `common::disk_usage` and returns `None`
on stat failure — callers (the strict-mode check) treat `None` as "skip",
matching the memory-check behaviour.

Plumbing follows the existing pattern: field on `StrictModeConfig` (+
output/diff/Hash), gRPC proto field `22`, validation 1..=100, REST/proto
conversions, and the hook into `check_strict_mode_toc_batch` alongside the
memory check (both guarded by `any_consumes_memory`).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix CI: Windows disk_usage test + e2e WAL config

- `missing_path_returns_none` panicked on Windows because
  `GetDiskFreeSpaceEx` succeeds for non-existent paths (it resolves up to
  the containing drive). Relax the assertion to "must not panic; if a
  value is returned it must be well-formed". The contract we care about
  (None on failure) is platform-defined, not something we can portably
  force.

- e2e test failed at batch 0 with "WAL buffer size exceeds available disk
  space": Qdrant's existing per-shard `DiskUsageWatcher` enforces
  `free >= 2 * wal_capacity_mb` and the default WAL didn't fit in the
  50 MB tmpfs. Bump tmpfs to 200 MB and shrink `wal_capacity_mb` to 1 MB
  (same pattern as `test_low_disk.py`) so our strict-mode gate is the
  one that fires, not the WAL pre-check. Raise the gate threshold to
  50% to match the larger headroom.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:54:03 +02:00
qdrant-cloud-botandCursor 5ad6b139af Add Rust edge example: add-named-vector (#9230)
* Add Rust edge example: add-named-vector

Port `lib/edge/python/examples/add-named-vector.py` to Rust under
`lib/edge/publish/examples/src/bin/add-named-vector.rs`.

Re-export `VectorNameOperations`, `CreateVectorName`, `DeleteVectorName`,
`VectorNameConfig`, `DenseVectorConfig`, and `SparseVectorConfig` from
`qdrant_edge` so the public Rust API can create/delete named vectors
without reaching into the internal `shard` crate.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fmt

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:54:03 +02:00
qdrant-cloud-botandCursor Agent ddcd1ad0ae Bump edge packages (Python + Rust) to 0.7.1 (#9228)
Co-authored-by: Cursor Agent <agent@cursor.com>
2026-06-03 14:53:10 +02:00
qdrant-cloud-botandCursor 169bcb4759 Switch murmur3 dep to upstream murmur3_32 crate (#9193)
Replace the qdrant fork of stusmall/murmur3 (pinned at git rev 2c39087) with
the standalone `murmur3_32` crate from crates.io. Output is bit-identical
(verified offline against the previous `murmur3_32_of_slice`, including a
100k-iteration random fuzz), so existing BM25 sparse vectors remain
wire-compatible. The new crate is ~10–18% faster on inputs ≥ 16 bytes,
which speeds up `token_id` during tokenization.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:49:45 +02:00
qdrant-cloud-botandCursor 0e37dfd290 ci(windows): skip IO-heavy tests that aren't OS-specific (#9188)
* ci(windows): skip IO-heavy tests that aren't OS-specific

On the Windows CI runner, several tests are 3-25x slower than on Ubuntu
purely due to slow filesystem IO. These tests exercise platform-agnostic
logic (optimizer, snapshot, WAL recovery, dedup, deferred points) and
are fully covered by the Linux and macOS jobs.

Mark them with `#[cfg_attr(target_os = "windows", ignore = "...")]` so:
- Windows CI skips them and finishes faster.
- They're still listed and runnable via `cargo test -- --ignored`
  on Windows for local debugging.

Based on JUnit timings from CI run 26462785436 (PR #8827), this should
save ~5 minutes wall-clock on the Windows job, taking it closer to the
~13min Ubuntu and ~9min macOS jobs (currently 20m24s).

Tests affected:
- lib/wal: check_wal, check_last_index, check_clear, check_reopen,
  check_truncate, check_prefix_truncate, test_prefix_truncate_parametric
- lib/edge/optimize: full tests module
- lib/segment deferred-point tests: read_operations,
  dense_segment_combinations, sparse, facets
- lib/collection: snapshot_test, points_dedup, wal_recovery,
  collection_test::test_ordered_read_api, snapshot_recovery_test

Co-authored-by: Cursor <cursoragent@cursor.com>

* revert(ci/windows): keep WAL and WAL-recovery tests on Windows

Reviewer correctly pointed out that WAL is mmap-backed and has
substantial Windows-specific code paths:

- Different segment allocation (fs4 vs rustix::ftruncate)
- Windows-specific delete_windows() with mmap-drop + retry loop
- Windows-specific sync_all() because directory fsync is unavailable
- Windows-specific lock proxy file (directories aren't lockable)

So those tests genuinely need Windows coverage. Reverted skips for:
- lib/wal/src/lib.rs: all check_* tests and test_prefix_truncate_parametric
- lib/collection/src/tests/wal_recovery_test.rs: all three tests

Still skipped on Windows (no OS-specific code in their production paths):
- lib/edge/optimize.rs (no cfg(windows) in source)
- lib/segment deferred-point tests (segment/ has no cfg(windows))
- lib/collection snapshot/dedup tests (collection/ has no cfg(windows))
- lib/collection integration snapshot_recovery + ordered_read_api

Co-authored-by: Cursor <cursoragent@cursor.com>

* revert(ci/windows): keep collection integration and snapshot_test

Per reviewer request, keep running these on Windows:
- lib/collection/tests/integration/* (snapshot_recovery_test,
  collection_test::test_ordered_read_api)
- lib/collection/src/tests/snapshot_test.rs

These exercise higher-level collection/snapshot behavior that benefits
from cross-platform validation.

Remaining Windows skips (production code has no cfg(windows) branches):
- lib/edge/src/optimize.rs: 14 optimizer tests
- lib/segment/src/segment/tests/mod.rs: 4 deferred-point tests
- lib/collection/src/tests/points_dedup.rs: 2 dedup tests

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(windows): also skip HNSW/quantization integration tests

Per reviewer, also skip these segment integration test modules on Windows:
- hnsw_quantized_search_test::* (25 tests)
- multivector_filtrable_hnsw_test::* (rstest cases)
- multivector_quantization_test::* (rstest cases)
- byte_storage_quantization_test::* (rstest cases)
- payload_index_test::test_struct_payload_index_nested_fields

These exercise pure HNSW/quantization correctness on top of standard
segment IO that is already covered by tests we keep running on Windows.

Adds ~930s of sequential time to the Windows skip list, bringing the
expected wall-clock saving from ~3 min to ~8-10 min.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:49:45 +02:00
qdrant-cloud-botandCursor e160c1b016 Bump edge packages (Python + Rust) to 0.7.0 (#9187)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:49:44 +02:00
57de7b189d build(deps): bump idna from 3.14 to 3.15 in /tests (#9115)
Port of #9098 to dev branch.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:47:41 +02:00
10c500b3fd test: surface peer startup crashes in dirty-shard test (#9161)
* test: wait for WAL flock release on peer restart in dirty-shard test

The flake addressed by #9124 (bumping `wait_for_peer_online` to 60s) was
misdiagnosed as CPU contention. Logs show the restarted peer panics within
~1s of startup at `consensus_wal.rs:36` with:

    Wal error: Can't init WAL: Kind(WouldBlock)
    Panic: Can't open consensus WAL: Kind(WouldBlock)

`wal::Wal::open` calls `fs4::FileExt::try_lock` (non-blocking `flock`) on
the WAL directory fd. After `p.kill()` (SIGKILL + waitpid) the kernel
normally releases the killed peer's flock immediately, but under
pytest-xdist load there is a small window where it lags. The fresh peer's
startup then races and panics. After the panic `/readyz` never returns
200, so neither 30s nor 60s rescues the test.

Fix: add a `wait_for_wal_unlocked` helper that polls both the consensus
WAL directory and the local-shard WAL directory with the same exclusive
non-blocking flock that qdrant uses, and call it after every `p.kill()`
that is followed by a `start_peer` on the same `peer_dir`. The two
60s timeouts are restored to the default 30s now that the underlying
race is gone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: fail fast if sync restart crashes in dirty-shard test

The sync restart in `test_dirty_shard_survives_update_collection` used
plain `wait_for_peer_online(sync_uri)`, which only polls `/readyz`. If
the freshly started peer panics on startup (e.g. WAL `WouldBlock`), the
test waits the full 30s timeout and then reports a `/readyz` timeout
instead of the actual panic message and exit code.

Switch the sync restart to `wait_for_peer_online_or_crash(...)` (same
helper already used for the dirty restart). On crash it dumps the peer
log tail so the next CI failure shows the real reason instead of a
generic timeout.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: drop speculative WAL flock wait, keep crash-detection switch

Reverts the `wait_for_wal_unlocked` helper that polled the WAL
directories with non-blocking flock. The kernel-side flock race it was
guarding against could not be reproduced in isolation (0/300 iters of
SIGKILL+wait+re-flock on bare Linux), so it was speculative.

Kept:
- Sync restart now uses `wait_for_peer_online_or_crash(...)` instead of
  plain `wait_for_peer_online`, so any startup panic surfaces fast with
  the actual log tail instead of hiding behind a 30s `/readyz` timeout.
- The two 60s timeouts bumped in #9124 are restored to the default 30s.

If the flake recurs in CI, the new failure output will tell us the
actual cause (panic message + exit code), which is more useful than
papering over it.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:47:01 +02:00
00a7fbe19e docs: refresh prevent_unoptimized description (#9133)
The previous description was outdated: it claimed that enabling this
option "blocks updates at the request level" until segments are
re-optimized. In practice the implementation uses "deferred points":
new points written to large unoptimized segments are persisted but
excluded from read/search results until the segments are optimized.

Updates are not blocked; only `wait=true` clients are made to wait for
the deferred points to become visible. Update this in the REST schema
(via `OptimizersConfig` / `OptimizersConfigDiff`), in the gRPC proto,
in the edge config docstrings, and regenerate the OpenAPI bundle via
`tools/generate_openapi_models.sh`.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 14:45:26 +02:00
e01c207f40 Bump version to 1.18.1 (#9134)
* Bump version to 1.18.1

Co-authored-by: Cursor <cursoragent@cursor.com>

* Update missed cherry picks

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:56:30 +02:00
a8d615884a test: wait for consensus before cleanup in test_resharding_deferred[up] (#9129)
`commit_read_hashring` returns once the leader has applied the entry,
but followers may not have applied it yet. The CommitRead handler calls
`invalidate_clean_local_shards` for `old.nodes()`, which cancels any
ongoing shard clean task. If the test's subsequent `cleanup?wait=true`
request to a follower arrives just before that follower applies
CommitRead, the cleanup task is started and then cancelled mid-flight,
and the endpoint returns HTTP 500 "Failed to clean shard points due to
cancellation, please try again".

Add `wait_for_same_commit` after `commit_read_hashring` so every peer
has applied the entry (and thus already invalidated any clean tasks
that don't exist yet) before we issue cleanup.

Observed flake:
https://github.com/qdrant/qdrant/actions/runs/26259341982/job/77289143792

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:58:21 +02:00
5fba8ff550 test: bump wait_for_peer_online timeout for dirty-shard crash-loop test (#9124)
Under parallel pytest-xdist load the rejoining peer needs more than the
default 30s budget to replicate the UpdateCollection entry and report
ready, causing intermittent CI failures:

  Exception: Timeout waiting for condition peer_is_online to be satisfied
  in 30 seconds

Bump both peer-online waits in the dirty shard crash-loop test to 60s,
matching the precedent set by #8963 for similar CPU-contention flakes.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:57:12 +02:00
837b4b76e7 Use cluster default shard transfer method for fallback (#9120)
* Use cluster default shard transfer method for fallback

When a WAL delta automatic transfer fails, the driver falls back to the
method passed via `fallback_method`. This was hard-coded to
`StreamRecords` (unless `prevent_unoptimized` was enabled), which is
inconsistent with the 1.18.0+ default of `Snapshot` and ignores any
configured `default_shard_transfer_method`.

Use `Collection::default_shard_transfer_method()` instead, so the
fallback matches the cluster default. With `prevent_unoptimized` we
still pin to `Snapshot` to preserve deferred point state exactly (raw
segment copy); stream_records would send deferred points but they
would not be deferred on the target.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Avoid wal_delta fallback, update fallback test

If the cluster default transfer method is wal_delta, the same-method
fallback would be refused by the driver. Use snapshot as a safe fallback
in that case; snapshot is also the 1.18.0+ default.

Update test_shard_wal_delta_transfer_fallback to assert the new
snapshot fallback (was stream_records).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address clippy wildcard_enum_match_arm

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:50:16 +02:00
0359e92f45 Fix clippy wildcard_enum_match_arm lint in test_tq.rs (#9101)
Replace wildcard match `_` with explicit enum variants
`DistanceType::Dot | DistanceType::L1 | DistanceType::L2` to satisfy
clippy's wildcard_enum_match_arm lint.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:46:48 +02:00
a4ea7aee55 fix: add sleep after killing uploaders in snapshot transfer throttled test (#9088)
The test_shard_snapshot_transfer_throttled_updates test was flaky because
it checked data consistency immediately after killing background upload
processes, without waiting for in-flight writes to propagate across peers.

All sibling tests (test_shard_snapshot_transfer_fast_burst,
test_shard_stream_transfer_throttled_updates, etc.) already include a
sleep(1) after killing uploaders. This was the only variant missing it.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:45:41 +02:00
368e9d4150 fix: reduce flakiness of gRPC TLS e2e test (#9086)
Pre-pull the fullstorydev/grpcurl Docker image before running the test
and retry the gRPC health check up to 3 times, so transient Docker
networking hiccups on CI runners no longer cause a hard failure.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:45:34 +02:00
ee2107c2db fix: validate vector dimensions before WAL write for async upserts (#9058)
* fix: validate vector dimensions before WAL write for async upserts

When upserting points with wait=false (the default), dimension
mismatches were silently discarded during background processing.
The API returned 200 "acknowledged" but the points were never stored,
causing silent data loss with no error feedback to the user.

This adds an early dimension validation check in do_upsert_points()
that runs before the operation is written to WAL. This ensures that
dimension errors are returned to the client regardless of the wait
parameter, matching the behavior of wait=true.

The validation handles all vector types:
- Dense single vectors
- Multi-dense vectors
- Named vectors (dense, multi-dense, sparse)
- Sparse vectors are skipped (no fixed dimension)

Closes #9039

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor: move vector dimension validation into dedicated module

Extract validate_vector_dimensions and helper functions from update.rs
into src/common/validate_vectors.rs for better code organization.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: update shard update test for early dimension validation

The test expected a shard-level error message, but now dimension
mismatches are caught before reaching the shards. Update the assertion
to accept either the early validation error or the shard-level error.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: assert actual dimension error message in shard update test

Check for the descriptive error ("Vector dimension error: expected dim: 4, got 3")
rather than the generic shard failure wrapper.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:43:19 +02:00
99355603f9 Fix match: {except: []} returning zero results with payload index (#9055)
* Add integration test for `match: {except: []}` with integer index

Regression test for https://github.com/qdrant/qdrant/issues/9050

An empty `except` list (NOT IN []) should always match all points that
have the field, both with and without a payload index. Currently the
integer (and keyword) indexed path incorrectly returns zero results
because:

1. serde deserializes `except: []` as `AnyVariants::Strings([])` (first
   variant of the untagged enum)
2. The map index filter_impl returns `iter::empty()` for empty
   cross-type variant, instead of matching everything

The test covers:
- except: [] without any index (baseline, currently works)
- except: [] with an integer index (currently broken)
- except: [] with a keyword index (currently broken)

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix `match: {except: []}` returning zero results with payload index

Fixes https://github.com/qdrant/qdrant/issues/9050

Root cause: `except: []` deserializes as `AnyVariants::Strings([])`
due to the untagged serde enum trying `Strings` first. On an integer
index, the filter_impl matched `Except + Strings(empty)` and returned
`iter::empty()` (zero results). The same issue existed symmetrically
on keyword indexes with `Integers(empty)` and on UUID indexes with
`Integers(empty)`.

The fix: when the cross-type variant reaches the Except branch (e.g.
Strings on an integer index, Integers on a keyword/UUID index), return
`None` unconditionally — regardless of whether the set is empty. This
delegates to the fallback condition checker, which already handles
`except: []` correctly by matching all values.

The `estimate_cardinality_impl` functions had the same bug (returning
`CardinalityEstimation::exact(0)` for the empty cross-type case) and
are fixed the same way.

Affected index types: integer, keyword (str), UUID.
Bool index is NOT affected — it already returns `None` for all
Any/Except conditions.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Expand match-except test to cover all index types and cross-type filters

Extends the regression test for #9050 to comprehensively cover:

- Integer, keyword, and UUID field indexes
- Empty except list (the original bug) for each index type
- Non-empty except list with matching types (normal filtering)
- Cross-type except values (e.g. strings on integer index, integers on
  keyword/UUID index) — type mismatch should exclude nothing → all match
- Exclude-all: listing every value should return zero results
- All scenarios tested both with and without a payload index

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:41:53 +02:00
1a7a5ac61a test: failing test for values_per_hash drift on duplicate geo point removal (#9043)
* test: add failing test for values_per_hash drift on duplicate geo point removal

Reproduces the bug where `remove_point` only calls `decrement_hash_value_counts`
once per unique geohash, while `add_many_geo_points` increments it once per value.
When a point has duplicate geo coordinates (same geohash), the counters drift
upward permanently after removal.

Ref: https://github.com/qdrant/qdrant/pull/9033#discussion_r3241154045
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: decrement values_per_hash once per value on geo point removal

`add_many_geo_points` increments `values_per_hash` once per value, but
`remove_point` deduplicated geohashes with a HashSet + `continue` that
also skipped the per-value decrement. A point with duplicate geo
coordinates (same geohash) therefore left the counters drifted upward
permanently after removal.

Move `decrement_hash_value_counts` above the dedup guard so it runs once
per value, matching the increment side. `points_map` and
`points_per_hash` track points, not values, so they stay deduplicated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:40:46 +02:00
5e0f3a380c refactor: split geo_index/mod.rs into separate files (#9018)
The geo_index module was a single 2134-line file. Split it into focused
files for better readability and maintainability:

- mod.rs: GeoMapIndex enum definition and core methods (~290 lines)
- builders.rs: GeoMapIndexMmapBuilder and GeoMapIndexGridstoreBuilder
- payload_index.rs: ValueIndexer, PayloadFieldIndex, PayloadFieldIndexRead impls
- tests.rs: all test code (~1340 lines)

No logic changes; all 40 existing tests pass.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:38:25 +02:00
d17514fb61 refactor(map_index): split monolithic mod.rs into focused submodules (#8999)
The ~2150-line map_index/mod.rs was difficult to navigate. Split it into
focused files while preserving all logic and public API:

- key.rs: MapIndexKey trait and impls for str, IntPayloadType, UuidIntType
- builders.rs: MapIndexBuilder, MapIndexMmapBuilder, MapIndexGridstoreBuilder
- payload_index_impl_str.rs: PayloadFieldIndex/Read for MapIndex<str>
- payload_index_impl_int.rs: PayloadFieldIndex/Read for MapIndex<IntPayloadType>
- payload_index_impl_uuid.rs: PayloadFieldIndex/Read for MapIndex<UuidIntType>
- facet_index_impl.rs: FacetIndex impl for MapIndex<N>
- value_indexer_impl.rs: ValueIndexer impls and value_retriever methods

mod.rs retains the MapIndex enum, its core inherent methods, type aliases,
constants, and tests.

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 10:36:55 +02:00
qdrant-cloud-botanddependabot[bot] e998330f9a build(deps): bump actix-http from 3.12.0 to 3.12.1 (#9010)
Bumps [actix-http](https://github.com/actix/actix-web) from 3.12.0 to 3.12.1.
- [Release notes](https://github.com/actix/actix-web/releases)
- [Changelog](https://github.com/actix/actix-web/blob/main/CHANGES.md)
- [Commits](https://github.com/actix/actix-web/compare/http-v3.12.0...http-v3.12.1)

---
updated-dependencies:
- dependency-name: actix-http
  dependency-version: 3.12.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 10:35:58 +02:00
570546c23e Fix phrase matching crossing string-array element boundaries (#8938)
* Fix phrase matching crossing string-array element boundaries

When indexing a string array like ["quick", "brown"], the index
concatenated tokens from all elements into a single flat document.
A phrase query for "quick brown" found the tokens consecutively,
producing false matches.

Insert a sentinel token ("\x00") between array elements during
indexing. It is registered as a normal vocab token that occupies a
position in the document, so phrase windows cannot span it. No
tokenizer ever produces this string, so it never appears in a query.

Closes #8937

Co-authored-by: Cursor <cursoragent@cursor.com>

* Strip null bytes from tokenizer output

Ensure no tokenizer can produce tokens containing '\0', which is
reserved as the array-boundary sentinel for phrase matching.

Covers all tokenizer paths:
- process_token_cow (used by Word, Whitespace, Prefix, Multilingual)
- Japanese tokenizer (has its own inline processing)

Co-authored-by: Cursor <cursoragent@cursor.com>

* Revert "Strip null bytes from tokenizer output"

This reverts commit fe6ea7a955.

* filter sentinel token from query

* Preallocate vector

Co-authored-by: Tim Visée <tim+github@visee.me>

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Tim Visée <tim+github@visee.me>
2026-05-08 16:14:23 +02:00