How to Use Turso's Async I/O with io_uring on Linux for Maximum Performance
Enable the io_uring Cargo feature at compile time and select the "io_uring" VFS at runtime to replace Turso's default syscall backend with a high-performance, ring-based asynchronous I/O interface on Linux.
Turso's embedded database engine supports an optional io_uring backend that eliminates traditional syscall overhead through Linux's modern asynchronous I/O interface. This implementation, located in the tursodatabase/turso repository, provides significant latency reductions for read-heavy and write-heavy workloads by batching operations and utilizing zero-copy fixed buffers.
Enabling the io_uring Feature at Compile Time
The io_uring backend is gated behind a Cargo feature and only compiles on Linux targets. You must explicitly opt-in during the build process.
Configuring Cargo.toml
Add the feature flag when building the core crate or any workspace that depends on it:
cargo build --features=io_uring
According to core/Cargo.toml, this feature pulls in the external io-uring crate and enables the rustix/io_uring implementation. The code is conditionally compiled using #[cfg(all(target_os = "linux", feature = "io_uring", not(miri)))], ensuring it is excluded from non-Linux targets and Miri sanitization environments.
Selecting the io_uring VFS at Runtime
Once compiled with the feature enabled, you must explicitly choose the io_uring backend when initializing a database connection.
Using the Rust SDK
The SDK exposes the with_io method to specify the VFS implementation. In sdk-kit/src/rsapi.rs, this method maps the string "io_uring" to the corresponding backend factory:
use turso_sdk_kit::DatabaseBuilder;
fn main() -> turso_sdk_kit::Result<()> {
let db = DatabaseBuilder::new()
.with_path("/var/lib/turso/my.db")
.with_io("io_uring".to_string()) // Selects the io_uring backend
.open()?;
let result = db.execute(
"INSERT INTO users (name) VALUES (?)",
("Alice",)
)?;
Ok(())
}
The with_io call triggers the factory logic in core/lib.rs, which returns Arc::new(UringIO::new()?) when the string matches "io_uring".
Using the CLI
Pass the --vfs flag to the tursodb binary (defined in cli/app.rs at line 65):
tursodb --path /var/lib/turso/my.db --vfs io_uring repl
This command-line argument propagates through to the same VFS factory, instantiating the UringIO driver for the session.
How the io_uring Backend Works
The UringIO type defined in core/io/io_uring.rs manages the complete lifecycle of the io_uring ring, from initialization through submission and completion handling.
Ring Initialization and Lifecycle
The UringIO::new() function (lines 95-134) performs the following setup:
- Creates an
io_uring::IoUringinstance with a submission queue capacity of 512 entries - Registers a sparse fixed-buffer arena for zero-copy I/O operations
- Probes the kernel for opcode support using
Probeto detect available operations - Initializes
RingState, a shared structure protected by a mutex that coordinates SQE submission - Allocates a
wait_lockto serialize thesubmit_and_waitsyscall
If the kernel lacks support for critical opcodes (e.g., IORING_OP_FTRUNCATE), the backend emits a warning and prepares synchronous fallback paths.
Submission and Completion Path
All file operations—open_file, pread, pwrite, pwritev, sync, and truncate—construct io_uring submission queue entries (SQEs) and push them via RingState::submit_entry. Multiple threads can submit SQEs concurrently without global locking contention.
A single leader thread executes submit_and_wait, draining the completion queue (CQE) and mapping user_data keys back to Rust futures via completion_from_key. Write-vector operations (pwritev) that require multiple kernel calls are automatically resubmitted, with completion wakeups triggered through wake_user_data.
Graceful Fallback Behavior
When the kernel does not support a specific opcode, Turso automatically falls back to synchronous POSIX syscalls. This ensures compatibility with older kernels while logging a warning at startup to inform operators of the degraded I/O path.
Verification and Testing
Confirm that your deployment is utilizing the high-performance backend through logging and targeted integration tests.
Runtime Verification
Upon successful initialization, UringIO::new() emits a debug log entry:
debug!("Using IO backend 'io-uring'");
If the backend is unavailable or the feature is disabled, the system silently falls back to the default syscall VFS.
Integration Testing
Force the io_uring path in tests using the builder pattern shown in tests/integration/query_processing/test_write_path.rs (line 1418):
#[test]
fn test_uring_write_path() {
let db = TestBuilder::new()
.with_io_uring(true) // Forces io_uring backend
.open();
db.execute("PRAGMA journal_mode=WAL").unwrap();
db.execute("INSERT INTO t (id) VALUES (1)").unwrap();
// Uses UringIO::pwritev internally
}
These tests verify correct behavior under the feature flag, including proper handling of shared_wal_* coordination primitives.
Summary
- Compile-time requirement: Enable the
io_uringfeature incore/Cargo.tomland build on Linux. - Runtime selection: Pass
"io_uring"toDatabaseBuilder::with_io()or use--vfs io_uringin the CLI. - Architecture: The
UringIOtype incore/io/io_uring.rsmanages a shared ring with batching (512 entries) and parallel submission capabilities. - Performance benefits: Reduced syscall overhead through batched
submit_and_waitcalls and zero-copy fixed-buffer registration. - Compatibility: Automatic fallback to synchronous syscalls for unsupported kernel opcodes ensures robustness across Linux versions.
Frequently Asked Questions
Is io_uring available on macOS or Windows?
No. The io_uring backend is strictly Linux-only. Attempting to use "io_uring" on macOS or Windows results in a runtime error: "io_uring is only available on Linux targets". Windows users should use the experimental_win_iocp VFS instead, while macOS uses the default syscall-based VFS.
What happens if my kernel doesn't support io_uring?
If the kernel lacks io_uring support entirely, Turso will fail to initialize the UringIO instance and fall back to the default VFS. If specific opcodes are missing (such as IORING_OP_FTRUNCATE), the backend logs a warning and executes those specific operations using standard std::fs syscalls while continuing to use io_uring for supported operations.
How does io_uring improve performance over standard syscalls?
The backend reduces system-call overhead by batching up to 512 submissions and waiting for up to 4 completions per io_uring_enter syscall. Additionally, fixed-buffer registration eliminates memory copies for read/write operations, and the lock-free submission path allows multiple threads to queue operations without contention. This architecture significantly reduces context switches and CPU cycles per I/O operation.
Can I use io_uring with the Turso hosted platform?
The io_uring backend is designed for embedded/self-hosted deployments where you control the underlying operating system and kernel. The Turso hosted service manages infrastructure configuration automatically and may not expose low-level VFS selection to end users. For self-hosted instances on Linux kernel 5.1+, enabling io_uring provides optimal performance for high-throughput workloads.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →