lance_io/uring.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! io_uring-based I/O for disks with high IOPS capacity (e.g. NVMe)
5//!
6//! This module provides two implementations of the [`Reader`](crate::traits::Reader) trait
7//! using Linux's io_uring interface for asynchronous I/O.
8//!
9//! One of these uses a pool of dedicated background threads which each own an io_uring instance.
10//! Read requests are submitted to a background thread's pool.
11//!
12//! The other implementation uses a thread-local io_uring instance. This only works if the future
13//! is polled by the same thread that submitted the request. This means that the runtime must be
14//! a single-threaded runtime.
15//!
16//! # Configuration
17//!
18//! The io_uring reader is enabled by using the `file+uring://` URI scheme instead of `file://`.
19//! Additional tuning parameters are controlled by environment variables:
20//!
21//! - `LANCE_URING_CURRENT_THREAD` - Use thread-local io_uring (default: false)
22//! - `LANCE_URING_BLOCK_SIZE` - Block size in bytes (default: 4KB)
23//! - `LANCE_URING_IO_PARALLELISM` - Max concurrent operations (default: 128)
24//! - `LANCE_URING_QUEUE_DEPTH` - io_uring queue depth (default: 16K)
25//! - `LANCE_URING_THREAD_COUNT` - Number of io_uring threads to use (default: 2)
26//! - `LANCE_URING_SUBMIT_BATCH_SIZE` - Number of requests to batch before submitting (default: 128)
27//! - `LANCE_URING_POLL_TIMEOUT_MS` - Thread poll timeout in milliseconds (default: 10)
28//!
29//! Note: the block size and io parallelism are not actually used by the io_uring implementation. These
30//! variables just control what the filesystem reports up to Lance.
31//!
32//! # Platform Support
33//!
34//! This module is only available on Linux and requires kernel 5.1 or newer.
35//! On other platforms, the code falls back to [`LocalObjectReader`](crate::local::LocalObjectReader).
36//!
37//! # Example
38//!
39//! ```no_run
40//! # use lance_io::object_store::ObjectStore;
41//! # async fn example() -> lance_core::Result<()> {
42//! // Enable io_uring by using the file+uring:// scheme
43//! let uri = "file+uring:///path/to/file.dat";
44//! let (store, path) = ObjectStore::from_uri(uri).await?;
45//! let reader = store.open(&path).await?;
46//!
47//! // Reader will use io_uring
48//! let data = reader.get_range(0..1024).await?;
49//! # Ok(())
50//! # }
51//! ```
52
53mod future;
54mod reader;
55mod requests;
56mod thread;
57
58// Thread-local io_uring implementation for current-thread runtimes
59pub(crate) mod current_thread;
60pub(crate) mod current_thread_future;
61
62#[cfg(test)]
63mod tests;
64
65use std::sync::LazyLock;
66
67pub(crate) use current_thread::UringCurrentThreadReader;
68pub use reader::UringReader;
69
70/// Default block size for io_uring reads (4KB)
71pub const DEFAULT_URING_BLOCK_SIZE: usize = 4 * 1024;
72
73/// Default I/O parallelism for io_uring (128 concurrent operations)
74pub const DEFAULT_URING_IO_PARALLELISM: usize = 128;
75
76/// Default io_uring queue depth (16K entries)
77pub const DEFAULT_URING_QUEUE_DEPTH: usize = 16 * 1024;
78
79/// Cached `LANCE_URING_BLOCK_SIZE` env var, read once at first access.
80pub(crate) static URING_BLOCK_SIZE: LazyLock<Option<usize>> = LazyLock::new(|| {
81 std::env::var("LANCE_URING_BLOCK_SIZE")
82 .ok()
83 .and_then(|s| s.parse().ok())
84});