feoxdb/lib.rs
1//! # FeOxDB - High-Performance Embedded Key-Value Store
2//!
3// Copyright 2025 Mehran Toosi
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! FeOxDB is an ultra-fast embedded key-value database designed for sub-microsecond latency.
18//!
19//! ## Performance
20//!
21//! View the latest benchmark results at: <https://feoxdb.com/benchmarks.html>
22//!
23//! ## Features
24//!
25//! - **Ultra-Low Latency**: <200ns GET operations, <700ns INSERT operations
26//! - **Lock-Free Operations**: Uses SCC HashMap and SkipList for concurrent access
27//! - **io_uring Support** (Linux): Kernel-bypass I/O for maximum throughput with minimal syscalls
28//! - **Flexible Storage**: Memory-only or persistent modes with async I/O
29//! - **JSON Patch Support**: RFC 6902 compliant partial updates for JSON values
30//! - **Atomic Operations**: Compare-and-swap (CAS) and atomic counters
31//! - **CLOCK Cache**: Efficient cache eviction algorithm
32//! - **Write Buffering**: Batched writes with sharded buffers to reduce contention
33//! - **Statistics**: Real-time performance metrics and monitoring
34//!
35//! ## ACID Properties and Durability
36//!
37//! FeOxDB provides ACI properties with relaxed durability:
38//!
39//! - **Atomicity**: ✅ Individual operations are atomic via Arc-wrapped records
40//! - **Consistency**: ✅ Timestamp-based conflict resolution ensures consistency
41//! - **Isolation**: ✅ Lock-free reads and sharded writes provide operation isolation
42//! - **Durability**: ⚠️ Write-behind logging with bounded data loss window
43//!
44//! ### Durability Trade-offs
45//!
46//! FeOxDB trades full durability for extreme performance:
47//! - **Write-behind buffering**: Flushes every 100ms or when buffers fill (1024 entries or 16MB per shard)
48//! - **Worst-case data loss**:
49//! - **Time window**: `100ms + 16MB / 4KB_random_write_QD1_throughput`
50//! - **Data at risk**: `16MB × num_shards (num_shards = num_cpus / 2)` (e.g., 64MB for 4 shards, 128MB for 8 shards)
51//! - Workers write in parallel, so time doesn't multiply with shards
52//! - Example (50MB/s 4KB random QD1): 420ms window, up to 64MB at risk (4 shards)
53//! - Example (200MB/s 4KB random QD1): 180ms window, up to 64MB at risk (4 shards)
54//! - **Memory-only mode**: No durability, maximum performance
55//! - **Explicit flush**: Call `store.flush()` to synchronously write all buffered data (blocks until fsync completes)
56//!
57//! ## Quick Start
58//!
59//! ### Memory-Only Mode (Fastest)
60//! ```rust
61//! use feoxdb::FeoxStore;
62//!
63//! # fn main() -> feoxdb::Result<()> {
64//! // Create an in-memory store
65//! let store = FeoxStore::new(None)?;
66//!
67//! // Insert a key-value pair
68//! store.insert(b"key", b"value")?;
69//!
70//! // Retrieve the value
71//! let value = store.get(b"key")?;
72//! assert_eq!(value, b"value");
73//!
74//! // Delete the key
75//! store.delete(b"key")?;
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! ### Persistent Mode
81//! ```no_run
82//! use feoxdb::FeoxStore;
83//!
84//! # fn main() -> feoxdb::Result<()> {
85//! // Create a persistent store
86//! let store = FeoxStore::new(Some("/path/to/data.feox".to_string()))?;
87//!
88//! // Operations are automatically persisted
89//! store.insert(b"persistent_key", b"persistent_value")?;
90//!
91//! // Flush to disk
92//! store.flush();
93//! # Ok(())
94//! # }
95//! ```
96//!
97//! ### Using the Builder Pattern
98//! ```no_run
99//! use feoxdb::FeoxStore;
100//!
101//! # fn main() -> feoxdb::Result<()> {
102//! let store = FeoxStore::builder()
103//! .device_path("/path/to/data.feox")
104//! .file_size(5 * 1024 * 1024 * 1024) // 5GB initial file size (default: 1GB)
105//! .max_memory(1024 * 1024 * 1024) // 1GB limit
106//! .hash_bits(20) // 1M hash buckets
107//! .enable_ttl(true) // Enable TTL support (default: false)
108//! .build()?;
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! ### Range Queries and Store Operations
114//! ```rust
115//! use feoxdb::FeoxStore;
116//!
117//! # fn main() -> feoxdb::Result<()> {
118//! let store = FeoxStore::new(None)?;
119//!
120//! // Insert sorted keys
121//! store.insert(b"user:001", b"Mehran")?;
122//! store.insert(b"user:002", b"Bob")?;
123//! store.insert(b"user:003", b"Charlie")?;
124//!
125//! // Range query (both start and end are inclusive)
126//! let results = store.range_query(b"user:001", b"user:003", 10)?;
127//! assert_eq!(results.len(), 3); // Returns user:001, user:002, user:003
128//!
129//! // Check existence
130//! assert!(store.contains_key(b"user:001"));
131//!
132//! // Get store metrics
133//! assert_eq!(store.len(), 3);
134//! println!("Memory usage: {} bytes", store.memory_usage());
135//! # Ok(())
136//! # }
137//! ```
138//!
139//! ### JSON Patch Operations (RFC 6902)
140//! ```no_run
141//! use feoxdb::FeoxStore;
142//!
143//! # fn main() -> feoxdb::Result<()> {
144//! let store = FeoxStore::new(None)?;
145//!
146//! // Insert a JSON document
147//! let initial_json = br#"{
148//! "name": "Mehran",
149//! "age": 30,
150//! "scores": [85, 90, 95]
151//! }"#;
152//! store.insert(b"user:123", initial_json)?;
153//!
154//! // Apply a JSON Patch to modify specific fields
155//! let patch = br#"[
156//! {"op": "replace", "path": "/age", "value": 31},
157//! {"op": "add", "path": "/email", "value": "[email protected]"},
158//! {"op": "add", "path": "/scores/-", "value": 100}
159//! ]"#;
160//!
161//! store.json_patch(b"user:123", patch)?;
162//!
163//! // The document is now updated with the patches applied
164//! let updated = store.get(b"user:123")?;
165//! # Ok(())
166//! # }
167//! ```
168//!
169//! ### Time-To-Live (TTL) Support
170//! ```rust
171//! use feoxdb::FeoxStore;
172//!
173//! # fn main() -> feoxdb::Result<()> {
174//! // Enable TTL feature via builder
175//! let store = FeoxStore::builder()
176//! .enable_ttl(true)
177//! .build()?;
178//!
179//! // Set key to expire after 60 seconds
180//! store.insert_with_ttl(b"session:123", b"user_session", 60)?;
181//!
182//! // Check remaining TTL
183//! if let Some(ttl) = store.get_ttl(b"session:123")? {
184//! println!("Session expires in {} seconds", ttl);
185//! }
186//!
187//! // Extend TTL to 120 seconds
188//! store.update_ttl(b"session:123", 120)?;
189//!
190//! // Remove TTL (make permanent)
191//! store.persist(b"session:123")?;
192//! # Ok(())
193//! # }
194//! ```
195//!
196//! ### Atomic Counter Operations
197//! ```rust
198//! use feoxdb::FeoxStore;
199//!
200//! # fn main() -> feoxdb::Result<()> {
201//! let store = FeoxStore::new(None)?;
202//!
203//! // Initialize a counter (must be 8-byte i64 value)
204//! let zero: i64 = 0;
205//! store.insert(b"counter:visits", &zero.to_le_bytes())?;
206//!
207//! // Increment atomically
208//! let new_value = store.atomic_increment(b"counter:visits", 1)?;
209//! assert_eq!(new_value, 1);
210//!
211//! // Increment by 5
212//! let new_value = store.atomic_increment(b"counter:visits", 5)?;
213//! assert_eq!(new_value, 6);
214//!
215//! // Decrement by 2
216//! let new_value = store.atomic_increment(b"counter:visits", -2)?;
217//! assert_eq!(new_value, 4);
218//! # Ok(())
219//! # }
220//! ```
221//!
222//! ### Compare-and-Swap (CAS) Operations
223//! ```rust
224//! use feoxdb::FeoxStore;
225//!
226//! # fn main() -> feoxdb::Result<()> {
227//! let store = FeoxStore::new(None)?;
228//!
229//! // Insert initial configuration
230//! store.insert(b"config:version", b"v1.0")?;
231//!
232//! // Atomically update only if value matches expected
233//! let swapped = store.compare_and_swap(b"config:version", b"v1.0", b"v2.0")?;
234//! assert!(swapped); // Returns true if swap succeeded
235//!
236//! // This CAS will fail because current value is now "v2.0"
237//! let swapped = store.compare_and_swap(b"config:version", b"v1.0", b"v3.0")?;
238//! assert!(!swapped); // Returns false if current value didn't match
239//!
240//! // CAS enables optimistic concurrency control for safe updates
241//! // Multiple threads can attempt CAS - only one will succeed
242//! # Ok(())
243//! # }
244//! ```
245//!
246//! ## Timestamps and Consistency
247//!
248//! FeOxDB uses timestamps for conflict resolution and consistency:
249//!
250//! ```rust
251//! # use feoxdb::FeoxStore;
252//! # fn main() -> feoxdb::Result<()> {
253//! # let store = FeoxStore::new(None)?;
254//! // Insert with explicit timestamp
255//! store.insert_with_timestamp(b"key", b"value_v1", Some(100))?;
256//!
257//! // Update with higher timestamp succeeds
258//! store.insert_with_timestamp(b"key", b"value_v2", Some(200))?;
259//!
260//! // Update with lower timestamp fails
261//! let result = store.insert_with_timestamp(b"key", b"value_v3", Some(150));
262//! assert!(result.is_err()); // OlderTimestamp error
263//! # Ok(())
264//! # }
265//! ```
266//!
267//! ## Performance and Benchmarking
268//!
269//! Run benchmarks to verify performance on your hardware:
270//!
271//! ```bash
272//! # Criterion benchmarks for detailed latency analysis
273//! cargo bench
274//!
275//! # Deterministic test for reproducible results
276//! cargo run --release --example deterministic_test 100000 100
277//! ```
278//!
279//! Typical results on M3 Max:
280//!
281//! | Operation | Latency (Memory Mode) | Throughput |
282//! |-----------|----------------------|------------|
283//! | GET | ~180-205ns | 11-14M ops/sec |
284//! | INSERT | ~630-970ns | 1.0-1.8M ops/sec |
285//! | DELETE | ~250ns | 4M ops/sec |
286//! | Mixed (80/20) | ~280ns | 3.8M ops/sec |
287//!
288//! ## Architecture Overview
289//!
290//! FeOxDB uses a lock-free, multi-tier architecture optimized for modern multi-core CPUs:
291//!
292//! ### Lock-Free Data Structures
293//! - **Hash Table**: SCC HashMap provides fine-grained locking optimized for high concurrency
294//! - **Ordered Index**: Crossbeam SkipList enables lock-free sorted traversal
295//! - **Atomic Operations**: All metadata updates use atomic primitives in hot path
296//!
297//! ### Async Write-Behind Logging
298//! - **Sharded Write Buffers**: Multiple write buffers with thread-consistent assignment to reduce contention
299//! - **Batched Writes**: Writes are buffered and flushed asynchronously in batches
300//! - **io_uring Integration**: On Linux, uses kernel-bypass I/O for minimal syscall overhead
301//! - **Write Coalescing**: Multiple updates to the same key are automatically coalesced
302//!
303//! ### Storage Tiers
304//! 1. **In-Memory Layer**: Hot data in SCC HashMap with O(1) access
305//! 2. **Write Buffer**: Sharded buffers with thread-local affinity for write batching
306//! 3. **Persistent Storage**: Sector-aligned async I/O with write-ahead logging
307//! 4. **Cache Layer**: CLOCK algorithm keeps frequently accessed data in memory
308//!
309//! ### Free Space Management
310//!
311//! FeOxDB uses a dual RB-tree structure for managing disk space:
312//! - One tree sorted by size for best-fit allocation
313//! - One tree sorted by address for efficient merging
314//! - Automatic coalescing of adjacent free blocks
315//! - O(log n) allocation and deallocation
316//! - Zero external fragmentation through immediate merging
317//!
318//! ## Thread Safety
319//!
320//! All operations are thread-safe and can be called concurrently:
321//!
322//! ```rust
323//! # use feoxdb::FeoxStore;
324//! # use std::sync::Arc;
325//! # use std::thread;
326//! # fn main() -> feoxdb::Result<()> {
327//! let store = Arc::new(FeoxStore::new(None)?);
328//! let mut handles = vec![];
329//!
330//! for i in 0..10 {
331//! let store_clone = Arc::clone(&store);
332//! handles.push(thread::spawn(move || {
333//! let key = format!("key_{}", i);
334//! store_clone.insert(key.as_bytes(), b"value").unwrap();
335//! }));
336//! }
337//!
338//! for handle in handles {
339//! handle.join().unwrap();
340//! }
341//! # Ok(())
342//! # }
343//! ```
344
345// Configure global allocator
346#[cfg(feature = "jemalloc")]
347#[global_allocator]
348static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
349
350pub mod constants;
351pub mod core;
352pub mod error;
353pub mod stats;
354pub mod storage;
355pub mod utils;
356
357pub use bytes::Bytes;
358pub use core::store::{FeoxStore, StoreBuilder, StoreConfig};
359pub use error::{FeoxError, Result};
360pub use stats::Statistics;
361
362#[cfg(test)]
363mod tests;