---
<a id="en"></a>
# 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
---
## About
This project is an open-source component of [js0.site ⋅ Refactoring the Internet Plan](https://js0.site).
We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:
* [Google Group](https://groups.google.com/g/js0-site)
* [js0site.bsky.social](https://bsky.app/profile/js0site.bsky.social)
---
<a id="zh"></a>
# expire_cache: 高性能分代缓存
`expire_cache` 实现了基于分代收集策略的高效过期缓存。它不追踪单个条目的过期时间,而是维护两个数据桶(代),显著降低过期检查的内存开销和 CPU 使用率。
## 目录
- [特性](#特性)
- [安装](#安装)
- [快速开始](#快速开始)
- [API 参考](#api-参考)
- [设计架构](#设计架构)
- [技术堆栈](#技术堆栈)
- [目录结构](#目录结构)
- [历史背景](#历史背景)
## 特性
- **高性能**:每条目摊销 O(1) 过期开销
- **并发访问**:基于 DashMap 的线程安全操作
- **异步支持**:原生异步初始化 `get_or_init_async`
- **灵活存储**:支持键值映射和集合
- **简洁 API**:清晰的 `get`、`insert` 和初始化接口
## 安装
在 `Cargo.toml` 中添加:
```toml
[dependencies]
expire_cache = "0.1.15"
```
按需启用特性:
```toml
[dependencies]
expire_cache = { version = "0.1.15", features = ["dashmap", "get_or_init_async"] }
```
可用特性:
- `dashmap`:启用 DashMap 支持
- `dashset`:启用 DashSet 支持
- `get_or_init`:启用同步初始化
- `get_or_init_async`:启用异步初始化
## 快速开始
### 基本映射用法
```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("键", "值");
if let Some(val) = cache.get("键") {
println!("找到: {}", *val);
}
// 等待过期
tokio::time::sleep(Duration::from_secs(65)).await;
assert!(cache.get("键").is_none());
}
```
### 异步初始化
```rust
use expire_cache::{AsyncInit, Expire};
use dashmap::DashMap;
struct 数据库加载器;
impl AsyncInit for 数据库加载器 {
type Key = &'static str;
type Val = String;
type Error = aok::Error;
async fn init(&self, key: &Self::Key) -> Result<Self::Val, Self::Error> {
// 模拟数据库查询
Ok(format!("数据_{}", key))
}
}
#[tokio::main]
async fn main() -> aok::Result<()> {
let cache: Expire<DashMap<&str, String>> = Expire::new(60);
let value = cache.get_or_init_async::<数据库加载器>("用户_123", 数据库加载器).await?;
println!("已加载: {}", *value);
Ok(())
}
```
### 集合用法
```rust
use expire_cache::Expire;
use dashmap::DashSet;
#[tokio::main]
async fn main() {
let cache: Expire<DashSet<&str>> = Expire::new(60);
cache.insert("活跃会话", ());
if cache.get("活跃会话").is_some() {
println!("会话存在");
}
}
```
## API 参考
### 核心类型
#### `Expire<T: Map>`
提供过期功能的主缓存包装器。
**方法:**
- `new(expire: u64) -> Self`:创建带过期周期的缓存(秒)
- `get(&self, key) -> Option<RefVal>`:从缓存检索值
- `insert(&self, key, val)`:向缓存插入值
- `get_or_init(&self, key, func) -> Result<RefVal, E>`:同步初始化
- `get_or_init_async(&self, key, init) -> Result<RefVal, E>`:异步初始化
#### `Map` Trait
存储后端的核心抽象。
**必需方法:**
- `clear(&self)`:清除所有条目
- `insert(&self, key, val)`:插入键值对
- `get(&self, key) -> Option<RefVal>`:按键获取值
#### `AsyncInit` Trait
异步值初始化接口。
**必需方法:**
- `init(&self, key) -> impl Future<Output = Result<Val, Error>>`:初始化值
## 设计架构
### 分代收集策略
缓存使用双缓冲方法,包含两个代:
```mermaid
graph TD
A[新插入] --> B[活跃代]
B --> C[检查活跃代]
C -->|找到| D[返回值]
C -->|未找到| E[检查被动代]
E -->|找到| D
E -->|未找到| F[返回空]
G[定时任务] --> H[清空被动代]
H --> I[交换活跃/被动角色]
I --> B
style B fill:#e1f5fe
style H fill:#fff3e0
```
### 数据流
1. **插入**:新条目总是进入活跃代
2. **查找**:先检查活跃代,再检查被动代
3. **过期**:后台任务定期清空被动代并交换角色
4. **生命周期**:条目存活时间在 `expire` 到 `2 * expire` 秒之间
### 内存管理
缓存使用原子操作进行代切换,利用 `boxleak` 实现内部结构的稳定内存分配。
## 技术堆栈
- **核心语言**:Rust 2024 Edition
- **并发**:DashMap 实现无锁并发访问
- **异步运行时**:Tokio 处理异步操作和定时器管理
- **内存管理**:`boxleak` 稳定分配,`sendptr` 安全指针共享
- **错误处理**:`aok` 轻量级错误类型
## 目录结构
```
src/
├── lib.rs # 公共 API 导出和核心 Expire 结构体
├── map.rs # DashMap 实现和 RefVal 包装器
├── set.rs # DashSet 实现
├── get_or_init.rs # 同步初始化 trait
└── get_or_init_async.rs # 异步初始化 trait
tests/
└── main.rs # 综合测试套件
```
## 历史背景
分代缓存的概念借鉴了硬件设计和垃圾收集策略的智慧。早期的大型机系统如 IBM System/360 引入缓存以弥合 CPU 与内存的速度差异。随着时间的推移,策略从简单的 LRU(最近最少使用)演变为更复杂的分代方法。
在硬件设计中,"缓存衰减"技术通过关闭长时间未访问的缓存行来降低功耗。类似地,`expire_cache` 将时间区间视为代。通过批量丢弃整代数据,它避免了追踪单个条目时间戳的巨大开销——这让人联想到分代垃圾收集器,它们假设"年轻"对象往往早夭,因此可以批量回收。
这种方法牺牲了绝对精度(精确的过期时间),换取了显著的吞吐量提升和内存碎片减少,使其非常适合对近似过期时间可接受的高吞吐量场景。
## 许可证
MulanPSL-2.0
---
## 关于
本项目为 [js0.site ⋅ 重构互联网计划](https://js0.site) 的开源组件。
我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注:
* [谷歌邮件列表](https://groups.google.com/g/js0-site)
* [js0site.bsky.social](https://bsky.app/profile/js0site.bsky.social)