Commit Graph

505 Commits

Author SHA1 Message Date
Luis Cossío
e0d948980a [UIO] Open UniversalMapIndex with S::Fs (#9256)
* open UniversalMapIndex with `S::Fs`

* Not found is Ok(None)
2026-06-01 14:46:45 -04:00
qdrant-cloud-bot
c150bd5caa 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-01 11:32:25 +02:00
Daniel Boros
6c7458d0cc feat: add readonly null index open (#9197)
* feat: add readonly null index open

* feat: add get_mutability_type to ReadOnlyNullIndex

* fix: pr reviews

* simpler phantomdata

---------

Co-authored-by: Luis Cossío <luis.cossio@outlook.com>
2026-05-29 22:51:35 +02:00
xzfc
fff9c1f1c7 Migrate InvertedIndexCompressedMmap to UIO (#9144)
* Migrate InvertedIndexCompressedMmap to UniversalRead

* Rehaul search_scratch.rs (was scores_memory_pool.rs)

- Use `typed_arena::Arena` instead of `bumpalo::Bump`.
  Reason: bump don't drop.
  Drawback: `Arena::new()` is not free, and `Arena::clear()` doesn't
  exist; but this commit has workarounds.
- Use names that make more sense.

* Misc fixes

* InvertedIndexCompressedMmap: explicit S type parameter
2026-05-29 17:49:49 +00:00
Luis Cossío
5bc9847972 [UIO] Load GraphLinks with any backend (#9214)
* load with any universal io

* Don't populate with sequential advice

* use appropriate fs for `exists`

* use `read_whole_via` more

* TODO

* clear ram cache after read_whole
2026-05-29 11:06:26 -04:00
Jojii
7a8703f166 [TQDT] API (#9172)
* [ai] TQDT in the API

* [ai] unify new sparse error for TQDT

* Rename to `turbo4`

* Add `Turbo4` to comments and doc strings.

* Also validate named sparse vector creation
2026-05-29 15:26:39 +02:00
Luis Cossío
fe70f1ecfc [DiskCache tests] Fix reopen on windows (#9189)
* reactivate tests

* try to release remote
2026-05-27 12:22:45 -04:00
Luis Cossío
49eebeb852 [DiskCache] use and impl schedule_whole (#9185)
* impl schedule_whole for DiskCache

* use schedule_whole for background prefill

* ignore unit arg in bench

* bundle R bounds into trait
2026-05-27 10:28:15 -04:00
xzfc
f0ef85e289 [UIO] Dynamic alignment (#9040)
* universal_io bench: skip io_uring/8bytes/read_batch_full

* ACow

* [UIO] Dynamic alignment [v2]

* override MmapFile::read_iter/read_multi_iter for better performance
2026-05-27 13:31:18 +00:00
xzfc
1e8e62913e Replace PendingSlots with slab::Slab (#9190) 2026-05-27 01:44:09 +00:00
Luis Cossío
48363df9d7 [UIO] Use UniversalRead::reopen (#9127)
* use universal reopen

* remove unused fs args
2026-05-26 13:00:22 -04:00
Daniel Boros
5e109bbecc feat: sync->async bridge (#9093)
* feat: add io_bridge

* fix: cached dispatcher

* feat: add open_with_handle

* fix: naming

* fix: wording

* fix: rebase io_bridge onto split BorrowedReadPipeline/OwnedReadPipeline

* fix: linter

* feat: add S3 backend

* chore: remove io_bridge

* fix: linter

* fix: linter

* fix: read handle

* chore: simplify S3Source

* fix: s3 test

* feat: support multi runtime

* fix: clippy errors

* fix: review comments

* feat: add io design

* feat: add S3 backend

* chore: fix docs

* fix: dev changes

* chore: add some docs

* chore: remove explicit type

* feat: add new methods

* [WIP] review refactor

* fmt

* fix: bytes alignment

* fix: linter

* feat: remove Bytes

* fix: ci/cd

* fix: tests

* fix: tests

* refactor: simplify io_bridge pipeline to Handle-based dispatch

Replace the BridgeRuntime worker thread + request channel + boxed
BridgeRequest with direct tokio Handle usage:

- BridgeRuntime is now just an Arc<Runtime>; schedule() spawns the read
  future via Handle::spawn instead of routing it through a dispatcher
  thread. Removes BridgeRequest and the now-unreachable S3RuntimeShutDown
  error variant.
- Guard against a panicking read task hanging wait() forever: the spawned
  task catches unwinds and converts them into a TaskPanicked error reply,
  so every scheduled slot is always answered.
- Encapsulate slot bookkeeping in PendingSlots, exposing only the needed
  operations instead of a public map + counter.
- Split the grown pipeline.rs into a pipeline/ module (slots / inner /
  borrowed / owned), de-duplicating the shared read-future construction.

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

* refactor: move pipeline buffer ownership into the read future

Instead of the pipeline owning the destination Vec<T> in a slot map and
the future writing through a SendBytePtr raw pointer, let the future
allocate the buffer itself and return it through BridgeResponse. The
buffer crosses the worker-thread boundary as a normal move via the reply
channel, wrapped in a SendableVec<T> newtype that asserts Send for
T: bytemuck::Pod only.

This removes the entire unsafe SendBytePtr apparatus from the pipeline:
no raw pointer, no unsafe fn, no per-call-site unsafe blocks, no
heap-stability invariants. The only remaining unsafe in the crate is one
bounded `unsafe impl<T: Pod> Send for SendableVec<T>` with a trivially
true invariant (Pod types are plain bytes).

PendingSlots collapses back to PendingSlots<U>: slots no longer carry
buffers. AlignedBufWriter::from_raw_bytes (used only by the SendBytePtr
path) and its test are removed.

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

* refactor: drop SendableVec wrapper now that Item: Send

UniversalRead's element type is now bound to `Item` (`Pod + Send`),
so the io_bridge pipeline no longer needs a hand-rolled `Send` wrapper
around `Vec<T>` to ship buffers through the reply channel. Replace
`SendableVec<T>` with `Vec<T>` end-to-end and tighten the local impls
to `T: Item`.

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

* feat: implement UniversalRead::reopen for BlobFile

BlobFile has no cached file metadata or mapping — `len()` queries the
object store fresh on each call — so reopen is a no-op, matching the
io_uring impl.

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

* feat: add new fs impl for Blob

* fix: is_in_ram_or_mmap for S3

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:49 +02:00
Andrey Vasnetsov
daa22f15e6 [UIO] Split UniversalReadFileOps into filesystem + file traits (#9151)
* [UIO] Split UniversalReadFileOps into filesystem + file traits

`UniversalReadFileOps` is now an instance-based trait describing a
filesystem handle (list/exists with `&self`, plus `from_context`). A new
`UniversalReadFs: UniversalReadFileOps` subtrait adds the
`open(&self, path, options) -> Self::File` capability with `type File:
UniversalRead`. `UniversalRead` no longer extends `UniversalReadFileOps`
and is purely a file-handle trait.

This separates "filesystem instance" from "file handle". Backends that
need per-instance configuration (S3 bucket name + credentials, mmap
default advice, io_uring runtime, block-cache controller `Arc`) gain a
typed home in `Self::ContextConfig`, and `list_files`/`exists`/`open`
become `&self` methods on the filesystem handle.

Concrete filesystem handles introduced for the three existing backends:

- `MmapFs` — unit struct; `ContextConfig = ()`; produces `MmapFile`.
- `IoUringFs` — carries `prevent_caching`; `ContextConfig =
  IoUringConfigContext`; produces `IoUringFile`. `IoUringConfigContext`
  becomes the construction input rather than a per-open argument.
- `BlockCacheFs` — carries `Arc<CacheController>`; `ContextConfig =
  BlockCacheConfigContext`; produces `CachedSlice`.

`TConfigContext` (universal builder methods) is kept so generic-over-`Fs`
code can still set cross-backend knobs via
`Fs::ContextConfig::default().with_prevent_caching(true)`.

Wrappers (`ReadOnly<S>`, `TypedStorage<S, T>`, `StoredStruct<S, T>`),
higher-level storages (`StoredBitSlice<S>`, `UniversalHashMap<K, V, S>`)
keep `<S: UniversalRead>` parameterization but their `open(...)`
constructors now grow a `fs: &Fs` argument bound by
`Fs: UniversalReadFs<File = S>`. `read_json_via` becomes
`read_json_via(fs: &Fs, path)`.

All test code and benches in `common` updated to construct
`MmapFs`/`IoUringFs` inline as needed. `common` compiles cleanly with
tests and benches. `gridstore`, `segment`, and `tonic` caller updates
are in flight in subsequent commits.

* WIP: gridstore + segment caller sweep (partial)

Threads `fs: &Fs` through gridstore's `BitmaskGaps`, `Bitmask`, `Pages`,
and `Gridstore::new`/`open`/`create_new_page`. Most segment callers
have `OpenOptions { extra: ... }` removed and `S::open(path, opts, ctx)`
sites updated mechanically but the trait change is not yet propagated.

Does NOT compile yet. Tracker still has static `S::open` calls, segment
generic constructors (`MmapInvertedIndex<S>`, `UniversalMapIndex`,
`StoredGeoMapIndex`, etc.) still call `S::open`/`S::list_files`/`S::exists`
statically — they need an `fs: &Fs` parameter added. Tonic API
`StorageReadService<S>` also unconverted.

Committed as branch checkpoint; cascade continues in subsequent work.

* gridstore: thread `fs: &Fs` through Bitmask, BitmaskGaps, Pages, Tracker

Per the new `UniversalReadFs` shape, every constructor/method that opens
files takes an `fs: &Fs` parameter. Gridstore is currently mmap-only,
so the top-level `Gridstore` / `GridstoreReader::open` callers in the
crate pass `&MmapFs` inline. Tests do the same.

gridstore lib + tests now compile cleanly. Segment + tonic cascade
still pending.

* WIP: segment caller sweep — dynamic_stored_flags first

* WIP: segment cascade - id_tracker partial

* common benches: update to new UniversalReadFs::open shape (clippy clean)

* segment flags: thread Fs through BufferedDynamicFlags / Bitvec / Roaring

DynamicStoredFlags::set_len now takes `fs: &Fs`. BufferedDynamicFlags
stores an `Arc<Fs>` so the flusher closure can call `set_len` on resize.
BitvecFlags and RoaringFlags expose a new `Fs` type parameter and the
flag tests now pass `Fs::default()` (MmapFs/IoUringFs via duplicate_item).

Concrete consumers (bool/null index, mmap dense/multi/sparse storages)
pin `Fs = MmapFs` and pass `&MmapFs` to inner opens.

* segment: thread Fs through field-index lifecycle methods

Apply the new UniversalReadFs::open shape across:
- full_text_index (MmapInvertedIndex, MmapFullTextIndex, UniversalPostings)
- geo_index (StoredGeoMapIndex build/open + tests + builders)
- numeric_index lifecycle (UniversalNumericIndex build/open)
- map_index lifecycle (UniversalMapIndex build/open)
- stored_point_to_values (open / from_iter)

Concrete consumers pin Fs = MmapFs and pass &MmapFs inline; generic
open paths thread `fs: &Fs` where Fs: UniversalReadFs<File = S>.

* segment: thread Fs through chunked vectors and id-tracker callers

- ChunkedVectors gains `Fs` generic so add_chunk can call create_chunk
  after open. ChunkedVectorsRead/load_config and chunks::{read_chunks,
  create_chunk} take `fs: &Fs`. Concrete callers (dense / multi-dense /
  sparse / quantized) pass MmapFs inline.
- DenseVectorStorageImpl stores `fs: Fs` so `update_from` can reopen
  ImmutableDenseVectors. ImmutableDenseVectors::open takes `fs: &Fs`.
- VectorStorageEnum DenseUring* variants thread IoUringFs alongside
  IoUringFile.
- segment_builder + segment_constructor_base pass MmapFs to
  ImmutableIdTracker::{new, open}.
- QuantizedStorage::from_file takes `fs: &Fs`; quantized_vectors callers
  pass &MmapFs.

* segment: apply nightly rustfmt after Fs refactor

cargo +nightly fmt --all over the segment crate after the
UniversalReadFs cascade. No semantic changes.

* segment: thread Fs through benches and id-tracker tests

Update the dynamic-mmap-flags and buffered-update-bitslice benches to
the new UniversalReadFs::open shape (pass `&MmapFs`). Update the
immutable-id-tracker test suite to forward `&MmapFs` to
`from_in_memory_tracker` / `open`.

* uio: pin Fs via UniversalRead::Fs assoc type; per-call OpenExtra

Two design changes that fall out of the per-instance Fs refactor:

1. Bidirectional Fs ↔ File pinning. `UniversalRead::Fs:
   UniversalReadFs<File = Self>` lets generic-over-`S` code refer to
   `S::Fs` directly instead of carrying an extra `<Fs: UniversalReadFs<File = S>>`
   generic param. `ReadOnly<S>` wraps a file but has no natural
   filesystem; a phantom `ReadOnlyFs<S::Fs>` satisfies the constraint
   while inherent `ReadOnly::open` keeps taking `&S::Fs` directly.

2. `prevent_caching` moves from filesystem-instance state to per-call
   `UniversalReadFs::OpenExtra: Default`. Was previously a knob on
   `IoUringConfigContext` / `IoUringFs`, conflating "how this fs is
   built" with "how this file is opened." Now `IoUringFs::OpenExtra =
   IoUringOpenExtra { prevent_caching }`; mmap and block-cache use `()`.
   `IoUringConfigContext` is gone, `TConfigContext` slims to a `Default`
   marker.

Tonic StorageReadService holds `Arc<S::Fs>` (was `PhantomData<S>`); its
`new()` builds via `S::Fs::from_context(default)` and the spawn_blocking
closures clone the Arc to call instance methods.

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

* uio: cfg-gate IoUringOpenExtra import for non-linux builds

The IoUringOpenExtra reexport from `universal_io` is gated on
`target_os = "linux"`. The previous commit left an unconditional import
in `persisted_hashmap/tests.rs`, breaking macOS/Windows CI.

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

* uio: migrate simple_disk_cache to per-instance Fs API

PR #9097 (merged into dev concurrently with this branch) introduced a
`DiskCache<R: UniversalRead>` using the pre-refactor trait shape:
file-handle-as-filesystem (`R::open`, `R::list_files`), an
`OpenOptionsExtra` field on `OpenOptions`, and trait methods without
`&self`. The Fs-instance refactor on this branch removed all three.

Reshape `simple_disk_cache` to match the new design without breaking the
lazy mirror semantics:

- New `DiskCacheFs<R>` is the filesystem handle. Holds a clone of the
  remote `R::Fs`; `list_files`/`exists` delegate; `from_context`
  forwards to the inner Fs context. `open` constructs a `DiskCache<R>`
  via the global `DiskCacheConfig`.
- `DiskCache<R>` now stores `remote_fs: R::Fs` + `remote_extra:
  <R::Fs as UniversalReadFs>::OpenExtra`, so lazy remote opens go
  through `self.remote_fs.open(path, options, extra)` instead of the
  removed `R::open`. `open_with_config` takes the remote Fs + extra
  explicitly (no more hard-coded `prevent_caching: true`; callers pass
  the appropriate `OpenExtra`).
- `UniversalRead for DiskCache<R>` now declares `type Fs =
  DiskCacheFs<R>` (no more `open` method on the file trait).
- Propagate the necessary bounds (`R::Fs: Clone`,
  `<R::Fs as UniversalReadFs>::OpenExtra: Clone`,
  `R::OwnedReadPipeline<u8, Range<u32>>: Send`) through `pipeline.rs`
  free functions and impl blocks that reach into `DiskCache::remote` /
  `local_state`.
- Drop the now-removed `extra: _` destructure in `LocalState::new`.
- Tests construct the remote Fs via `R::Fs::from_context(Default::default())`
  and exercise `DiskCache::open_with_config`. 17 simple_disk_cache
  tests pass; the 3 `empty_read_does_not_materialize_local_file`
  failures pre-exist on dev (verified) and are unrelated.

`mold -run cargo clippy --all-targets` clean.

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

* style: nightly fmt on simple_disk_cache migration

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

* style: nightly fmt for local_state imports after rebase

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

* uio: split DiskCacheFs into its own module

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

* uio: replace TConfigContext with OpenExtra trait; move DiskCacheConfig onto DiskCacheFs

- Drop the empty TConfigContext marker; ContextConfig is now unconstrained
  so backends can require explicit construction.
- Add OpenExtra trait with with_prevent_caching for backend-agnostic
  per-call knobs; impl for () (no-op) and IoUringOpenExtra.
- DiskCacheFs now carries Arc<DiskCacheConfig> via the new
  DiskCacheFsContext<C>; the prefill flow moves from the deleted
  open_with_config into DiskCacheFs::open so Populate::Blocking /
  PreferBackground work through the trait API.
- Remove the DiskCacheConfig global; callers must construct the context
  explicitly.

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

* style: nightly fmt after OpenExtra refactor

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

* docs: fix spelling — Implementors → Implementers

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

* fix: possible panic insetad of error propagation

* fix: missing cfg annotation

---------

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: Daniel Boros <dancixx@gmail.com>
2026-05-26 12:57:30 +02:00
Luis Cossío
557051ecf0 [UIO] Impl DiskCache::reopen (#9143) 2026-05-25 15:01:14 -04:00
Luis Cossío
ed9489523c [UIO] Introduce OwnedReadPipeline::schedule_whole (#9128) 2026-05-25 12:53:17 -04:00
Luis Cossío
cf8d968991 Merge pull request #9097
* impl async prefill

* use enum in `init_lock`

* init from scratch if channel closes

* cfg tests with prefill

* dirty update to pipe2

* cleanup & deduplicate code

* fix prefill pipeline

* self nits

* fix rebase

* separate file for `LocalState`

* more doc comments

* use acq/rel ordering

* handle empty byte range conversion

* todo reopen

* review: remove redundant functions

* review: T: + Send

* fmt
2026-05-25 16:47:26 +02:00
Andrey Vasnetsov
88302575a5 [UIO] Require T: Send on UniversalRead (#9147)
Introduce a `pub trait Item: bytemuck::Pod + Send` marker (mirroring the
existing `UserData` pattern) and use it in place of `T: bytemuck::Pod`
on the read side of `UniversalRead`, its pipeline traits, and all
impls/wrappers. Some implementations buffer `Vec<MaybeUninit<T>>` that
may be transferred across threads in future backends; tightening the
bound makes that explicit at the trait level instead of leaving callers
to add `+ Send` ad-hoc. Write paths keep the looser `bytemuck::Pod`
bound since they only borrow `&[T]`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 19:09:51 +02:00
xzfc
34f2c82901 Cache sparse benchmark setup (#9141)
* dataset: add features to fs-err

Reason: fix `cargo check --all-targets -p dataset`. Feature unification
pulls `fs-err/debug` + `fs-err/tokio` without `fs-err/debug_tokio`.

* dataset: run `cargo metadata` to get target dir

Reason: avoid re-downloading in separate worktrees. The old
`cargo locate-project` ignores custom target dirs.

* sparse/benches/search: cache vectors/indices
2026-05-23 16:45:42 +00:00
Andrey Vasnetsov
2f815b751a Clear id tracker page cache after building a segment (#9137)
The segment builder evicts the page cache of vector storage, quantized
vectors, payload, payload index and the vector index after a build to
avoid cache pollution, but the id tracker was never cleared. Its on-disk
files (mappings, versions, deleted bitslice) are written during the build
and stay resident in the page cache, so after each optimization the id
tracker files linger as cache even though they are meant to be on-disk
only (expected_cache_bytes == 0).

Add `IdTracker::clear_cache` (default no-op) plus a `clear_cache_if_on_disk`
policy wrapper, and implement the eviction for the mutable and immutable
trackers:
- ImmutableIdTracker pages out its two mmap-backed storages via madvise
  and drops the RAM-loaded mappings file via fadvise(DONTNEED).
- MutableIdTracker drops its append-only log files via fadvise(DONTNEED).

The builder calls `clear_cache_if_on_disk`, mirroring the payload index.
The id tracker has no on-disk mode yet, so this always clears for now;
a TODO marks where to gate it once an on-disk mode exists.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 17:44:04 +02:00
Luis Cossío
965cf4c4b3 [UIO] Introduce UniversalRead::reopen (#9123)
* add `UniversalRead::reopen` fn

* thx coderabbit
2026-05-22 08:22:08 -04:00
qdrant-cloud-bot
899e2e34a5 Bump dev version to 1.18.2-dev (#9135)
Co-authored-by: Cursor Agent <agent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 11:57:11 +02:00
xzfc
899857c4f5 Make OpenOptions explicit (#9104)
* Refactor: make OpenOptions explicit

* Refactor: remove unused OpenOptions::disk_parallel

* Refactor: do not wrap `advice` in Option

* Refactor: Add OpenOptionsExtra
2026-05-20 11:06:19 +00:00
xzfc
faf2714f4b Warn on clippy::wildcard_enum_match_arm (#9096) 2026-05-19 18:14:15 +00:00
Luis Cossío
9e32c940e4 Introduce simple disk cache (#8792)
* [AI + manual] initial impl

[AI] read_batch which actually batches

manual nits

[AI] better handling of local and remote paths

manual refactor, respect open options

don't delete local file

dumbify read_batch

we want to refactor it anyway

simplify

rename to `DiskCache` in `simple_disk_cache` module

* refactor to use always use ReadPipeline

pass meta to remote pipeline

* nits

* run tests for more Remotes

* fix no more <T> in UniversalRead

* fmt

* chore(deps): unify roaring as workspace dep, move duplicate to dev-deps

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

---------

Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:11:51 -04:00
dependabot[bot]
2b23ab8dd5 build(deps): bump quick_cache from 0.6.21 to 0.6.22 (#9084)
Bumps [quick_cache](https://github.com/arthurprs/quick-cache) from 0.6.21 to 0.6.22.
- [Release notes](https://github.com/arthurprs/quick-cache/releases)
- [Commits](https://github.com/arthurprs/quick-cache/compare/v0.6.21...v0.6.22)

---
updated-dependencies:
- dependency-name: quick_cache
  dependency-version: 0.6.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 06:43:06 +02:00
xzfc
b0cea26003 BorrowedReadPipeline/OwnedReadPipeline (#9079)
* BorrowedReadPipeline/OwnedReadPipeline

* `fn new(file: &Self::File)` -> `fn new(file: Self::File)`

Also, partially revert some related changes from the previous commit.
2026-05-18 23:01:59 +00:00
xzfc
dc240d005f Reorganize universal_io modules (#9078)
* Let's not expose `universal_io` internal modules.
* Reorganize universal I/O code files
2026-05-18 21:58:21 +00:00
xzfc
f0ddf96150 UniversalReadFileOps: require Debug (#9063) 2026-05-16 12:51:58 +00:00
lphuc2250gma
752b7e6b63 fix: duplicated words in metrics.rs and validation.rs comments (#9041)
Signed-off-by: Noa Levi <275430404+lphuc2250gma@users.noreply.github.com>
Co-authored-by: Noa Levi <275430404+lphuc2250gma@users.noreply.github.com>
2026-05-15 10:05:43 +02:00
Luis Cossío
ee739aff79 [UIO] Introduce Populate enum (#8946)
* introduce `Populate` enum

* fix: apply CodeRabbit auto-fixes

Fixed 6 file(s) based on 6 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* fix coderabbit fix

* Use from rather than into

* add `Auto` variant

* fix rebase

* fix rebase again

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: timvisee <tim@visee.me>
2026-05-14 14:49:09 -04:00
Luis Cossío
bc44640216 uring only for linux (#9046) 2026-05-14 12:41:21 -04:00
xzfc
4bd0cb29f5 Replace Meta with U: UserData (#9009) 2026-05-13 00:06:27 +00:00
Andrey Vasnetsov
75b3c92018 refactor mmap hashmap (#8405)
* AlignedBuf

* UniversalHashMap

* Update wording

---------

Co-authored-by: xzfc <xzfcpw@gmail.com>
2026-05-12 04:03:51 +00:00
Luis Cossío
6599f545da [UIO] Additional UniversalRead/Write usage simplifications (#8961)
* bytemuck::Pod already implies 'static

* no supertraits

* regions gaps with TypedStorage

* StoredBitSlice with TypedStorage

* bytemuck for PostingsHeader

* bytemuck for TrackerHeader

* bytemuck for MmapRange

* bytemuck for stored_point_to_values::Header
2026-05-08 20:07:03 +02:00
Andrey Vasnetsov
64244330b1 Move element type from UniversalRead trait to method generics (#8955)
* Move element type from UniversalRead trait to method generics

Lifts the `T` parameter off `trait UniversalRead<T>` (and the matching
`UniversalWrite<T>`) and onto the read/write methods themselves. The
`ReadPipeline` associated type becomes a GAT over `T`. With per-method
generics, callers that need to read several element types from one storage
just write `S: UniversalRead` instead of stacking
`UniversalRead<u8> + UniversalRead<Counts> + ...`.

Removes the workarounds the old shape required:
- `TypedStorage<S, T>` newtype (sole purpose was disambiguating multi-bounds)
- `UniversalReadFamily` HKT shim
- `StoredGeoMapIndexStorage` four-bound trait alias
- `CachedSlice<T>` is now non-generic; `T` moves to `get_range`/`len`

No runtime behavior change: alignment in `IoUringRuntime` and `CachedSlice`
is preserved because `T` is still known at each call site.

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

* Restore TypedStorage as a typed-access fail-safe

Reintroduces `TypedStorage<S, T>` as a transparent wrapper around
`UniversalRead`/`UniversalWrite` storage that fixes the element type to
`T`. With per-method generics on the underlying traits, callers can
otherwise read or write any `T` from the same handle; this wrapper
binds it at the type level so accidental cross-type access fails to
compile.

The wrapper exposes inherent typed methods (`read::<P>`, `read_iter`,
`write`, `len`, …) that delegate to the inner storage with `T` fixed.
It does not implement `UniversalRead`/`UniversalWrite` itself — those
are intentionally avoided to prevent the typed binding from being
bypassed via the generic trait methods.

Restores the wrapping at the previous call sites: `StoredStruct`'s
inner storage, `ImmutableIdTracker`'s version mmap, the geo and
numeric index storages, the chunked-vectors chunks, and the
immutable dense vector storage.

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

* Silence clippy::len_without_is_empty on TypedStorage

`TypedStorage::len` returns `Result<u64>` (a fallible byte length from
the underlying storage), so an `is_empty` companion would also be
fallible and offer nothing over `len()? == 0`. Suppress the lint at
the impl block, matching how the underlying `UniversalRead::len` is
already exempted.

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

* fmt

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:37:34 +02:00
Tim Visée
4bca939259 Bump dev version to 1.18.1-dev (#8960) 2026-05-08 17:27:10 +02:00
xzfc
d8e49327f8 Split mmap_hashmap into multiple files (#8933)
Co-authored-by: generall <andrey@vasnetsov.com>
2026-05-07 14:35:15 +00:00
Daniel Boros
dddc6e9b6f feat/vector-storage-read-only (#8889)
* feat: add vector storage read enum

* fix: linter

* chore: remove open functions

* fix: compiler error

* review fix 1: remove constructiors (for now), use UniversalReadFamily, implement 1st part of VectorStorageReadEnum

* fmt

* read only chucked_vector_storage

* fnt

* feat: add dense chunked variants to vector storage read enum (#8916)

* feat: add multi dense chunked (#8918)

---------

Co-authored-by: generall <andrey@vasnetsov.com>
2026-05-06 13:31:23 +02:00
xzfc
d5789bb78b Callback-based InvertedIndex::for_each_token_id (#8905)
* TokenizerTextKind

* IteratorExt::try_any

* Callback-based InvertedIndex::for_each_token_id
2026-05-05 22:25:13 +00:00
Luis Cossío
fb051280a4 Properly handle O_DIRECT in io-uring (#8833)
* handle o_direct with custom buffer

* make separate impl

* use correct inner offset

* align buffer

* codespell

* review fixes

* use o_direct in tests

* clippy

* fix simple read impl

* simpler to_vec()

* use router function

* change todo comment
2026-05-05 11:07:36 -04:00
Luis Cossío
c1ba01ba39 [UIO] Generic dynamic flags (#8893)
* rename status

* impl simple StoredStruct

* use StoredStruct in `status`

* rename to `DynamicStoredFlags`

* Propagate `S` generic

* codespell nit
2026-05-05 10:46:10 -04:00
Roman Titov
4ef7b1e4a5 io_uring pipelinemaxxing (#8897) 2026-05-05 11:17:56 +02:00
Roman Titov
cb34d95417 UniversalRead cleanup (#8894)
* Cleanup lifetimes and generic type parameters

- Rename read pipeline lifetime from `'a` into `'file`
- Use explicit `where` clauses everywhere

* Cleanup

* fixup! Cleanup lifetimes and generic type parameters
2026-05-04 23:03:08 +02:00
Roman Titov
6942b54bfb Simplify UniversalReadPipeline (#8850) 2026-05-04 19:57:29 +02:00
generall
3eef48279b refactor(common): use EitherVariant for NumericIndexInner::stream_range
Drop the bespoke 4-arm \`NumericRangeIter\` enum introduced in the prior
commit. The existing \`common::either_variant::EitherVariant\` is the
same shape and already has \`Iterator\` plus all the standard adapter
specializations.

Adds a \`DoubleEndedIterator\` impl to \`EitherVariant\` (\`next_back\`,
\`nth_back\`, \`rfold\`, \`rfind\`) so it can be used in this position.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:31:35 +02:00
Andrey Vasnetsov
7486a57cf3 Fix missed-wakeup race in SaveOnDisk::wait_for (#8847)
* test: reproduce missed-wakeup race in SaveOnDisk::wait_for

Adds test_wait_for_no_missed_wakeup plus a #[cfg(test)]-only
test_set_pre_park_sleep_ms hook that injects a sleep inside wait_for
between releasing the read guard on data and parking on the condvar.
This widens the race window from nanoseconds to ~100ms so the writer
thread's notify_all reliably fires *before* the waiter parks, exposing
the bug as a hard failure (notification lost, wait_for hits timeout).

This commit only adds the test and the instrumentation; the bug is
still present, so the new test fails.

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

* fix: prevent missed wakeup in SaveOnDisk::wait_for

write and write_optional were calling change_notification.notify_all
without holding notification_lock. A waiter that had released its read
guard on data but had not yet parked on the condvar would miss the
notification and wait the full timeout — even though the condition was
already true.

Acquire notification_lock around notify_all (in a new notify_change
helper). The waiter holds notification_lock across the release-and-park
gap, so notify_change blocks until the waiter has actually parked,
guaranteeing delivery.

This matches the standard parking_lot Condvar pattern where the mutex
that protects the predicate is held while signalling. The new
test_wait_for_no_missed_wakeup test (added in the previous commit) now
passes; manifests in production as the flaky
test_fix_reshard_down_without_shard_key consensus test, where
wait_for_shard_key_activation timed out waiting on a replica state
that had already become Active.

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

* test: remove missed-wakeup race instrumentation

Removes the #[cfg(test)] test_pre_park_sleep_ms hook and the
test_wait_for_no_missed_wakeup test that depended on it. The
instrumentation existed only to deterministically reproduce the
missed-wakeup race in the unfixed wait_for; with the fix in place
(notify_change holds notification_lock around notify_all) the race is
closed and the hook has no remaining purpose.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:29:09 +02:00
xzfc
5eba7da9b3 UniversalReadPipeline (#8798)
* UniversalReadPipeline

* Performance: cache pointers in MmapFile

The previous commit removed `MmapFile::read_batch` method override. So,
`MmapFile` now re-uses the default `UniversalRead::read_batch` impl,
which is implemented using `UniversalReadPipeline` interface.

Unsurprisingly, it caused a slowdown in the benchmarks, particularly
this one:

    cargo bench -p common --bench universal_io -- mmap/8bytes/read_batch_full

This commit reclaims the performance back.

* remove unfulfilled lint

* Review suggestions

---------

Co-authored-by: Luis Cossío <luis.cossio@outlook.com>
Co-authored-by: Roman Titov <ffuugoo@users.noreply.github.com>
2026-04-29 02:05:30 +00:00
Luis Cossío
658d952103 cfg linux imports (#8829) 2026-04-28 22:00:35 +02:00
Ivan Boldyrev
ca4ed7af69 Immutable storage for mmap numeric index (#8594)
* Immutable storage for mmap numeric index

Do not use a deleted mmap-ed storage, instead, reconstruct a bitmask
from the index and segment-level deleted mask.  It effectively makes
the mmap numeric index immutable, and immutable numeric index too as
it delegates the storage to mmap numeric index.

* Index reload tests

* Make clippy happy

* Restore deletion bitmask to reduce IO on index load

It is more compact than count data we read before to check for cleared
payloads.

* immutable payload index storage 1 bis review (#8638)

* use bitwise operations instead of loop

* [AI] Only propagate bitslice

* fmt

* Fix deleted bitmask length

It must be the same length as `point_to_values` length.

* account for ram usage

* Default missing external deletion bits to live, not deleted.

* clear doc for flush

---------

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
Co-authored-by: Arnaud Gourlay <arnaud.gourlay@gmail.com>
2026-04-27 19:29:58 +02:00
Daniel Boros
068fbc1426 feat: add internal shard level storage api (#8778) 2026-04-27 13:28:52 +02:00