高性能 Rust 时序数据存储库: mmap 分段存储、Block 聚合、延迟压缩、持久化队列、Journal 变更日志、独立 C ABI wrapper、Python wrapper、Node.js wrapper、Java wrapper 和 .NET wrapper。
timslite 是一个可嵌入到其它项目中的本地时序数据存储引擎。它面向需要高性能本地写入、精确时间戳读取、范围查询、持久化消费队列和轻量变更日志的应用,不需要单独部署数据库服务。
你可以把主项目作为标准 Rust library 使用。C ABI 已迁移到独立的 wrapper/cffi crate (timslitecffi),Python/Node.js/Java/.NET wrapper 分别位于 wrapper/python、wrapper/nodejs、wrapper/java 和 wrapper/dotnet。
timslite 仍处于 0.x 早期发布阶段。已发布到 crates.io、PyPI、npm、Maven Central 和 NuGet;仓库主干可能已进入下一版本开发,因此 README 中的官方安装示例固定使用当前已发布的版本。Rust API、C ABI、Python wrapper、Node.js wrapper、Java wrapper、.NET wrapper 和磁盘格式在 1.0 前仍可能调整。
当前可用能力:
- Rust 核心存储引擎已实现。
- 主
timslitecrate 是标准 Rust library,不导出 C ABI 符号,也不直接构建cdylib。 - C ABI wrapper 位于 wrapper/cffi,头文件维护在 wrapper/cffi/include/timslite.h。
- Python wrapper 位于 wrapper/python。
- Node.js wrapper 位于 wrapper/nodejs。
- Java wrapper 位于 wrapper/java。
- .NET wrapper 位于 wrapper/dotnet。
- 官方包已覆盖 Rust、Python、Node.js、Java 和 .NET 生态。
- CI 已覆盖 Rust format、clippy、unit/integration tests 和 Python wrapper tests。
- 性能基准仍在后续完善中;
benches/目录已存在,但 benchmark target 还不是必过验证项。
适合:
- 在 Rust 服务或本地工具中嵌入时序数据存储。
- 高频 timestamp 写入、精确 timestamp 读取和范围查询。
- 希望用 mmap 分段文件保存本地数据,并对历史 block 做压缩。
- 需要基于 dataset 的持久化 queue 消费。
- 需要辅助热迁移、审计或同步的变更日志。
暂不适合:
- 需要严格事务、WAL 或强一致 crash recovery 的场景。
- 需要任意 UTF-8 dataset 名称的场景。公开 dataset name、dataset type、queue group name 必须匹配
^[0-9A-Za-z_-]+$。
官方 crates.io 包:
[dependencies]
timslite = "0.1.2"或使用 Cargo 命令:
cargo add timslite@0.1.2仓库开发或验证未发布改动时,可以继续使用本地 path 或 git 依赖:
[dependencies]
timslite = { path = "../timslite" }[dependencies]
timslite = { git = "https://github.com/snower/timslite" }官方 PyPI 包:
python -m pip install "timslite"发布包优先使用预编译 wheel;如果当前平台没有匹配 wheel,pip 会回退到 sdist 源码构建,需要本机 Rust toolchain 和原生编译工具链。
官方 npm 包:
npm install timsliteNode.js wrapper 要求 Node.js 22+,timestamp 和 identifier 使用 bigint 暴露以避免精度丢失。发布包包含支持平台的预编译 Node-API native binding;缺失预编译产物时会尝试源码构建。
官方 Maven Central 坐标:
<dependency>
<groupId>io.github.snower</groupId>
<artifactId>timslite</artifactId>
<version>0.1.2</version>
</dependency>Gradle:
implementation("io.github.snower:timslite:0.1.2")Java wrapper 支持 Java 8+,JAR 内包含已发布平台的 native libraries。
官方 NuGet 包:
dotnet add package Timslite.NET wrapper 目标框架为 net8.0,NuGet 包内包含已发布 RID 的 native assets。
timslite 不需要外部数据库进程。主要 Rust 依赖:
-
memmap2: mmap 文件 I/O。 -
zstd: 默认 block 压缩算法。 -
miniz_oxide: deflate 压缩支持。 -
log: 日志门面。 主 Rust library 不依赖 C ABI 运行时;libc仅用于独立的wrapper/cfficrate。
use timslite::{DataSetConfigBuilder, Store, StoreConfig};
fn main() -> timslite::Result<()> {
let store_config = StoreConfig::builder()
.enable_background_thread(true)
.enable_journal(true)
.build();
let mut store = Store::open("./data/timslite", store_config.clone())?;
let dataset_config = DataSetConfigBuilder::from_store(&store_config)
.index_continuous(0)
.retention_window(0);
let dataset = store.create_dataset_with_config(
"sensor",
"temperature",
Some(dataset_config),
)?;
dataset.write(1_700_000_001, b"21.5")?;
dataset.write(1_700_000_002, b"21.7")?;
let row = dataset.read(1_700_000_001)?;
assert_eq!(row.unwrap().1, b"21.5");
let rows = dataset.query(1_700_000_001, 1_700_000_010)?;
assert_eq!(rows.len(), 2);
store.close()?;
Ok(())
}打开已有 dataset:
use timslite::{Store, StoreConfig};
fn main() -> timslite::Result<()> {
let mut store = Store::open("./data/timslite", StoreConfig::default())?;
let dataset = store.open_dataset("sensor", "temperature")?;
let latest = dataset.read_latest()?;
println!("latest: {:?}", latest);
Ok(())
}DataSet::read_latest() 会读取 latest_written_timestamp 对应的精确 timestamp。如果该 timestamp 已删除或已过期,会返回 Ok(None),不会自动向前搜索上一条有效数据。DataSet::read(timestamp) 始终按传入的精确 timestamp 读取;read(-1) 读取真实 timestamp -1,不表示 latest。
Store 是顶层入口,负责:
- dataset 生命周期、registry、全局运行时上下文。
- 全局 immutable compressed-block cache。
- 可选后台维护线程。
- 可选内置
.journal/logs专用 journal 存储。 - 新建 dataset 时使用的默认配置。
Store::create_dataset* 和 Store::open_dataset* 直接返回 DataSet。普通写入、读取、删除、查询和普通 dataset queue 都在 DataSet / DatasetQueue 上操作,Store 不再提供面向 record 的 facade API。
dataset 由 (name, dataset_type) 标识,例如 ("sensor", "temperature")。
通过 Store 获取的 DataSet 会携带 cache、journal sink、read-only 状态等运行时上下文;调用方不需要也不应该为普通 record API 传入这些内部资源。
常用操作:
-
write(timestamp, data): 写入或修正一个 timestamp。 -
append(timestamp, data): 追加到最新 tail record,或创建新的未来 timestamp。 -
delete(timestamp): 标记一个 timestamp 为删除。 -
read(timestamp): 精确读取一个 timestamp。 -
query(start, end): 范围查询,左右闭区间。 -
read_exist/query_exist/read_length/query_length: 轻量存在性和长度查询。
单条逻辑 record 最大 4 MiB。普通聚合 block payload 上限为 64 KiB;更大的 record 会使用 single-record block。
timestamp 是应用传入的 signed i64,timslite 不要求它必须是系统时间。0 和负数都是合法业务 timestamp;读取 latest 请使用显式 read_latest()。
如果业务 timestamp 使用秒,retention_window 也应使用秒。如果业务使用其它单位,retention 也必须使用同一单位。
index_continuous = 0: 稀疏索引,通常作为默认选择。
index_continuous = 1: 连续 timestamp grid,gap 使用 filler entry 表示。适合需要固定 timestamp slot 的场景,但稀疏写入时会产生更多索引项。
pending block 保持 raw 且可变。下一次写入导致 pending block overflow 时,旧 block 会 seal、compress,并变为 immutable。只有 immutable compressed block 会进入全局读缓存。
默认压缩算法是 zstd。compress_type = 1 时可使用 deflate。
use std::time::Duration;
use timslite::StoreConfig;
let config = StoreConfig::builder()
.data_segment_size(64 * 1024 * 1024)
.index_segment_size(4 * 1024 * 1024)
.initial_data_segment_size(256 * 1024)
.initial_index_segment_size(4 * 1024)
.compress_type(0) // 0=zstd, 1=deflate
.compress_level(6)
.cache_max_memory(256 * 1024 * 1024)
.flush_interval(Duration::from_secs(15))
.idle_timeout(Duration::from_secs(30 * 60))
.retention_check_hour(0) // UTC hour
.enable_background_thread(true)
.enable_journal(true)
.build();新建 dataset 时,可以基于 store 默认配置创建 dataset 专属配置:
use timslite::DataSetConfigBuilder;
let dataset_config = DataSetConfigBuilder::from_store(&config)
.index_continuous(0)
.retention_window(30 * 86400)
.build();append(timestamp, data) 用于扩展最新记录:
-
timestamp < latest_written_timestamp: 返回错误。 -
timestamp > latest_written_timestamp: 创建新 record。 -
timestamp == latest_written_timestamp: 仅当 latest record 仍是未压缩 tail record 时允许原地追加。 - 空 append 会先执行 timestamp 顺序和 retention 校验,再作为 no-op。
- 追加后的逻辑 record 仍不能超过 4 MiB。
追加到已有 latest record 不会再次通知普通 dataset queue;创建新 timestamp 时会通知 queue。
每个 dataset 可以打开一个持久化 queue。每个 consumer group 使用独立的 4 KiB state file 保存消费进度。
use std::time::Duration;
use timslite::{Store, StoreConfig};
fn main() -> timslite::Result<()> {
let mut store = Store::open("./data/timslite-queue", StoreConfig::default())?;
let dataset = store.create_dataset_with_config("jobs", "default", None)?;
let queue = dataset.open_queue()?;
let consumer = queue.open_consumer("worker_1")?;
let ts = queue.push(b"job payload")?;
if let Some((polled_ts, payload)) = consumer.poll(Duration::from_secs(1))? {
assert_eq!(polled_ts, ts);
assert_eq!(payload, b"job payload");
consumer.ack(polled_ts)?;
}
Ok(())
}如果 consumer 需要收到后续 push 的数据,应先打开 consumer 再 push。新 consumer 的初始位置从当前 latest_written_timestamp 开始。
Journal 由 StoreConfig.enable_journal(true) 控制,默认开启。普通 dataset 的 DataSetConfig.enable_journal 默认关闭,需要显式开启后才记录该 dataset 的 create/drop/write/delete/append。
内置 journal 存储固定为 .journal/logs,不作为普通 DataSet 暴露,记录:
-
0x01: create dataset。 -
0x02: drop dataset。 -
0x11: dataset write。 -
0x12: dataset delete。 -
0x13: dataset append。
Journal timestamp 是从 1 开始递增的 sequence,不是系统时间。
Journal v1 是辅助变更日志,不是严格 WAL。write/delete/append record 中保存的是源 dataset 的 index pointer。消费者如果需要 payload,必须在源 dataset 仍可访问时通过 Store::read_journal_source_record(dataset_identifier, index_info) 读取:
let journal_queue = store.open_journal_queue()?;
let consumer = journal_queue.open_consumer("migrator_1")?;如果源 dataset 已删除、已被 retention 回收、已 checkpoint、已 correction 或已覆盖,旧 journal record 可能无法单独精确 replay。
retention_window 是 dataset 级配置,单位与业务 timestamp 相同:
let dataset_config = timslite::DataSetConfigBuilder::from_store(&store_config)
.retention_window(30 * 86400); // timestamp 为秒时表示 30 天启用 retention 后:
- 过期 timestamp 的读取返回
None。 - 过期 timestamp 不允许 delete、out-of-order rewrite 或 correction。
- 回收只删除整个时间范围都已过期的 data/index segment。
-
retention_check_hour使用 UTC hour,范围0..=23。
C ABI 是独立 wrapper crate,不属于主 timslite Rust library。公开 C 头文件位于 wrapper/cffi/include/timslite.h,实现位于 wrapper/cffi,crate 名为 timslitecffi。
C ABI wrapper 暂不作为独立官方包发布;需要 C ABI 时从源码构建:
cargo build --manifest-path wrapper/cffi/Cargo.toml --release
cargo test --manifest-path wrapper/cffi/Cargo.toml -- --test-threads=1主要 API 组:
- Store lifecycle:
tmsl_store_open,tmsl_store_open_with_config,tmsl_store_close。 - Dataset lifecycle:
tmsl_dataset_create,tmsl_dataset_create_with_config,tmsl_dataset_open,tmsl_dataset_close,tmsl_dataset_drop。 - Data operations:
tmsl_dataset_write,tmsl_dataset_append,tmsl_dataset_delete,tmsl_dataset_read,tmsl_dataset_query。 - Queue operations:
tmsl_queue_open,tmsl_queue_consumer_open,tmsl_queue_poll,tmsl_queue_ack。 - Background helpers:
tmsl_store_tick_background_tasks,tmsl_store_next_background_delay。
read/query/queue API 返回的 buffer 必须使用头文件中声明的 timslite free 函数释放。
Python wrapper 位于 wrapper/python,暴露与 Rust 类似的 Store、DataSet、Query、Queue 概念。
官方安装:
python -m pip install "timslite"仓库开发和测试:
cd wrapper/python
pip install maturin pytest
maturin develop
python -m pytest tests/ -vPython 例子见 wrapper/python/README.md。
Node.js wrapper 位于 wrapper/nodejs,基于 Node-API (napi-rs),暴露与 Rust 类似的 Store、Dataset、Queue 概念。
官方安装:
npm install timslite本地构建和测试:
cd wrapper/nodejs
npm install
npm run build
node -e "const t = require('.'); console.log('version:', t.version())"Node.js wrapper 使用 BigInt 表示 timestamp 和 identifier,避免精度丢失。
Java wrapper 位于 wrapper/java,基于 UniFFI 生成 C ABI 绑定并通过 Kotlin/JVM 后端暴露 Java API。支持 Java 8+,提供 Store、Dataset、Queue、Journal 等完整功能。
官方 Maven Central 坐标:
<dependency>
<groupId>io.github.snower</groupId>
<artifactId>timslite</artifactId>
<version>0.1.2</version>
</dependency>Java wrapper 详情见 wrapper/java/README.md,设计文档见 wrapper/java/design.md。
.NET wrapper 位于 wrapper/dotnet,基于 UniFFI 生成 .NET 绑定,提供 Store、Dataset、Queue、Journal 等 API。
官方 NuGet 安装:
dotnet add package Timslite.NET wrapper 详情见 wrapper/dotnet/README.md。
- Dataset name、dataset type、queue group name 必须匹配
^[0-9A-Za-z_-]+$,最长 255 字节。 -
.journal/logs是 Store 内部保留路径,不作为普通DataSet打开。Journal 开启时通过journal_read/journal_query/open_journal_queue等专用 API 访问。 - 磁盘上的多字节 integer 使用 little-endian。
- 后台 flush 默认 15 秒,只执行 mmap sync,不 seal、不压缩 pending block。
- timslite 优先高读写性能,不提供严格事务保证。crash 后最近未 flush 的写入可能丢失。
- 在本仓库运行测试时建议使用
cargo test -- --test-threads=1,因为文件系统测试共享临时路径。
作为依赖集成时,通常只需要正常 cargo build。如果你从源码验证:
cargo build
cargo build --release
cargo test -- --test-threads=1根 crate 的构建只验证 Rust library;需要 C ABI 时请单独构建 wrapper/cffi:
cargo build --manifest-path wrapper/cffi/Cargo.toml --release
cargo test --manifest-path wrapper/cffi/Cargo.toml -- --test-threads=1更严格的本地检查:
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings设计文档入口是 design.md。常用专题: