Files
qdrant/docs
Andrey VasnetsovandClaude Fable 5 6633205704 [UpdateOnly] create Blobstore-backed storages append-only under a feature flag (#10154)
* [UpdateOnly] create Blobstore-backed storages append-only under a feature flag

A new `append_only_storages` feature flag, enabled by `serverless_compatible`,
switches every Blobstore creation site — the payload storage, the appendable
field indexes (numeric, map, geo, full-text) and the sparse vector storage —
to the append-only Logstore mode. One shared helper maps each site's Gridstore
layout to its Logstore counterpart, carrying the page size and compression
over; blocks and regions have no append-only equivalent. Only creation
consults the flag: an existing storage keeps its persisted mode, both modes
are always readable, so flipping the flag never strands data.

Two changes make the flag usable rather than booby-trapped:

`Logstore::delete_value` now succeeds trivially where nothing is stored, as
mutable mode does, and errors only for a stored value. The ordinary write
paths delete defensively — an index clears a slot before filling it, an empty
value is stored as a deletion — and only ever hit occupied slots when
something is genuinely mutated in place.

A segment derives `append_only_storages` from the persisted payload storage
mode when it opens — not from the flags, which may have changed since it was
created — and it forces append-only mutation semantics on itself: every
mutation clones to a fresh slot, and the same-operation slot-reuse shortcut
is disabled, since the second step of a multi-step write would rewrite a
payload row those storages cannot rewrite.

The end-to-end test runs as its own binary (feature flags are process-global)
with `serverless_compatible` on: the segment comes out holding Logstore
storages, and upserts, updates of existing points, multi-step same-operation
writes, deletes, an index build over existing points, a flush and a reload all
run against them.

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

* [UpdateOnly] assume the flag pairing instead of deriving it, trim the docs

Per review: `append_only_storages` without `append_only_mutations` is not a
state to defend against — `init_feature_flags` forces the pairing, and the
same-operation slot-reuse check reads the flag directly. That deletes the
segment-side derivation: the `append_only_storages` segment field, the
persisted-mode read at open, and the `is_append_only` accessor chain through
`Blobstore`, `PayloadStorageImpl` and `PayloadStorageEnum`.

Docstrings and comments trimmed to the guarantees; how the write paths use
them is their own business.

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

* [UpdateOnly] restructure the creation config around mode-neutral options

`CreateOptions` in the blobstore crate holds what a caller actually decides —
page size, block size, compression — and `into_config(append_only)` turns them
into the config of either mode, each taking the fields it can express. The
segment-side `storage_config` supplies only the mode, from the feature flag.

That removes the misnomer chain the previous cut left behind: nothing named
gridstore returns a config that might not be one, and no call site builds a
`GridstoreConfig` just to have its fields repacked.

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

* [UpdateOnly] keep Logstore strict; fix the callers that deleted nothing

Per review, `Logstore::delete_value` goes back to an unconditional error: a
delete reaching an append-only storage is a caller bug to fix, not a case to
absorb. The callers that issued vacuous deletes are fixed instead:

- The numeric and geo indexes only delete from the storage when their
  in-memory index actually held values at the slot — the two are written in
  lockstep, so an empty slot has nothing stored either. The map and text
  indexes already worked this way.
- The sparse storage skips the delete for keys at or past its end, where
  nothing was ever stored.

Each removed call was wasted work in mutable mode too. The e2e test now also
drives a numeric index and the sparse storage against append-only mode, and
asserts that deleting a stored sparse vector fails.

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

* [UpdateOnly] drop the same-op slot reuse; upserts write the whole point at once

The append-only path never needs a multi-step point write: the one real
multi-stepper was the shard's upsert — `upsert_point` followed by a payload
step under one operation number — and it now goes through
`upsert_moved_point`, which writes vectors and payload as one operation and
one slot. With that, the same-operation slot-reuse carve-out in
`handle_point_mutate` has nothing to carry: on an append-only segment every
mutating step clones to a fresh slot, unconditionally, and the
`append_only_storages` special case disappears with it. The version gate
skips only on strictly newer versions, so a caller that still multi-steps
stays correct — it pays a slot per step.

`PointToUpsert` now exposes the point's parts — raw vectors, decoded vectors,
payload — and both write paths are provided from them: `upsert_into` hands
the parts to `upsert_moved_point`, `write_moved` adapts them to the
copy-on-write move callback. The two hand-written `upsert_into` bodies and
the follow-up payload helper are gone.

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

* Regenerate OpenAPI for the `append_only_storages` feature flag

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

* one extra debug assertion

* fmt

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:35:55 +02:00
..
2025-07-28 13:27:03 +02:00
2024-07-17 09:24:35 +02:00
2025-07-17 14:32:18 +02:00
2025-07-17 14:32:18 +02:00
2025-07-17 14:32:18 +02:00