Skip to content

Security: YoKONCy/TriviumDB

Security

docs/security.md

TriviumDB 安全特性详解

基于源码的完整文档,覆盖并发安全、数据完整性、内存安全、输入验证与跨平台 I/O 加固五个维度。


目录


并发安全模型

1. 进程级互斥锁(文件锁)

实现位置database.rs:169-181

let lock_file = std::fs::OpenOptions::new()
    .create(true).write(true)
    .open(&lock_path)?;
lock_file.try_lock_exclusive().map_err(|_| {
    TriviumError::Generic(format!(
        "Database '{}' is already opened by another process. ...", path, lock_path
    ))
})?;

通过 fs2::FileExt::try_lock_exclusive<db_path>.lock 文件上持有独占锁。锁由 Database 结构体的 _lock_file 字段持有,在 Database drop 时自动释放。

保证

  • 同一数据库文件不可能被两个进程同时写入
  • 锁文件在进程异常退出后由 OS 自动释放(不同于 PID 文件,不会产生死锁残留)
  • 在 Linux/macOS(flock)和 Windows(LockFileEx)上均有效

2. 线程级 Arc<Mutex<T>> + 中毒恢复

实现位置database.rs:114-121 + 全文所有读写操作

fn lock_or_recover<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
    mutex.lock().unwrap_or_else(|poisoned| {
        tracing::warn!("Mutex was poisoned, recovering...");
        poisoned.into_inner()
    })
}

MemTableWal 均包装在 Arc<Mutex<T>> 中。任何线程 panic 并持有锁时,Mutex 转为"中毒"状态。lock_or_recover 在此情形下不会 unwrap 崩溃,而是主动剥离中毒标记,恢复内部数据继续服务

保证

  • 单线程 panic 不会导致整个进程崩溃
  • 后续请求可以继续正常访问数据(数据状态由 WAL 保证一致性)
  • 所有读写操作均通过此函数获取锁,零例外

3. WAL 写入的锁隔离策略

实现位置database.rs:279-293(以 insert 为例)

// 先持有 memtable 锁写内存,再持有 wal 锁写日志
// 两把锁不同时持有,避免死锁
let id = {
    let mut mt = lock_or_recover(&self.memtable);
    mt.insert(vector, payload.clone())?
};
{
    let mut w = lock_or_recover(&self.wal);
    w.append(&WalEntry::Insert { ... })?;
}

memtable 锁和 wal从不同时持有——先释放 memtable 锁,再获取 wal 锁。这是经典的锁顺序规则,消除了死锁的可能性。

保证

  • 任意并发调用组合下不会发生死锁
  • 不同操作(insert、delete、link)遵循完全一致的锁获取顺序

4. Compaction 线程的并发安全

实现位置storage/compaction.rs

后台 Compaction 线程通过 Arc::clone 共享 memtablewal 的引用,同样使用 lock_or_recover 获取锁。Compaction 操作与前台写操作在同一个 Mutex 下序列化,不存在竞态条件


数据完整性保障

5. WAL CRC32 逐条校验

实现位置storage/wal.rs:113-115(写入),wal.rs:232-244(读取)

每条 WAL 记录的磁盘格式为:

[len: u32 (4B)] [bincode 序列化数据: len bytes] [crc32: u32 (4B)]

写入时计算,读取(崩溃恢复)时验证:

// 写入
let checksum = crc32fast::hash(&data);
writer.write_all(&checksum.to_le_bytes())?;

// 恢复
let computed_crc = crc32fast::hash(&data);
if stored_crc != computed_crc {
    tracing::error!("WAL CRC mismatch ... Stopping recovery.");
    break; // 停止回放,丢弃后续数据
}

保证:磁盘坏块、OS 写半条记录均可被检测。CRC 不匹配时停止回放而非跳过,防止损坏数据静默渗入。


6. WAL 单条记录大小上界检查

实现位置storage/wal.rs:214-217

// 单条不超过 256MB
if len > 256 * 1024 * 1024 {
    break; // 损坏的 len 字段
}

防止损坏的 len 字段触发天量内存分配(OOM 或 DoS)。


7. WAL 事务原子性(TxBegin / TxCommit 封条)

实现位置storage/wal.rs:260-297

崩溃恢复时对事务做两阶段过滤:只有见到匹配 TxCommit 的事务才会回放,否则整体丢弃:

状态 行为
TxBegin + 匹配 TxCommit 全量回放
TxBegin,无 TxCommit(掉电) 整体丢弃,并物理截断 WAL 尾部(Partial Truncation)
无事务边界的独立操作(旧格式) 直接回放(向后兼容),推进安全游标

极限防御:LSN (Log Sequence Number) 与物理截断 如果在回放时发现未闭合的事务(通常因为机器暴力断电),系统不仅在内存中丢弃它们,还会通过计算最后一个完美闭环事务的精确物理字节偏移量 (safe_commit_offset),在重播前直接调用内置 set_len()。这彻底切断了具有传染性的“幽灵尾部”,防止系统下次启动接收正常追加后,由于 in_tx=true 的状态污染,将新的健康数据错吞进旧的失效事务里。

保证:要么全部回放,要么全部丢弃;不出现"插入了 5 条、应该 10 条"的部分状态。并且绝对防止失效事务封条污染后续追加数据。


8. Mmap 双文件一致性标记(.flush_ok

实现位置storage/file_format.rs:86-103(写),file_format.rs:282-326(读)

Mmap 模式下,.tdb.vec 均写入成功后,才原子写 .flush_ok 标记,内含两文件的精确字节大小

[tdb_size: u64 (8B)] [vec_size: u64 (8B)]

加载时交叉校验,失败则降级为安全模式(忽略 .vec,仅从 .tdb 骨架恢复 + WAL 回放)。


9. 文件魔数与最小尺寸校验

实现位置storage/file_format.rs:257-267

const MAGIC: &[u8; 4] = b"TVDB";

if mmap.len() < HEADER_SIZE as usize {
    return Err(TriviumError::Generic("File too small for header".into()));
}
if &bytes[0..4] != MAGIC {
    return Err(TriviumError::Generic(
        format!("Invalid file magic: expected TVDB, got {:?}", &bytes[0..4])
    ));
}

防止加载非 TriviumDB 文件或截断损坏文件,同时避免后续偏移量计算出现越界读取。


10. 原子写入协议:write-tmp → fsync → rename

实现位置storage/file_format.rs:142-240storage/vec_pool.rs:flush_rewrite()

所有持久化路径均遵循:

① 写 .tmp 临时文件 → ② sync_all() → ③ robust_rename(tmp → 正式文件)

任何步骤崩溃,旧文件完好。.tmp 在下次启动时可安全忽略。


11. WAL 三级落盘模式

实现位置storage/wal.rs:48-68

pub enum SyncMode {
    Full,    // 每条后 fsync — 防 OS 崩溃,金融级
    Normal,  // 每条后 flush 到 OS 缓冲 — 防进程崩溃(默认)
    Off,     // 不主动 flush — 仅测试
}

12. WAL Drop 安全刷盘

实现位置storage/wal.rs:336-341

Database::drop 时调用 flush_writer(),将 BufWriter 缓冲区主动刷入磁盘,防止正常退出时因 Arc<Mutex<Wal>> 析构链导致的静默数据丢失。


内存安全与 unsafe 边界

13. mmap 的 unsafe 安全契约

实现位置storage/vec_pool.rs:open()flush_append()

// SAFETY: MAP_PRIVATE (copy-on-write)
//   - VectorType 要求 T: Pod + Zeroable,字节对齐和全零初始化安全
//   - len 由 expected_count * dim * size_of::<T>() 精确计算,不超出文件大小
let mmap = unsafe {
    memmap2::MmapOptions::new().len(expected_size).map_copy(&file)?
};

MAP_PRIVATE 映射:写入只产生进程私有 COW 页,不影响底层文件,其他进程/映射不受影响。


14. mmap 字节到 &[T] 转换安全性

实现位置storage/vec_pool.rs:get()rebuild_merged_cache()

let ptr = bytes.as_ptr();
if (ptr as usize) % std::mem::align_of::<T>() == 0 {
    unsafe { std::slice::from_raw_parts(ptr as *const T, self.dim) }
} else {
    // 非对齐:bytemuck::pod_read_unaligned 安全回退
}

mmap 返回的地址始终页对齐(4096B);f32 需 4B 对齐、u64 需 8B 对齐,均严格满足。代码中有运行时对齐检查和安全回退路径。


15. AVX2 SIMD 运行时 CPU 特性检测

实现位置vector.rs:130-141

if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
    // SAFETY: 运行时已确认 CPU 支持
    return unsafe { cosine_similarity_avx2(a, b) };
}
cosine_similarity_scalar(a, b) // 安全标量回退

不支持 AVX2 的 CPU 自动回退到纯 Rust 路径,不会执行非法指令


16. bytemuck::Pod 编译期内存安全约束

实现位置vector.rs:13-14

pub trait VectorType: bytemuck::Zeroable + bytemuck::Pod + ...

Pod(Plain Old Data)是编译期保证:无指针、无引用、无 padding 歧义、全零合法。使得 bytemuck::cast_slice 在编译期被证明安全,完全不需要运行时 unsafe。


17. 数组边界守卫

实现位置database.rs(BQ 精排分支)

if offset + dim <= vectors.len() {
    let score = T::similarity(query_vector, &vectors[offset..offset + dim]);
}

访问 flat_vectors 时,始终通过 offset + dim <= len 守卫,防止 mmap_count 与实际数组大小不一致时的越界访问。


资源配额与恶意负载防御 (Anti-DoS & OOM)

18. Cypher 引擎的 Lazy Evaluation 防 OOM 内存大爆炸

实现位置query/executor.rs:eval_expr_by_id() 和路径扩展逻辑

// 🚀 OOM 防御:我们只在中间计算层存储 NodeId,绝对不克隆包含 Vector 和 Payload 的巨型 Node 结构!
let mut bindings_set: Vec<HashMap<String, u64>> = Vec::new();

由于图查询匹配(如 (a)-[]->(b)-[]->(c))会产生爆炸性的笛卡尔积中间结果,若在每一步扩展中传递深拷贝的实体节点数据(包含高维向量、全量 JSON Payload),1 万条路径即会瞬间击穿几个 GB 的内存。 TriviumDB 执行器在整条中间路径遍历中仅保留轻量级 u64 IDs,所有的 WHERE 财产过滤条件使用即时查询计算,仅仅在最后的 RETURN 吐出最终小切片结果时,才去执行昂贵的 build_node 数据装填,物理上隔离了内存爆炸。


19. 向量维度强校验

实现位置storage/memtable.rsdatabase.rs:Transaction::commit()

if vector.len() != self.dim {
    return Err(TriviumError::DimensionMismatch { expected: dim, got: vector.len() });
}

所有写入向量路径(insertinsert_with_idupdate_vector、事务 Dry-Run)均强校验维度,返回类型化错误而非 panic。


20. 查询向量 NaN / Infinity 检测

实现位置database.rs:462-475

for item in qv {
    let f = item.to_f32();
    if f.is_nan() || f.is_infinite() {
        return Err(TriviumError::Generic("Query vector contains NaN or Infinity".into()));
    }
}

NaN 进入余弦计算会传染整个结果。在检索管线 L0 层直接拦截,防止毒素向量污染后续所有计算。


21. 认知管线参数钳位(防数学奇点)

实现位置database.rs:478-485

safe_cfg.top_k              = safe_cfg.top_k.max(1);
safe_cfg.fista_lambda       = safe_cfg.fista_lambda.clamp(1e-5, 100.0);
safe_cfg.teleport_alpha     = safe_cfg.teleport_alpha.clamp(0.0, 1.0);
safe_cfg.dpp_quality_weight = safe_cfg.dpp_quality_weight.clamp(0.0, 10.0);
safe_cfg.bq_candidate_ratio = safe_cfg.bq_candidate_ratio.clamp(0.0, 1.0);

所有数学参数强制钳入合法范围:fista_lambda 过大全变 0,teleport_alpha 超出 [0,1] PPR 概率失去意义,dpp_quality_weight 过大导致 float 溢出。


22. 事务 Dry-Run 预检与极巨载荷拦截(零损伤回滚)

实现位置database.rs:Transaction::commit() 第一阶段及其他直写 API

所有业务验证(节点是否存在、ID 是否冲突、维度是否匹配、是否包含 NaN/Inf)在纯内存虚拟状态上完成,不触碰 MemTable 或 WAL。

防OOM极巨载荷拦截 (Payload Limiting):为防止恶意构造或意外产生的数以百兆计的超大 Payload 文本占用物理内存和撑爆日志,Database::insertupdate_payloadTransaction::commit 会强制性对 JSON 载荷施加 8MB 大小限制 (MAX_PAYLOAD_SIZE)。超越此数值的数据写入将被直接判定失败,保护操作系统的内存水位和文件视窗。

任何验证失败直接返回 Err,MemTable 和 WAL 零损伤。只有 Dry-Run 全部通过,才进入不可失败的 WAL 写入 → MemTable 应用路径。


23. Tombstone 节点的防误更新

实现位置storage/memtable.rs:update_vector()

// 必须检查 payload 存在性,而非 ids_to_indices
// delete() 移除 payload 但 ids_to_indices 中的槽位仍存在(指向已置零位置)
if !self.payloads.contains_key(&id) {
    return Err(TriviumError::NodeNotFound(id));
}

防止对已逻辑删除的节点进行向量更新。


24. BQ 索引与 FreeList 墓碑复用(零 Ghost Node)

实现位置database.rs:delete()storage/memtable.rsindex/bq.rs

传统图索引(如 HNSW)在节点被逻辑删除后,会在索引图中留下幽灵引用,导致检索结果污染和性能退化。TriviumDB 通过 FreeList 墓碑隐式复用机制 彻底消除了该问题:

  • 原位物理擦除:无需进行极其耗时的全局紧凑,已删除的 index 将被推入快速复用队列。下一个插入的节点直接占据其所在的物理行。
  • 并行特征网同步清零:删除节点时,对应的 fast_tags 位特征槽不仅立即随之置零,彻底杜绝该废弃指纹在后续查询被误读;
  • BQ 动态刷新:下一次 Compaction 触发 BQ 增量重建时,由于底层是纯扁平的一维二进制指纹数组(Vec<u64>),置零的槽位自然产生空回聚光灯盲区;新索引通过直接赋值原子切换,旧索引并发安全释放。

保证:删除操作对检索质量零副作用,无空间碎片,无需用户手动触发重建,不仅杜绝了 Ghost Node 幽灵节点,更实现了无限频次改写下的 O(1) 平均生命周期开销!


25. 图谱扩散防爆炸截断

实现位置graph/traversal.rs:69-83

// 能量阈值守护:得分 ≤ 0 的节点不再传播(防负反馈循环)
next_tier.retain(|_, energy| *energy > 0.0);

// 侧向抑制 Top-K(防稠密图 OOM)
if lateral_inhibition_threshold > 0 && next_tier.len() > lateral_inhibition_threshold {
    sorted_tier.truncate(lateral_inhibition_threshold);
}

if next_tier.is_empty() { break; } // 能量衰竭,提前终止

两道独立截断:能量守护防负反馈循环,侧向抑制 Top-K 防稠密图爆炸性展开导致 OOM。


跨平台 I/O 加固(Windows 兼容性)

24. mmap 释放先于 rename(P0 修复)

实现位置storage/vec_pool.rs:flush_rewrite()flush_append()

self.mmap = None;           // 先解除内核映射锁
robust_rename(&tmp, dst)?;  // 再执行原子替换

Windows 强制锁定语义:映射存活时 rename 目标文件必定 ERROR_ACCESS_DENIED。先 Drop mmap,COW 私有脏页安全丢弃(数据已写入 .tmp)。


25. 杀毒软件瞬态锁定重试(robust_rename)

实现位置storage/file_format.rsstorage/vec_pool.rs

Windows 杀毒软件在文件关闭瞬间抢占扫描,通常几毫秒后自动释放。实现指数退避重试(1→2→4→…→50ms,最多 10 次)仅针对 ERROR_ACCESS_DENIED(5)ERROR_SHARING_VIOLATION(32),其他错误立即快速失败。非 Windows 平台编译为直接调用 std::fs::rename,零开销。


29. WAL 清空使用 truncate 而非 remove+create

实现位置storage/wal.rs:307-330

truncate(true) 将文件截断为零字节但保留 inode,不触发杀软的"新文件扫描",避免 WAL clear 期间再次产生文件锁冲突。


30. OS 页面缓存的安全收回 madvise

实现位置storage/vec_pool.rs:advise_dontneed()

// 通知 OS 立即回收刚刚写入磁盘的高维物理页,阻止污染 VFS 文件缓存
#[cfg(target_os = "linux")]
libc::madvise(ptr, len, libc::MADV_DONTNEED);

对于数十 GB 的向量基库,单纯依靠 OS 自我调节 LRU 会引发主机端周期性严重卡顿(Threshing)。引擎使用安全封装的非阻塞 FFI 建议系统,配合 Windows 的 VirtualUnlock 提供安全回收,以极低的成本维持了 60 帧 0 卡顿的主机交互体验。


附录:unsafe 使用汇总

位置 unsafe 操作 安全契约
vec_pool.rs:open() MmapOptions::map_copy() T: Pod+Zeroable;MAP_PRIVATE;len ≤ 文件实际大小
vec_pool.rs:flush_append() MmapOptions::map_copy() 同上;重映射前旧 mmap 已释放
vec_pool.rs:get() slice::from_raw_parts() 运行时对齐检查;index < mmap_count 守卫
vec_pool.rs:rebuild_merged_cache() slice::from_raw_parts() 同上
file_format.rs:load() Mmap::map() 仅读;mmap Drop 前不删文件
file_format.rs:load_bq() ptr::copy_nonoverlapping() bytemuck Pod 对齐;dst Vec 已预分配足够容量;src 长度精确边界
vector.rs:cosine_similarity_avx2() AVX2 SIMD 指令 运行时 is_x86_feature_detected! 检测通过才调用
index/bq.rs:popcount_distance() CPU 原生 popcnt 指令 运行时自动检测 CPU 支持;纯数学运算,无内存安全风险

所有 unsafe 块均附有明确的 // SAFETY: 注释。整个代码库没有 unsafe impl Send/Sync——Send + SyncArc<Mutex<T>> 自动推导,类型系统级别安全。

There aren’t any published security advisories