Commit Graph
61 Commits
Author SHA1 Message Date
Andrey VasnetsovandClaude Fable 5.1 81bb80a5d3 Expose id tracker memory placement in collection config (#10597)
Add `id_tracker: { memory: cold | pinned }` to CollectionParams,
CollectionParamsDiff and CreateCollection (REST + gRPC `IdTrackerParams`),
mirroring `payload: { memory }`. `cold` builds the disk-resident id tracker,
`pinned` the in-RAM immutable one. Unset keeps the current behavior: the
`serverless_compatible` feature flag decides.

The requested placement is persisted as an optional `id_tracker_memory` on
SegmentConfig (skipped when unset, so existing configs are unchanged); the
segment builder resolves it through `SegmentConfig::id_tracker_memory_placement`
instead of reading the feature flag directly.

The config mismatch optimizer rebuilds non-appendable segments whose effective
placement differs from the requested one. Appendable segments are skipped: they
always use the mutable tracker and get the current config when indexed.

`cached` is rejected by validation: the disk mapping reader has no
populate-on-open path.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:28:24 +02:00
Roman TitovandClaude Opus 5 5476473bec Validate UpdateCollection operation *before* applying (#10436)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 15:41:03 +02:00
Roman TitovandClaude Opus 5 0e11b6ee8b Apply ChangeAliases operation atomically (#10425)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 13:15:57 +02:00
Andrey VasnetsovandClaude Opus 5 aa6c5d8403 Global quota API (#10035)
* feat: global quota API

Memory and disk are node-wide resources, so configuring their thresholds
per collection through strict mode makes little sense. Move them behind a
single cluster-wide `QuotaManager`.

The quota config is seeded from `storage.quotas` in the settings (and so
from env vars), overridden by `quota.json` in the storage directory, and
updated cluster-wide through a new `SetQuotaConfig` consensus operation
which rewrites that file on every peer. Raft snapshots carry it too, so a
peer that joins by snapshot picks it up.

Quotas are enforced wherever the strict mode memory and disk checks used
to run, but no longer gated behind `strict_mode.enabled`: a value set in
an enabled strict mode config still wins per resource, the quota is the
default. Rejections name both the condition that tripped and the config
that governs it.

`GET /quotas` reports the config plus current utilization to global read
users; `PUT /quotas` replaces it for global manage users.

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

* fix: cover the quota endpoints in the API consistency checks

`test_all_rest_endpoints_are_covered` and the OpenAPI endpoint count both
break on any new REST endpoint. Add `GET`/`PUT /quotas` to `ACTION_ACCESS`
with their JWT access tests, and bump the expected API count. The quota
endpoints stay out of `REST_ENDPOINT_WHITELIST`: that list is for
data-plane endpoints reported per-endpoint in metrics.

Also add a Raft snapshot CBOR compatibility test — snapshots are exchanged
between peers of different versions during a rolling upgrade, so
`quota_config` must be absent-tolerant in both directions.

Review feedback: persist through `SaveOnDisk`, which already implements the
write-before-swap protocol this was doing by hand; validate the config at
both persistence boundaries, since a hand-edited quota file or a config
arriving through consensus does not pass the REST handler's validation, and
a `0%` limit would reject every update forever.

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

* test: assert seeding a quota from invalid settings persists nothing

Follow-up to review feedback claiming `SaveOnDisk::load_or_init` writes the
init value before it is validated. It does not — only `SaveOnDisk::new`
persists — but the property matters: were seeding to persist first, invalid
settings would leave a `quota.json` that fails validation on every
subsequent start, and the node could only be recovered by deleting it by
hand. Pin it down with a test.

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

* refactor: make QuotaManager the single reader of memory and disk

The quota checks measured memory and disk themselves, while the optimizer
and the WAL disk watcher each called `fs4::available_space` behind their
own ad-hoc caches. Fold all of it into QuotaManager: it owns the readings,
the freshness policy, and the limits they are compared against.

Moves the module to `lib/shard`, since the optimizer sits below `storage`
and has to reach it; `storage::quota` re-exports it, so consensus, the
`/quotas` API and StorageConfig are unchanged. The manager is installed as
a process singleton by TableOfContent, ahead of loading any collection.

- Callers hand in QuotaLimits overrides instead of a StrictModeConfig, and
  an override can now only tighten. A collection-level admin could raise
  `max_disk_usage_percent` past a cluster-wide limit that needed global
  manage rights to set; ties resolve to the quota so the rejection names
  the knob that actually has to change.
- Measurements are cached for 5s, but a reading at or above its limit is
  never reused: a rejected client retries, and freeing the resource has to
  take effect on the next request rather than a TTL later.
- `fits_on_disk` sizes an optimization against physical free space only,
  never the configured limits. Optimizations are what free a full disk, so
  the quota must not be what stops one.
- `percent_of` widens to u128 instead of saturating the multiply, which
  under-reported utilization (failing open) above ~184 PB.
- StorageConfig::quotas is optional; absent means no quota is enforced.

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

* feat: don't recover dead replicas onto a node at a resource limit

Recovering a dead replica pulls a whole copy of its shard onto this node.
If it is already at its memory or disk quota that transfer cannot finish,
and starting it only pushes the node further past the limit. Skip it and
reconsider on a later sync, once the resource frees up.

Adds QuotaManager::check_capacity for work that lands bytes here without
being an update. Unlike fits_on_disk the configured limits do apply:
taking on a replica is not what frees a full node, so there is no deadlock
to avoid by letting it through.

The check is hoisted out of the per-shard loop because a node over its
limit re-measures on every call, so checking per dead shard would cost a
statvfs each. It is free when no quota is configured.

Also trims the comments across the quota module, which had grown well past
what the code needs.

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

* test: drop trivial and duplicated quota tests

Six tests removed, ~140 lines, with no loss of coverage:

- a_rejection_names_the_knob_that_has_to_change asserted that a format!
  contains its own literals; the message is covered end-to-end by the
  override test and by test_global_quota.py.
- a_node_over_its_quota_has_no_capacity_to_take_on_a_replica was 30 lines
  for check_capacity, a one-line delegation to check_update the test above
  it already calls.
- a_rejecting_measurement_is_never_served_from_the_cache duplicated the
  meter test, which proves the same rule with an injected reader instead
  of inferring it from the real filesystem.
- free_space_is_reported_without_enforcing_anything covered a one-line
  accessor, and its point is what the fits_on_disk test is for.
- The two resolve tests and the three meter tests each collapse into one.

DiskFit::Unknown keeps its coverage as two lines inside the fits_on_disk
test rather than its own fixture. The snapshot compat pair becomes one
test: the second only asserted cluster_metadata.is_empty(), which says
nothing about quotas — the real check was the deserialize, now an expect
that states it.

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

* fix: re-measure free space as the disk fills, and drop a Windows-only assert

Two CI failures, both from this branch.

e2e test_low_disk: the DiskUsageWatcher I replaced escalated to checking
on every call once free space fell below 512 MB. Folding it into the quota
manager lost that — available_bytes passed no limit, so a reading was
reused for the full 5s however little space was left. On a disk filling as
fast as that test fills it, 5s blind is enough to actually run out and the
WAL write dies instead of returning "No space left on device".

available_bytes now takes a watch_below level and never reuses a reading
under it, which is what the old ladder was expressing. The watcher passes
max(min_free, 512 MB), so the escalation point is back; above it the 5s
cache still costs fewer syscalls than the old 128-call ladder. fits_on_disk
gets the same rule by passing required_bytes, so a merge that does not fit
re-checks rather than sitting on a stale sample.

Windows: fits_on_disk on a missing path was asserted to be Unknown, but
GetDiskFreeSpaceEx resolves up to the containing drive and succeeds — as
common::disk_usage's own test documents. Dropped; the branch is a two-line
else and is not portably reachable.

Also renames an_optimization_is_sized_against_the_disk_not_the_quota, which
needed explaining to be understood.

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

* feat: report the quota config in telemetry

Reads it from the quota manager rather than the settings, so it is the
config the node is actually enforcing: a peer that missed a consensus
update reports what it is applying, not what the cluster agreed on.

Gated on global access, the same access `GET /quotas` requires, and left
out of `PeerTelemetry` — a quota is per-node state, so each peer reports
its own.

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

* fix: regenerate OpenAPI, and cover the quota in the telemetry key sets

Two CI failures from the previous commit.

Referencing QuotaConfig from TelemetryData moves its definition earlier
in `components/schemas`, because TelemetryData is generated ahead of
QuotaStatus. Regenerated rather than hand-patched, so the schema is a
pure move.

test_telemetry_detail asserts the exact set of top-level telemetry keys.
The quota is reported at every level, including 0 — it is three scalars,
it is the default the endpoint serves, and it is what explains an update
being rejected — so both key sets gain it.

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

* feat: remove `max_disk_usage_percent` from strict mode

Disk is a node-wide resource, so a per-collection percentage of it never
meant anything a caller could act on: the limit describes how full the
*node* is, and which collection the write happens to target has nothing
to do with it. The global quota is where it belongs.

It shipped in 1.18.2 without documentation, so this drops it outright
rather than deprecating. Removal is soft in every direction: StrictModeConfig
has no `deny_unknown_fields`, so a client still sending it gets it ignored
rather than a 400, and the same struct deserializes the persisted collection
config, so collections created on 1.18.2+ keep loading. Proto field 22 is
reserved so the number is never reused.

The e2e test becomes a quota test — the fixture and the timing are the
interesting parts and they carry over unchanged; only how the threshold is
configured differs.

`max_resident_memory_percent` was documented and stays for now.

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

* refactor: enforce the strict mode memory limit outside the quota

`max_resident_memory_percent` was folded into the quota as an override,
which meant the quota check had to know about strict mode, and retiring
the setting would mean unpicking `EffectiveLimit` and `LimitSource` from
the resolution logic.

It is now a check of its own in `verification/mod.rs`, next to the strict
mode checks it belongs with, borrowing only the measurement from the quota
manager — which stays the node's single reader of process memory, so both
checks still share one reading. Deleting the setting later is deleting one
function and its one caller.

`QuotaManager::check_update` takes no arguments and consults the quota
alone. A collection can still only tighten the limit for itself, because
its own check runs in addition rather than in place of the quota's, and
each rejection now names the config that has to change without having to
carry a `LimitSource` to say so.

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

* refactor: enforce the quota on the update path, not in strict mode

The quota check sat inside `check_strict_mode_toc_batch` only because that
was the one place holding the collection's strict mode config. It doesn't
need one any more, and the placement had a real cost: coverage depended on
each handler remembering to ask for a strict mode check, and four of the
internal update RPCs do — `sync_internal`, which moves the most bytes onto
a node, does not.

It now runs in `Collection::update_from_client` and `update_from_peer`,
which every update passes through. `update_from_client` checks ahead of the
shard split, so an operation is accepted or refused whole rather than
landing on some shards and being refused by others.

Classification moves with it, from ~10 `consumes_memory` impls on request
DTOs to one exhaustive `CollectionUpdateOperations::consumes_quota`. The
internal enum has variants — raw upserts, conditional upserts, the syncs —
that have no client-facing request type, so per-DTO impls structurally
could not classify them.

Shard-transfer syncs stay excluded, as they are today: a transfer is sized
up once before it starts, and refusing its batches partway abandons work
that is nearly done only for it to restart from the beginning.

Index and named-vector creation reach shards through consensus, past this
check — a peer must not refuse what the cluster agreed to — so they keep
their pre-consensus check, now against the quota manager directly.

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

* feat: deprecate `max_resident_memory_percent` in strict mode

Same reason the disk threshold went: memory is node-wide, so a
per-collection percentage of it caps how full the *node* is, which has
nothing to do with which collection is being written to. The node-wide
quota caps it once for everything.

Unlike the disk threshold this one shipped documented, in 1.18.0, so it
keeps working — as a limit a collection can tighten for itself, never lift
— and gets the usual markers: `#[deprecated]` on both Rust structs,
`[deprecated = true]` on proto field 21, and `deprecated: true` in the
OpenAPI schema, which schemars derives from the attribute.

The note names 1.21 as the removal. Recording a version matters here: the
audit in docs/plans/overdue-deprecations.md found that this repo has never
written a removal deadline down, and members of the 1.15.0 deprecation
batch are still in tree.

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

* fix: reconcile the quota readers with #9891

#9891 landed effective (cgroup) figures in telemetry while this branch was
making QuotaManager the single reader of memory and disk. Two collisions,
neither of which git sees.

`segment::utils::mem::total_memory_bytes` is now a shared accessor with a
5s TTL, so a cgroup resize is picked up. The quota module had its own
`OnceLock` copy that froze the value at startup — exactly what #9891 set
out to fix — so it delegates to the shared one instead.

Telemetry's new `disk_size` called `common::disk_usage::disk_usage`
directly. That reader lost its TTL cache on this branch when the caching
moved into the quota manager's meter, so it would have taken an uncached
`statvfs` on every telemetry request, and it put a second disk reader back
in the tree. It goes through `QuotaManager::disk_capacity_bytes` now,
sharing the reading the quota check already takes. Verified it still
reports the storage filesystem, matching `df`.

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

* refactor: split the quota manager by what each half does

`manager.rs` had grown to 450 lines holding three separate jobs: owning
the config and its file, taking the readings, and comparing one against
the other.

- `manager/store.rs` — the `Store` enum, `QUOTA_CONFIG_FILE`, and config
  validation, which is now the store's own business rather than something
  every caller has to remember to do first.
- `manager/measure.rs` — every reading, and `DiskFit`. The "nothing else
  calls `statvfs` or reads process RSS" claim is now checkable by looking
  at one file.
- `manager/enforce.rs` — `check_update` / `check_capacity` and the
  threshold comparison.
- `manager/mod.rs` — the struct, its construction, and the config
  accessors: what a reader needs to see first.

Tests move with their subject. No behaviour change: `set_config` used to
validate before delegating to the store, and now the store validates on
write, which is the same order of operations from the outside.

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

* fix: count copy-on-write deletes toward the quota

Dropping a vector or a payload key does not free anything on its own:
copy-on-write rewrites the point to produce the version without that
field, so storage grows first and is only reclaimed once the optimizer
gets to it. Gating those as if they were reclaiming space let a full node
keep taking writes that make it fuller.

Deleting whole points stays exempt. That is the one operation that has to
work on a node at its limit, or there is no way back under it.

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

* docs: say that the quota's reported usage is per node

`GET /quotas` returns one cluster-wide config and one set of utilization
figures, which reads as though both describe the cluster. They do not:
memory and disk are node-local, so `usage` is whatever the peer that
served the request is seeing, and a peer under its limit says nothing
about the others.

Also corrects `resident_memory_percent`, which claimed to be a share of
total system memory. It is a share of the memory available to the
process, which under a cgroup is the limit rather than the host's RAM.

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

* feat: treat a node over its quota as a failed replica, not a bad request

A quota rejection described the request as invalid (400) and was classified
non-transient, which is how the replica set recognises errors that every
replica would produce alike. A quota is the opposite: the input is fine and
the answer depends on which machine you ask. On the default `wait=false`
path that combination silently dropped the write — `update.rs` only
deactivates transient failures when nothing completed — leaving the replica
Active and permanently missing data its co-replicas had.

It is now `InsufficientStorage`, transient, HTTP 507 / gRPC
`ResourceExhausted`. So a node that is out of room is handled like one that
is offline:

- last active replica, or every replica over quota: nothing could take the
  write, and the client is told the cluster is out of room.
- more than one replica: the full node is deactivated through the same path
  a dead peer takes, and the update stands if enough replicas accepted it.
  `check_capacity` already keeps recovery off that node until it has room.

The check also moves off `update_from_client`, which applied the
coordinator's own limit to the whole operation even when it held no replica
of the shards being written. Each replica set now gates its own local write
and records the refusal as a failure of this peer, so a node only ever
answers for itself.

`ResourceExhausted` is shared with rate limiting, and the reverse conversion
mapped it straight to `RateLimitExceeded` — a forwarded rejection came back
as 429. Statuses now carry a marker so the two stay distinguishable across
the wire.

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

* feat: report quota pressure per node, and across the cluster

A quota is node-local, so finding out which node has hit one meant asking
each of them in turn — and nothing at all showed up in monitoring.

`/metrics` gains a `quota_exceeded` gauge for the local node. It is emitted
only while the quota is enabled: with it off the value would be a constant
0 that says nothing about the node, and an alert built on it would go quiet
rather than fire if someone disabled the quota. Telemetry's `quota` field
carries the same verdict alongside the config, since that is where the
metric is derived from.

`GET /quotas` now answers for the whole cluster. A new `GetQuotaUsage` RPC
on the internal `QdrantInternal` service returns what one peer is using,
and the handler fans it out to every known peer in parallel. Peers that do
not answer are left out rather than failing the request — the nodes that
are out of room are exactly the ones most likely to time out, and a partial
answer still names them. Outside distributed mode the field is absent
rather than a map of one.

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

* feat: report the quota metric per resource

`quota_exceeded` was one flag for the whole node, which does not say what
to go and fix — disk is freed by deleting or optimizing, memory by
unloading. It now carries a `resource` label:

    quota_exceeded{resource="memory"} 0
    quota_exceeded{resource="disk"} 1

A resource with no limit gets no series at all, for the same reason the
metric is absent while the quota is disabled: a series that can never reach
1 reads as healthy and would quietly carry an alert that cannot fire.

`QuotaManager::exceeded` returns the per-resource verdict, with `None` for
a resource this node does not cap. Telemetry reports the same breakdown,
since the metric is derived from it. The peer usage RPC keeps a single
flag — it sits next to both percentages, so it only has to answer "is this
peer refusing writes".

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

* fix: drop a no-op error conversion the linter caught

`check_global_access` already returns a `StorageError`, so mapping it
through `StorageError::from` converted the type to itself and tripped
`clippy::useless_conversion`.

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

* feat: hold a tripped quota until usage clears a release margin

A resource resting on its limit crosses it in both directions on the noise
between two readings, and each crossing is expensive: the node refuses a
write, its replica is deactivated, usage dips, recovery starts sending a
whole shard copy back, and the arriving data pushes it over again. The loop
sustains itself, and every lap costs a shard transfer.

A limit now trips at its configured value but only clears once usage has
fallen 5 percentage points below it, so the crossing has to be real. The
margin is floored at 1%, since a limit smaller than the margin would
otherwise be impossible to fall back under and would strand the node.

The verdict is carried on the manager rather than recomputed, which makes
it the thing reporting shows: expect `exceeded` to be set while the
utilization next to it is already back under the limit. Rejections say so
too, rather than claiming a limit that is no longer exceeded:

    Disk usage is at 87% of total capacity. It reached the configured limit
    of 90% and has to fall below 85% before this node takes writes again.

Changing the config clears the verdicts. New limits are a deliberate act,
and should not be held back by the margin of a limit that no longer exists.

Both resources are now evaluated on every check instead of stopping at the
first failure, so a verdict is never left behind reporting a reading that
has since been superseded.

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

* feat: make the quota release margin configurable

5 points is a guess about how noisy a deployment's usage is, which is not
something one number can be right about: a node whose disk moves in
gigabyte steps needs a wider margin than one that creeps, and an operator
who wants the old flip-on-every-reading behaviour should be able to ask
for it.

`release_margin_percent` joins the rest of the quota config, so it seeds
from `QDRANT__STORAGE__QUOTAS__RELEASE_MARGIN_PERCENT`, replicates through
consensus, and changes with `PUT /quotas`. Defaults to 5 and is filled in
when a request omits it, so it always answers with the margin actually in
force rather than leaving the caller to assume one. `0` releases as soon as
usage is back under the limit.

`QuotaConfig` grows a hand-written `Default` for it, since deriving one
would have quietly defaulted the margin to 0 and disabled the hysteresis
for anyone constructing a config in code.

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

* refactor: leave the release margin unset by default, and hold verdicts in atomics

`release_margin_percent` is `null` unless someone sets it, rather than
materialising 5 into every config. A quota written today then does not pin a
number a later release may want to revise, and `{"enabled": false}` still
round-trips as itself. `QuotaConfig::limits` resolves it, next to `enabled`,
so enforcement never sees the unset case.

The verdicts move from a `Mutex<QuotaExceeded>` to one `AtomicBool` per
resource. They are judged independently and nothing reads them as a pair, so
the lock only added contention to the path every update takes; a verdict
that races a concurrent check is re-decided by the next one from a fresh
reading.

That also drops the tri-state. Only "was this over its limit" has to survive
between checks — whether a resource is enforced at all follows from the
config and the reading, so it is derived when reporting rather than stored.

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

* docs: drop a comment arguing with a design that was never here

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:20:34 +02:00
0a16a62f99 feat: io_uring setting to control which components use the io_uring backend (#10008)
* feat: `io_uring` setting to control which components use the io_uring backend

A few components have both an mmap and an io_uring variant reading the very
same files: the immutable dense vector storages, the single-file TurboQuant
storage, and the mmap payload storage. Until now the choice was a side effect
of `async_scorer` — a vector-search knob — plus, for the payload storage, a
feature flag that was parked off because io_uring is ~2x slower than mmap when
the data fits the page cache (#9310, #9409).

Add `storage.performance.io_uring`, optional, with two modes:

- unset (default): unchanged behaviour. The vector storages keep following
  `async_scorer`; the payload storage stays on mmap.
- `disabled`: no component uses io_uring.
- `auto`: a component uses io_uring when its memory placement is `cold` (data
  is left on disk, so reads hit the disk and there is something to gain), its
  feature flag allows it, and the kernel supports io_uring. Components meant to
  sit in RAM keep using mmap.

The decision lives in one place, `segment::common::io_uring::use_io_uring`, so
the openers no longer each reach for the async-scorer global. Kernel support is
now probed up front through `is_io_uring_supported()` instead of opening a file
and falling back on error.

`async_payload_storage` now defaults to on: it no longer decides anything by
itself, it only lifts the ban, and the payload storage no longer follows
`async_scorer` at all — so turning it on cannot silently move an existing
`async_scorer: true` deployment onto the slower path.

Which backend a component ended up on depends on the config, the placement and
the kernel at once, so report it in `SegmentInfo`: `vector_data[name].io_backend`
and `payload_storage_io_backend`, both `"mmap" | "io_uring"`, absent for
components that have no such choice.

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

* Trim comments, drop trivial tests

Two tests were only restating their own implementation: `test_mode_round_trip`
round-tripped the encode/decode pair next to it, and `test_io_uring_config`
checked that serde deserializes a two-variant enum. The mode matrix test stays,
it is the one that pins the semantics.

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

* Flatten `IoBackend` in OpenAPI, derive `JsonSchema` for `IoUringMode`

Per-variant doc comments on a plain string enum make schemars emit a `oneOf`
of anonymous single-value objects instead of a flat `enum`. Move the variant
descriptions into the enum doc, as `Memory` and friends already do.

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

* Update lib/segment/src/vector_storage/turbo/turbo_vector_storage.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Update lib/segment/src/types.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Update lib/segment/src/types.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Update lib/segment/src/types.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Update lib/segment/src/types.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* upd openapi schema

* Update lib/common/common/src/flags.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Require kernel io_uring support in the async-scorer fallback

`use_io_uring` returned `get_async_scorer()` verbatim when the `io_uring`
setting is unset, so an enabled async scorer on a kernel without io_uring
opened the io_uring storage, failed, and fell back to mmap with an error
log per segment. Gate that branch on `is_io_uring_supported()` too, like
`Auto` already is, so the component just stays on mmap.

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

* upd openapi schema

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>
2026-07-28 21:17:36 +02:00
Andrey VasnetsovandClaude Fable 5 94fdd0e746 Support memory placement in service-level storage config defaults (#9950)
* Support memory placement in service-level storage config defaults

Follow-up to #9684: `storage.payload.memory` and
`storage.collection.vectors.memory` set service-wide placement defaults
for newly created collections, deprecating `storage.on_disk_payload` and
`storage.collection.vectors.on_disk`.

Defaults resolve as: request `memory` > request legacy flag > service
`memory` > service legacy flag; exactly one level is filled to avoid
spurious memory-vs-legacy mismatch warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Mark hnsw_index.on_disk deprecated in config.yaml, document memory option

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:35:52 +02:00
ab0d3ecc62 Add unified memory: cold|cached|pinned placement parameter for collection components (#9684)
* Add unified `memory: cold|cached|pinned` placement parameter for collection components

Introduce a single `memory` parameter that controls how each collection
component's data is held in RAM, replacing the inconsistent zoo of
`on_disk` / `always_ram` / `on_disk_payload` flags:

- `cold`: not pre-loaded from disk, cached with usage
- `cached`: pre-populated into page cache on load, evictable under pressure
- `pinned`: materialized on heap, never evicted by cache pressure

The parameter is available on dense vectors, HNSW config, all quantization
configs, the sparse index, all payload field index types, and payload
storage (as a new `payload: { memory }` sub-object on collection params).
When set, it overrides the deprecated legacy flag; when unset, behavior is
unchanged. Legacy flags are marked deprecated (Rust + proto) but keep
working; conflicts are resolved in favor of `memory` with a warning.

New capabilities enabled by the tri-state model:
- HNSW graph links can be pinned (first production caller of the existing
  `GraphLinksResidency::Pinned`)
- sparse mmap index, quantized vectors and on-disk payload field indexes
  gain a `cached` tier (mmap + populate on open)

`pinned` is rejected by API validation for components without a heap
variant (dense vector storage, payload storage). Low-memory mode degrades
placements at load time via `Memory::clamp_to_low_memory`, matching the
existing `prefer_disk`/`skip_populate` behavior. Effective-placement
comparison in the config-mismatch optimizer avoids spurious rebuilds when
the same placement is expressed through the new parameter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix gpu-gated tests for the new `memory` field

CI clippy runs with --all-features, which compiles the gpu-gated tests
that were missed locally: add the `memory` field to config literals and
allow deprecated placement params, same as in the rest of the tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add OpenAPI tests for memory placement, keep sparse config downgrade-clean

- OpenAPI tests: create/update collections with `memory` on every component,
  assert the parameters are echoed in collection info, assert legacy-only
  collections expose no new fields, and assert `pinned` is rejected (422)
  for dense vector storage and payload storage on both create and update.
- Persist only the explicitly requested `memory` parameter in
  `sparse_index_config.json` instead of the legacy-resolved placement, so
  configurations using only the deprecated `on_disk` flag keep byte-identical
  files that older Qdrant versions load without unknown fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Validate collection meta ops at construction, not only in the API layer

The `memory: pinned` rejection for dense vectors and payload storage
lived in `Validate` impls on the internal request types, which only ran
through the REST actix extractor. gRPC validates just the proto message,
so a gRPC client could persist `pinned` where it is not supported and
have it silently treated as `cached`.

Run the derived validation in `CreateCollectionOperation::new` and
`UpdateCollectionOperation::new` instead: the constructors are the
common chokepoint for all API paths, before the operation is proposed
to consensus. This covers every validator on these types, not just the
`memory` checks, and keeps consensus-apply unaffected so mixed-version
clusters never reject already-committed operations.

`UpdateCollectionOperation::new` becomes fallible; `remove_replica` now
uses `new_empty` since it carries no user config. Regression tests drive
the gRPC conversion path and assert `InvalidArgument` for `pinned` on
create and update, with `cold`/`cached` accepted as a control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 14:32:51 +02:00
Andrey VasnetsovandCursor Agent d02ef48f24 Dynamic cpu pool (#8790)
* [AI] inptoduce CPU process measurement

* use parking_lot + 4 seconds refresh rate

* [AI] AdaptiveSearchHandle

* fmt

* openapi schema

* keep Runtime field

* fix test

* [AI] instead of async semaphore, use 2 runtimes

* Adjust usage window to 2 seconds

* Address CodeRabbit review comments for dynamic CPU pool

- OpenAPI / telemetry: user-facing cpu_cores_used description (2s window, when null).
- process_cpu_usage: backoff after procfs errors; serialize Linux unit tests on CACHE.
- Docs: decouple runtime thread comments from hardcoded 4× multiplier; name search_runtime in test.
- consensus test: replace stale runtime comment.

Made-with: Cursor

* chore(openapi): regenerate master spec via generate_openapi_models.sh

Replace hand-edited cpu_cores_used description with output from
schema_generator + merge pipeline so openapi_consistency_check passes.

Made-with: Cursor

---------

Co-authored-by: Cursor Agent <agent@cursor.com>
2026-04-27 12:56:29 +02:00
Andrey Vasnetsovandtimvisee f321c9fe37 Low Memory mode (#8714)
* [AI] implement parameter + cover populate + cover quantized vectors

* telemetry OpenAPI schema

* [AI] hook immutable payload indexes

* fmt

* do not populate payload index if we fallback to mmap

* Reformat

* Also suppress universal IO disk cache population

---------

Co-authored-by: timvisee <tim@visee.me>
2026-04-20 11:33:35 +02:00
f5625fd85e Return error instead of panicking for corrupted aliases file on startup (#8293)
* Return error instead of panicking for corrupted aliases file on startup

* common::fs::ops: provide file name in error messages

And drop FileStorageError in favor of std::io::Error, since we always
convert all kinds of errors into ServiceError anyway.

* TableOfContent:🆕 return errors instead of panics

Also, drop context strings. We use fs_err anyway, that should be enough.

---------

Co-authored-by: leohenon <77656081+lhenon999@users.noreply.github.com>
Co-authored-by: xzfc <xzfcpw@gmail.com>
2026-04-10 10:45:17 +02:00
Andrey Vasnetsov b9cc0778f5 add tracing ID into audit logging (#8402)
* add tracing ID into audit logging

* format and refactor

* clippy

* review fixes

* fmt
2026-03-17 00:00:06 +01:00
xzfc 4cabb7fd8e Merge io and memory into common (#8155)
* Unify parking_lot/arc_lock feature

* Move lib/common/{io,memory}/* -> lib/common/common/*

- Mmap-related items are grouped into `common::mmap` sub-module:
  - `memory/src/chunked_utils.rs`      -> `common/src/mmap/chunked.rs`
  - `memory/src/madvise.rs`            -> `common/src/mmap/advice.rs`
  - `memory/src/mmap_ops.rs`           -> `common/src/mmap/ops.rs`
  - `memory/src/mmap_type_readonly.rs` -> `common/src/mmap/mmap_readonly.rs`
  - `memory/src/mmap_type.rs`          -> `common/src/mmap/mmap_rw.rs`
- Filesystem-related items are grouped into `common::fs` sub-module:
  - `common/src/fs.rs`          -> `common/src/fs/sync.rs`
  - `io/src/file_operations.rs` -> `common/src/fs/ops.rs`
  - `io/src/move_files.rs`      -> `common/src/fs/move.rs`
  - `io/src/safe_delete.rs`     -> `common/src/fs/safe_delete.rs`
  - `memory/src/checkfs.rs`     -> `common/src/fs/check.rs`
  - `memory/src/fadvise.rs`     -> `common/src/fs/fadvise.rs`
- Rest is moved straight into `common`:
  - `io/src/storage_version.rs` -> `common/src/storage_version.rs`

The old `io` and `memory` are now hollow crates that re-export items
from `common`. These hollow crates will be removed in next commits.

* Replace uses of `io` and `memory` with new paths in `common`

Since `io` and `memory` are just re-exports of `common`, these
replacements are no-op.

* Remove `io` and `memory` crates
2026-02-17 10:58:59 +01:00
Andrey VasnetsovandTim Visée 076aa13399 HTTPS snapshot links (#8095)
* [manual] generate snapshot download link with respect of enabled tls

* Simplify http(s) selection

---------

Co-authored-by: Tim Visée <tim+github@visee.me>
2026-02-10 18:13:15 +01:00
Andrey Vasnetsovandcoderabbitai[bot] 1ae2070e1c audit logging (#8071)
* initial implementation

* internal logging type

* wrap more places into auth function

* [manual refactor] use &Auth in .toc fucntion - minimise direct .access()

* [manual refactor] fmt

* [manual refactor] remove unannotated .access in creation of snapshots

* [manual refactor] remove unannotated .access in cluster telemetry

* [manual refactor] remove unannotated .access

* [manual refactor] do not log /metrics api access

* [manual refactor] do not run the service if audit logging failed to init

* [manual refactor] make auditable operation names for point and cluster updates

* [manual refactor] remove excess cloning of auth object

* [manual refactor] fmt

* [AI] instead of manual writing into the file, use tracing_appender crate

* [AI] simplify configuration to match internal crate

* fmt

* [manual refactor] cover staging options

* [AI] refactor x-forwarded-for handelling

* [manual refactor] use consts for x-forwarded-for + recover handelling for RFC case in tonic

* Update src/tonic/forwarded.rs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* [manual] review fix: use FORWARDED in actix

* [manual] review fix: do not log strict mode checks

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-09 19:16:20 +01:00
bc3f9a1c60 feat: Optimize the collection loading process during startup (#8053)
* feat: optimize the collection loading process during startup

* simplify config options

* chore: update config.yaml

* Remove global state

* Disallow zero values

* Remove intermediate map

* Rename ConcurrentLoadConfig to LoadConcurrencyConfig

* Flatten config

* Accept string inputs from environment variables

* Fix test compilation

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: chenliang <chenliang@chenliangdeMacBook-Air.local>
Co-authored-by: timvisee <tim@visee.me>
2026-02-04 17:35:33 +01:00
921b1ce83d Prevent unoptimized updates (#7643)
* wip: wait for optimization before applying update

* implement api parameter

* nits

* fix deadloack when no optimizers are running

* release handle mutex

* Derive PartialEq

* Remove unused function

* fix missing kb conversion in threshold

* Update lib/api/src/grpc/proto/collections.proto

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

* sync comment change

---------

Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>
Co-authored-by: timvisee <tim@visee.me>
Co-authored-by: Tim Visée <tim+github@visee.me>
2026-01-14 16:37:03 +01:00
xzfc f1ee3895b6 Safe delete (#7830)
* Replace `Option<Segment>` with `enum LoadSegmentOutcome`

* Replace some Path/PathBuf with str/String

* Rename field Segment::{current_path -> segment_path}

* safe_delete
2026-01-05 08:54:29 +00:00
Andrey Vasnetsov dba26be155 Merge pull request #7835
* alt api key

* rm typo

* fix key length check logic

* differnt dirs for tests

* fix tests

* rm unused
2026-01-02 13:18:53 +01:00
Andrey Vasnetsov 925d5127c8 remove init_from (#7454)
* wip: remove init_from

* remove init_from tests

* upd schema

* rm test
2025-10-29 17:37:02 +01:00
Andrey Vasnetsovandtimvisee e744bd6014 implement custom collection metadata (#7123)
* implement custom collection metadata

* persist metadata change

* Also unset a key in the test

---------

Co-authored-by: timvisee <tim@visee.me>
2025-08-26 11:49:17 +02:00
Tim Visée a5869beb99 Deprecations for Qdrant 1.15.0 (#6892)
* Deprecate init_from

* Mark memmap_threshold as deprecated

* Mark locks API as deprecated

* Mark RBAC collection access payload filter as deprecated

* Allow deprecations in tests and benchmarks
2025-07-17 16:10:45 +02:00
Andrey Vasnetsov c11b96301b remove max indexing threads from config (#6760)
* remove max_optimization_threads from config.yaml

* fmt
2025-06-26 10:26:09 +02:00
xzfc 706571d8b4 Configurable HNSW healing threshold (#6756) 2025-06-25 10:22:18 +00:00
Andrey Vasnetsovandtimvisee dc203e11b0 Add setting to limit max number of collections (#6548)
* add setting to limit max number of colelctions

* upd openapi

* Add max_collections to config

---------

Co-authored-by: timvisee <tim@visee.me>
2025-05-20 13:40:56 +02:00
Andrey Vasnetsovandtimvisee f7d0814ab6 IO resource usage permit (#6015)
* rename cpu_budget -> resource_budget

* clippy

* add io budget to resources

* fmt

* move budget structures into a separate file

* add extend permit function

* dont extend existing permit

* switch from IO to CPU permit

* do not release resource before aquiring an extension

* fmt

* Review remarks

* Improve resource permit number assertion

* Make resource permit replace_with only acquire extra needed permits

* Remove obsolete drop implementation

* allocate IO budget same as CPU

* review fixes

---------

Co-authored-by: timvisee <tim@visee.me>
2025-02-20 09:05:00 +01:00
Arnaud Gourlay 4eee1b81d6 API validation for unique dense and sparse vector names (#5808)
* API validation for unique dense and sparse vector names

* less alloc

* test empty name as well

* quotes instead of ticks
2025-01-15 16:51:05 +01:00
Tim ViséeandArnaud Gourlay a69ddffed8 Enable mmap storage using blob store for payloads by default (#5783)
* Enable mmap storage using blob store for payloads by default

* adapt test for new mmap payload storage

---------

Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>
2025-01-13 17:45:14 +01:00
Tim Visée e1a4968d5c Enable mmap storage using blob store for sparse vectors by default (#5784) 2025-01-13 14:46:55 +01:00
Luis Cossío 2de122adea Compatibility for mmap sparse vectors (#5454)
* implement mmap sparse vector storage

* add to VectorStorageEnum

* clippy

* add tests, fix both simple and mmap storages

* smol correction on total_vector_count

* add sparse storage type to config

* fix reading config without storage type

* generate openapi

* use blob_store by path

* hidden setting to enable new storage

* validate existing path in `BlobStore::open()`

* use new dir for each sparse vector name

* fix and rename `max_point_offset`

Plus some extra refactors

* add storage compat test, to always check both storages work

* fix opening of storage + other misc fixes

* FIX!!!

`Unset` operations in the Tracker weren't updating the
`next_pointer_id`. So, when reopening the storage, those points wouldn't
get marked as deleted in the bitslice, thus creating the illusion that
they should exist, when they did not.

* refactor naming from `iter_*` to `for_each_*`

* fix checking for BlobStore existance

* fix typo

* fix error message

* better docs for open_or_create

* fix after rebase
2024-11-27 11:45:16 -06:00
7e7348613e Add UUID to collection config (#5378)
* Add UUID to collection...

...and recreate collection, when applying Raft snapshot, if UUID of collection is different

* fixup! Add UUID to collection...

Remove UUID field from gRPC and exclude it from OpenAPI spec 🤡

* fixup! fixup! Add UUID to collection...

Always generate collection UUID 🤦‍♀️

* Raft snapshot recreate collection no expose UUID (#5452)

* separate colleciton config structure from API

* fmt

* Update lib/collection/src/operations/types.rs

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

---------

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

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Tim Visée <tim+github@visee.me>
2024-11-15 19:03:50 +01:00
Luis Cossío d902422203 Backward compatibility for mmap payload storage (#5398)
* support mmap storage backward compat

* fix clippy

* review fixes + bump + restore Cargo.lock

* fix clippy

* map_err instead of match

* add sanity tests for payload storage trait

* fix clippy

* error conversion

* test persistance too

* add config to enable mmap storage (#5434)
2024-11-13 08:49:42 -06:00
Andrey Vasnetsov edfb52ea4b Async fixes (#5426)
* expose async in telemetry

* move async scorer config to proper section (as it was documented)

* temporary disable mmap prefault

* rollback prefault_mmap_pages

* upd openapi
2024-11-12 12:31:03 +01:00
Arnaud Gourlay a9c45313e7 Remove redundant clones (#5402)
* Remove redundant clones

* fmt
2024-11-08 13:02:23 +01:00
Jojii 3595310706 [Strict mode] migrate toc_new -> toc (#5120)
* Replace all remaining calls of toc() with toc_new

* mirgate toc_new -> toc
2024-09-25 15:15:58 +02:00
e9bad464ff [Strict-Mode] Basic implementation (#4887)
* add CollectionRequestVerification

* add to api

* rebase

* improve implementation

* implement strict mode for SearchRequest+Batch

* improve code + fix Clippy

* improve error handling

* restructure StrictModeVerification trait

* generate docs

* check `enabled` option

* review remarks

* rename StrictModeConfigDiff in grpc

* use missing payload detection from issue api

* performance improvement

* decouple extractor from issues (#4945)

* some review remarks

* don't default to empty functions in StrictModeVerification trait

* update openapi

* filter_limit => query_limit

* replace discovery_max_context_size and recommend_max_examples with max_input_examples

* review remarks

* review fix: include possible index types into error message

* review remarks

---------

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>
Co-authored-by: generall <andrey@vasnetsov.com>
2024-08-29 10:49:23 +02:00
xzfc 26b0f08455 Tidy up integration tests (#4756) 2024-07-26 21:28:37 +02:00
Jojii 3005de2a01 Add optimizer_overwrite config option (#4317)
* add config option

* add optimizers_config to optimizer calls

* also add for tests

* add to build_optimizers

* rename function parameter
2024-05-31 09:02:44 +02:00
Jojiiandgenerall 6249623f35 Add vectors and quantization options and defaults (#4299)
* add vectors and quantization options and defaults

* remove default quantization settings and add config options for consensus

* change config structure

* change parameter hierarchy

* apply new config values

* review changes

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-05-23 14:16:00 +02:00
Kenshin Tanaka 0d46aeb460 Implement S3 snapshot manager (#4150)
* Add SnapshotsStorageConfig enum(Local or S3) and deserialize implementation

* [refactor]  use snapshots_config instead of s3_config

* update config

* add AWS official`aws-sdk-s3`

* implement store_file() WITHOUT error handling

* implement list_snapshots

* implement delete_snapshot

* run `cargo +nightly fmt`

* delete println

* implement get_stored_file

* Add error handlings

* Refactor AWS S3 configuration and error handling

* fix bugs

* create an empty test file

* fix `alias_test.rs` for StorageConfig type

* tempolary delete some test and try s3 test

* Update integration-tests.yml to use snap instead of apt-get for installing yq

* Update integration-tests.yml to use sudo when installing yq

* add sudo

* make (full/non-full) snapshots downloadable

* debug

* small fix

* Add S3 endpoint URL configuration option

* fix

* fix

* debug

* fix endpoint

* update to http://127.0.0.1:9000/

* update

* fix

* fix `#[get("/collections/{collection}/shards/{shard}/snapshots/{snapshot}")]` for s3

* put original tests back

* refactor

* small fix (delete println & echo)

* use object_store and refactor

* create snapshot_storage_ops and implement

* Refactor get_appropriate_chunk_size function to adjust chunk size based on service limits and file size

* cargo +nightly fmt --all

* make it more abstract

* Refactor SnapshotsStorageConfig deserialization in SnapShotsConfig

* small update

* small fix

* Update dependencies in Cargo.lock

* Update minio image to satantime/minio-server

* Refactor snapshot storage paths in snapshots_manager.rs and snapshot_storage_ops.rs

* Fix issue with downloaded file size not matching expected size in download_snapshot function

* add flush

* Use Streaming instead of donloading once

* apply `cargo +nightly fmt --all`

* Fix issue with opening file in SnapshotStream::LocalFS variant

* Fix error handling in SnapshotStream::LocalFS variant

* Add integration test for Shard Snapshot API with S3 storage (#7)
2024-05-10 16:54:23 +02:00
Tim Visée 8723b7e93e Add API key field to snapshot restore, fix snapshot recovery with API key (#4155)
* Add API key to HTTP client

* Add API key field to snapshot recovery requests

* Add API key to channel service

* Provide API key when doing snapshot transfer

* Configure API key header name constant in a central place

* Reformat

* Update OpenAPI spec

* Remove suffixed spaces from configuration file

* Allow to specify HTTP headers in some consensus test utility functions

* Add snapshot transfer test with configured API key

* Use random API key in test

* Fix compilation errors and clippy warnings
2024-05-02 17:10:34 +02:00
Andrey Vasnetsov 44f2744d74 Byte storage api support (#4065)
* wip: include datatype in vector params API

* generate api schemas

* propagate datatype to segment creation

* fix review

* fmt
2024-04-18 15:22:28 +02:00
28dee3821f RBAC: require access token in ToC functions (#3972)
* claims -> access

* Require access for Dispatcher::toc()

* Require Access for various collection functions

* review fixes

* comment nit

* Add periods to docsting

* Disallow creating and deleting sharding keys for non-manage access

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Luis Cossío <luis.cossio@outlook.com>
2024-04-08 22:19:43 +00:00
xzfcandgenerall eda7f8aed3 RBAC: new JWT schema (#3941)
* RBAC: new JWT schema

* Address review comments

Renames, validation, default values, drop Ord

* require access to whole collection to read its stats

* fmt

* remove unrelated

* Introduce AccessRequrements

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2024-04-03 13:10:46 +00:00
xzfc 20e8615fe4 RBAC: Introduce Access object and use it (#3925)
* Introduce Access object and use it

* Make Access non-optional

* Allow creating sharding key if collection claim is not present

* Tear up rbac crate into the main crate and storage crate
2024-03-27 15:44:26 +00:00
xzfc 10ec37ba85 RBAC support for collections API (#3863) 2024-03-20 20:22:33 +00:00
Alexandru Cihodaruandgenerall 61fa4e74e4 Snapshot store (#3643)
* [snapshots]: Define trait for snapshots managemet

* Define a trait with methods for managing snapshots after their
  creation. The goal of this trait is to allow implementation of
  snapshot management logic for different storage backends without major
  changes in the code.

Signed-off-by: Alexandru Cihodaru <alexandru.cihodaru@gmail.com>

* [snapshots]: Move snapshot deletion logic

* Move the snapshot deletion logic into the implementation of
  SnapshotStorage for LocalFileSystemConfig.
* Replace snapshot deletion logic with calls to the SnapshotStorage
  implementation.

Signed-off-by: Alexandru Cihodaru <alexandru.cihodaru@gmail.com>

* [snapshots]: Move snapshot list logic

* Move the snapshot listing logic into the implementation of
  SnapshotStorage for LocalFileSystemConfig.

Signed-off-by: Alexandru Cihodaru <alexandru.cihodaru@gmail.com>

* [snapshots]: Move snapshot creation logic

* Move the snapshot creation logic into the implementation of
  SnapshotStorage for LocalFileSystemConfig.

Signed-off-by: Alexandru Cihodaru <alexandru.cihodaru@gmail.com>

* review fixes

* fmt

* refactor full storage snapshot

* create directory on get_stored_file + make sure temp archive file is removed

---------

Signed-off-by: Alexandru Cihodaru <alexandru.cihodaru@gmail.com>
Co-authored-by: generall <andrey@vasnetsov.com>
2024-02-28 20:04:12 +01:00
Tim Visée 56171c6087 Dynamic CPU saturation integration (#3115)
* Integrate dynamic CPU saturation with public API changes

This reverts commit 08930fefa9.

* Call num_cpus only once
2024-02-05 14:08:02 +01:00
0301e39943 Dynamic CPU saturation internals (#3364)
* Move CPU count function to common, fix wrong CPU count in visited list

* Change default number of rayon threads to 8

* Use CPU budget and CPU permits for optimizer tasks to limit utilization

* Respect configured thread limits, use new sane defaults in config

* Fix spelling issues

* Fix test compilation error

* Improve breaking if there is no CPU budget

* Block optimizations until CPU budget, fix potentially getting stuck

Our optimization worker now blocks until CPU budget is available to
perform the task.

Fix potential issue where optimization worker could get stuck. This
would happen if no optimization task is started because there's no
available CPU budget. This ensures the worker is woken up again to
retry.

* Utilize n-1 CPUs with optimization tasks

* Better handle situations where CPU budget is drained

* Dynamically scale rayon CPU count based on CPU size

* Fix incorrect default for max_indexing_threads conversion

* Respect max_indexing_threads for collection

* Make max_indexing_threads optional, use none to set no limit

* Update property documentation and comments

* Property max_optimization_threads is per shard, not per collection

* If we reached shard optimization limit, skip further checks

* Add remaining TODOs

* Fix spelling mistake

* Align gRPC comment blocks

* Fix compilation errors since last rebase

* Make tests aware of CPU budget

* Use new CPU budget calculation function everywhere

* Make CPU budget configurable in settings, move static budget to common

* Do not use static CPU budget, instance it and pass it through

* Update CPU budget description

* Move heuristic into defaults

* Fix spelling issues

* Move cpu_budget property to a better place

* Move some things around

* Minor review improvements

* Use range match statement for CPU count heuristics

* Systems with 1 or 2 CPUs do not keep cores unallocated by default

* Fix compilation errors since last rebase

* Update lib/segment/src/types.rs

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Update lib/storage/src/content_manager/toc/transfer.rs

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Rename cpu_budget to optimizer_cpu_budget

* Update OpenAPI specification

* Require at least half of the desired CPUs for optimizers

This prevents running optimizations with just one CPU, which could be
very slow.

* Don't use wildcard in CPU heuristic match statements

* Rename cpu_budget setting to optimizer_cpu_budget

* Update CPU budget comments

* Spell acquire correctly

* Change if-else into match

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Rename max_rayon_threads to num_rayon_threads, add explanation

* Explain limit in update handler

* Remove numbers for automatic selection of indexing threads

* Inline max_workers variable

* Remove CPU budget from ShardTransferConsensus trait, it is in collection

* small allow(dead_code) => cfg(test)

* Remove now obsolete lazy_static

* Fix incorrect CPU calculation in CPU saturation test

* Make waiting for CPU budget async, don't block current thread

* Prevent deadlock on optimizer signal channel

Do not block the optimization worker task anymore to wait for CPU budget
to be available. That prevents our optimizer signal channel from being
drained, blocking incoming updates because the cannot send another
optimizer signal. Now, prevent blocking this task all together and
retrigger the optimizers separately when CPU budget is available again.

* Fix incorrect CPU calculation in optimization cancel test

* Rename CPU budget wait function to notify

* Detach API changes from CPU saturation internals

This allows us to merge into a patch version of Qdrant. We can
reintroduce the API changes in the upcoming minor release to make all of
it fully functional.

---------

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>
Co-authored-by: Luis Cossío <luis.cossio@outlook.com>
2024-01-31 11:56:34 +01:00
d04c552619 Limit automatic shard transfers (stateless) (#3458)
* Do not select ourselves when finding auto shard transfer source

* Limit automatic shard transfers with basic transfer count check

* Extract shard recovery transfer request logic to separate function

* Add global shard transfer tracker structure to allow rate limiting

* Count and limit incoming and outgoing shard transfers separately

* Make automatic shard transfer limit configurable

* Move shard transfer tracker from global to collection level

* Comment out new config parameters

* Fix incorrect comment

* Fix missing space in log message

* Fix negated condition

* Remove logic for requesting shard transfer on replica state change

* Check shard transfer limits in consensus sync, use consensus state

Instead of bothering with tracking proposed shard transfers, this now
purely relies on state that is already in consensus. Each time we sync
consensus, we request the maximum number of shard transfers up to the
limit at that time.

* Remove now obsolete shard transfer tracker

* Revert now obsolete changes

* Improve transfer IO counting comment, revert now obsolete code

* Fix typos

* Fix flipped variables, don't take self reference on Copyable type

* Update lib/collection/src/collection/shard_transfer.rs

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Handle incoming/outgoing transfer counts separately, don't tuple it

* Improve loop for counting incoming/outgoing transfers

* Remove unused test function

* Add consensus tests for automatic shard transfer limits

* Apply suggestions from code review

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>

* Remove debug lines from new test

* Reorder last test a bit to resolve flakyness

* We can have one more transfer for recovery on the other alive node

* Attempt to reduce test flakyness, more points and more frequent polling

* Update config/config.yaml

Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>

* Explicitly note default shard transfer limit in configuration

* Use default for shard transfer IO everywhere

* Rename transfer limit check function to be more explicit

---------

Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>
Co-authored-by: Luis Cossío <luis.cossio@qdrant.com>
2024-01-26 16:09:43 +01:00
Tim Visée c8b0b0a484 Add config property to set default shard transfer method (#3255)
* Add config option to set default automatic shard transfer method

* Also use configured shard transfer method if user doesn't specify

This is not for automatic shard transfers, but when a user initiates a
transfer manually.

* Improve comment for shard transfer method configuration

* Fix test compilation
2023-12-21 20:22:04 +00:00