# expire_cache: High-performance generational cache
`expire_cache` implements an efficient expiration cache using generational collection strategy. Instead of tracking individual item expiration times, it maintains two data buckets (generations), significantly reducing memory overhead and CPU usage for expiration checks.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Design Architecture](#design-architecture)
- [Technical Stack](#technical-stack)
- [Directory Structure](#directory-structure)
- [Historical Context](#historical-context)
## Features
- **High Performance**: O(1) amortized expiration overhead per item
- **Concurrent Access**: Built on DashMap for thread-safe operations
- **Async Support**: Native async initialization with `get_or_init_async`
- **Flexible Storage**: Support for both key-value maps and sets
- **Simple API**: Clean interface with `get`, `insert`, and initialization methods
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
expire_cache = "0.1.15"
```
Enable features as needed:
```toml
[dependencies]
expire_cache = { version = "0.1.15", features = ["dashmap", "get_or_init_async"] }
```
Available features:
- `dashmap`: Enable DashMap support
- `dashset`: Enable DashSet support
- `get_or_init`: Enable synchronous initialization
- `get_or_init_async`: Enable asynchronous initialization
## Quick Start
### Basic Map Usage
```rust
use expire_cache::Expire;
use dashmap::DashMap;
use std::time::Duration;
#[tokio::main]
async fn main() {
let cache: Expire<DashMap<&str, &str>> = Expire::new(60);
cache.insert("key", "value");
if let Some(val) = cache.get("key") {
println!("Found: {}", *val);
}
// Wait for expiration
tokio::time::sleep(Duration::from_secs(65)).await;
assert!(cache.get("key").is_none());
}
```
### Async Initialization
```rust
use expire_cache::{AsyncInit, Expire};
use dashmap::DashMap;
struct DatabaseLoader;
impl AsyncInit for DatabaseLoader {
type Key = &'static str;
type Val = String;
type Error = aok::Error;
async fn init(&self, key: &Self::Key) -> Result<Self::Val, Self::Error> {
// Simulate database query
Ok(format!("data_for_{}", key))
}
}
#[tokio::main]
async fn main() -> aok::Result<()> {
let cache: Expire<DashMap<&str, String>> = Expire::new(60);
let value = cache.get_or_init_async::<DatabaseLoader>("user_123", DatabaseLoader).await?;
println!("Loaded: {}", *value);
Ok(())
}
```
### Set Usage
```rust
use expire_cache::Expire;
use dashmap::DashSet;
#[tokio::main]
async fn main() {
let cache: Expire<DashSet<&str>> = Expire::new(60);
cache.insert("active_session", ());
if cache.get("active_session").is_some() {
println!("Session exists");
}
}
```
## API Reference
### Core Types
#### `Expire<T: Map>`
Main cache wrapper providing expiration functionality.
**Methods:**
- `new(expire: u64) -> Self`: Create cache with expiration period in seconds
- `get(&self, key) -> Option<RefVal>`: Retrieve value from cache
- `insert(&self, key, val)`: Insert value into cache
- `get_or_init(&self, key, func) -> Result<RefVal, E>`: Sync initialization
- `get_or_init_async(&self, key, init) -> Result<RefVal, E>`: Async initialization
#### `Map` Trait
Core abstraction for storage backends.
**Required Methods:**
- `clear(&self)`: Clear all entries
- `insert(&self, key, val)`: Insert key-value pair
- `get(&self, key) -> Option<RefVal>`: Get value by key
#### `AsyncInit` Trait
Async value initialization interface.
**Required Methods:**
- `init(&self, key) -> impl Future<Output = Result<Val, Error>>`: Initialize value
## Design Architecture
### Generational Collection Strategy
The cache uses a double-buffer approach with two generations:
```mermaid
graph TD
A[New Insert] --> B[Active Generation]
B --> C[Read Check Active]
C -->|Found| D[Return Value]
C -->|Not Found| E[Check Passive Generation]
E -->|Found| D
E -->|Not Found| F[Return None]
G[Timer Task] --> H[Clear Passive Generation]
H --> I[Swap Active/Passive Roles]
I --> B
style B fill:#e1f5fe
style H fill:#fff3e0
```
### Data Flow
1. **Insertion**: New entries always go to the active generation
2. **Lookup**: Check active generation first, then passive generation
3. **Expiration**: Background task periodically clears passive generation and swaps roles
4. **Lifecycle**: Items live between `expire` and `2 * expire` seconds
### Memory Management
The cache uses atomic operations for generation switching and leverages `boxleak` for stable memory allocation of the internal structure.
## Technical Stack
- **Core Language**: Rust 2024 Edition
- **Concurrency**: DashMap for lock-free concurrent access
- **Async Runtime**: Tokio for async operations and timer management
- **Memory Management**: `boxleak` for stable allocation, `sendptr` for safe pointer sharing
- **Error Handling**: `aok` for lightweight error types
## Directory Structure
```
src/
├── lib.rs # Public API exports and core Expire struct
├── map.rs # DashMap implementation and RefVal wrapper
├── set.rs # DashSet implementation
├── get_or_init.rs # Synchronous initialization trait
└── get_or_init_async.rs # Asynchronous initialization trait
tests/
└── main.rs # Comprehensive test suite
```
## Historical Context
The concept of generational caching draws inspiration from both hardware design and garbage collection strategies. Early mainframe systems like IBM System/360 introduced caches to bridge CPU-memory speed gaps. Over time, strategies evolved from simple LRU (Least Recently Used) to more sophisticated generational approaches.
In hardware design, "Cache Decay" techniques reduce power consumption by deactivating cache lines unused for extended periods. Similarly, `expire_cache` treats time intervals as generations. By bulk-discarding entire generations, it avoids the massive overhead of tracking individual item timestamps—reminiscent of generational garbage collectors that assume "young" objects often die quickly and can be collected in batches.
This approach trades absolute precision (exact expiration times) for significant throughput improvements and reduced memory fragmentation, making it ideal for high-throughput scenarios where approximate expiration timing is acceptable.
## License
MulanPSL-2.0