Ward-Software-Defined-Systems/WardSONDB
GitHub: Ward-Software-Defined-Systems/WardSONDB
基于 Rust 和 fjall LSM-tree 存储引擎构建的轻量级 JSON 文档数据库,专为 SIEM 安全事件场景提供高速摄取、索引查询和聚合分析的一站式解决方案。
Stars: 2 | Forks: 0
# WardSONDB
一个用 Rust 构建的轻量级、高性能 JSON 文档数据库。专为 SIEM 和安全事件工作负载而设计——在单个二进制文件中提供快速的数据摄取、索引查询、聚合管道和自动数据保留。
## 主要特性
- **单一二进制文件,零依赖** —— 无需 JVM,无需集群设置,无需外部服务
- **高吞吐量数据摄取** —— 76,000+ 次单次插入/秒,278,000+ 文档/秒的批量插入
- **辅助索引与复合索引** —— 在数百万文档中实现亚毫秒级的索引查找
- **Bitmap 扫描加速器** *(Alpha)* —— 对分类字段进行亚毫秒级的聚合和过滤计数,无需读取文档
- **复合范围扫描** *(Alpha)* —— 在复合索引上支持相等前缀 + 范围后缀,用于快速的时间窗口查询
- **聚合管道** —— `$match`、`$group`、`$sort`、`$limit`、`$skip`,并支持索引加速执行
- **纯索引查询路径** —— 计数、去重和聚合操作完全不接触文档
- **TTL / 自动过期** —— 每个集合的保留策略,并带有后台清理
- **TLS 支持** —— 自动生成自签名证书或导入你自己的证书
- **API 密钥身份验证** —— 用于生产部署的简单基于 token 的身份验证
- **Prometheus 指标** —— `/_metrics` 端点用于监控集成
- **自定义文档 ID** —— 可选择在插入时提供你自己的 `_id`,或者让 WardSONDB 自动生成 UUIDv7
- **乐观并发** —— 基于更新时 `_rev` 的冲突检测
## 性能
在 Mac Studio (M4 Max, 128GB RAM, 1.8TB SSD) 上对 **345 万条 SIEM 事件** 的基准测试结果:
| 查询类型 | 耗时 | 扫描文档数 | 策略 |
|-----------|------|-------------|----------|
| Bitmap 聚合:按 event_type 计数 | **0.096ms** | 0 | bitmap_aggregate |
| Bitmap 计数:未索引字段 (severity=6) | **0.17ms** | 0 | bitmap |
| Bitmap NOT:event_type \u2260 firewall | **0.12ms** | 0 | bitmap |
| 复合范围:type + time \u2265 6h (851K 匹配) | **137ms** | 0 | compound_range |
| 复合范围:action + time \u2265 6h (32K 匹配) | **5.5ms** | 0 | compound_range |
| 复合范围:type + time \u2265 1h (0 匹配) | **0.042ms** | 0 | compound_range |
| 复合 EQ:type + action (2.9M 匹配) | **485ms** | 0 | compound_eq |
| 索引相等 + 排序 + limit 50 | **9.5ms** | 50 | index_sorted |
| 索引计数 (3M 匹配) | **432ms** | 0 | index_eq |
| 去重值(已索引字段) | **8ms** | 0 | index_eq |
| 通过 ID 获取 | **<1ms** | \u2014 | primary |
| 单文档插入吞吐量 | **76,000+/sec** | \u2014 | \u2014 |
| 批量插入吞吐量 | **278,000+ docs/sec** | \u2014 | \u2014 |
所有数据均基于 Mac Studio M4 Max 上测量的 345 万条生产级 SIEM 事件 (防火墙、威胁、DHCP、DNS、WiFi)。运行 `cargo bench` 获取可复现的合成基准测试。
## 快速入门
### 构建
```
cargo build --release
```
### 运行
```
# 基础 — 端口 8080 上的 HTTP
./target/release/wardsondb
# 使用 TLS(自动生成的自签名证书)
./target/release/wardsondb --tls
# 生产环境 — TLS、自定义端口、API key auth、bitmap 加速
ulimit -n 65536
./target/release/wardsondb --tls --port 443 --data-dir /var/lib/wardsondb --api-key "your-secret-key" \
--cache-size-mb 512 --write-buffer-mb 512 --flush-workers 4 --compaction-workers 4 \
--bitmap-fields "event_type,severity,status"
```
### Linux:使用 jemalloc 以避免 RSS 膨胀
在 Linux 上,glibc 的默认分配器 (ptmalloc2) 会在每个线程的 arena 中保留已释放的内存。在 Tokio 的多线程运行时中,即使应用程序没有发生内存泄漏,RSS 也会持续攀升——这是因为分配器在囤积碎片化的内存。这种情况在 macOS 上不会发生,因为 libmalloc 会积极地回收释放的内存。
如果你要在 Linux 上进行部署,请将 jemalloc 添加到 `Cargo.toml`:
```
[dependencies]
tikv-jemallocator = "0.6"
```
并在 `main.rs` 中:
```
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
```
### 创建集合并插入数据
```
# 创建 collection
curl -X POST http://localhost:8080/_collections \
-H "Content-Type: application/json" \
-d '{"name": "events"}'
# 插入 document(自动生成的 UUIDv7 ID)
curl -X POST http://localhost:8080/events/docs \
-H "Content-Type: application/json" \
-d '{
"event_type": "firewall",
"network": {"src_ip": "10.0.0.1", "action": "block"},
"severity": "high"
}'
# 使用自定义 ID 插入
curl -X POST http://localhost:8080/events/docs \
-H "Content-Type: application/json" \
-d '{
"_id": "evt-firewall-2026-03-25-001",
"event_type": "firewall",
"network": {"src_ip": "192.168.1.100", "action": "block"}
}'
# Bulk insert
curl -X POST http://localhost:8080/events/docs/_bulk \
-H "Content-Type: application/json" \
-d '{"documents": [{"event_type": "dns", "query": "example.com"}, {"event_type": "dhcp", "mac": "AA:BB:CC:DD:EE:FF"}]}'
```
### 创建索引
```
# Single-field index
curl -X POST http://localhost:8080/events/indexes \
-H "Content-Type: application/json" \
-d '{"name": "idx_event_type", "field": "event_type"}'
# Compound index(用于 filter + sort 查询)
curl -X POST http://localhost:8080/events/indexes \
-H "Content-Type: application/json" \
-d '{"name": "idx_type_time", "fields": ["event_type", "received_at"]}'
```
### 查询
```
# Filter + sort + limit
curl -X POST http://localhost:8080/events/query \
-H "Content-Type: application/json" \
-d '{
"filter": {"event_type": "firewall"},
"sort": [{"received_at": "desc"}],
"limit": 50
}'
# 统计匹配的 document
curl -X POST http://localhost:8080/events/query \
-H "Content-Type: application/json" \
-d '{"filter": {"event_type": "firewall"}, "count_only": true}'
# Distinct 值
curl -X POST http://localhost:8080/events/distinct \
-H "Content-Type: application/json" \
-d '{"field": "network.src_ip", "limit": 100}'
```
### 聚合
```
# Top event 类型
curl -X POST http://localhost:8080/events/aggregate \
-H "Content-Type: application/json" \
-d '{
"pipeline": [
{"$group": {"_id": "event_type", "count": {"$count": {}}}},
{"$sort": {"count": "desc"}},
{"$limit": 10}
]
}'
# 带有时间过滤的 Top blocked IP
curl -X POST http://localhost:8080/events/aggregate \
-H "Content-Type: application/json" \
-d '{
"pipeline": [
{"$match": {"network.action": "block", "received_at": {"$gte": "2026-03-01T00:00:00Z"}}},
{"$group": {"_id": "network.src_ip", "count": {"$count": {}}, "ports": {"$collect": "network.dst_port"}}},
{"$sort": {"count": "desc"}},
{"$limit": 10}
]
}'
```
### 数据保留
```
# 对 event 设置 30 天数据保留期
curl -X PUT http://localhost:8080/events/ttl \
-H "Content-Type: application/json" \
-d '{"retention_days": 30, "field": "_created_at"}'
```
## CLI 选项
| 标志 | 默认值 | 描述 |
|------|---------|-------------|
| `--port` | `8080` | 监听端口 |
| `--data-dir` | `./data` | 数据目录 |
| `--tls` | `false` | 启用 TLS |
| `--tls-cert` | | 自定义 TLS 证书路径 |
| `--tls-key` | | 自定义 TLS 密钥路径 |
| `--api-key` | | API 密钥(可重复使用以指定多个密钥) |
| `--api-key-file` | | 包含 API 密钥的文件(每行一个) |
| `--ttl-interval` | `60` | TTL 清理间隔(以秒为单位) |
| `--metrics-public` | `false` | 允许未经身份验证访问 `/_metrics` |
| `--log-level` | `info` | 日志级别 (trace/debug/info/warn/error) |
| `--log-file` | `wardsondb.log` | 日志文件路径 |
| `--bitmap-fields` | | 逗号分隔的字段列表,用于 bitmap 扫描加速 *(Alpha)* |
| `--bitmap-memory-mb` | `0` | bitmap 内存预算,单位为 MiB (0 = 自动: min(4096, 系统内存的 10%)) |
| `--verbose` | `false` | 在终端显示每个请求的日志 |
| `--cache-size-mb` | `64` | Block + blob 缓存大小,单位为 MiB (在所有分区之间共享) |
| `--write-buffer-mb` | `64` | 最大写入缓冲区大小,单位为 MiB (在所有分区之间计算总和) |
| `--memtable-mb` | `8` | 每个分区的最大 memtable 大小,单位为 MiB (超出时触发 flush) |
| `--flush-workers` | `2` | 后台 flush 工作线程数 |
| `--compaction-workers` | `2` | 后台 compaction 工作线程数 |
## API 概述
完整的 API 文档:[API.md](API.md)
### 系统
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| GET | `/` | 服务器信息 |
| GET | `/_health` | 健康检查 |
| GET | `/_stats` | 服务器统计信息 |
| GET | `/_metrics` | Prometheus 指标 |
| GET | `/_collections` | 列出集合 |
| POST | `/_collections` | 创建集合 |
### 文档
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| POST | `/{collection}/docs` | 插入文档 (可选自定义 `_id`) |
| POST | `/{collection}/docs/_bulk` | 批量插入 (每个文档可选自定义 `_id`) |
| GET | `/{collection}/docs/{id}` | 通过 ID 获取 |
| PUT | `/{collection}/docs/{id}` | 替换文档 |
| PATCH | `/{collection}/docs/{id}` | 部分更新 (JSON Merge Patch) |
| DELETE | `/{collection}/docs/{id}` | 删除文档 |
### 查询与聚合
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| POST | `/{collection}/query` | 使用过滤器 DSL 查询 |
| POST | `/{collection}/aggregate` | 聚合管道 |
| POST | `/{collection}/distinct` | 返回去重后的字段值 |
| POST | `/{collection}/docs/_delete_by_query` | 按过滤器批量删除 |
| POST | `/{collection}/docs/_update_by_query` | 按过滤器批量更新 |
### 索引与存储
| 方法 | 路径 | 描述 |
|--------|------|-------------|
| GET | `/{collection}/indexes` | 列出索引 |
| POST | `/{collection}/indexes` | 创建索引 |
| DELETE | `/{collection}/indexes/{name}` | 删除索引 |
| GET | `/{collection}/storage` | 存储信息 |
| PUT | `/{collection}/ttl` | 设置保留策略 |
| GET | `/{collection}/ttl` | 获取保留策略 |
| DELETE | `/{collection}/ttl` | 移除保留策略 |
## Bitmap 扫描加速器 *(Alpha)*
bitmap 扫描加速器消除了对低基数分类字段(例如 `event_type`、`severity`、`network.action`)查询时的全集合扫描。它维护内存中的 bitmap,将字段值映射到文档位置,从而能够在任何规模下实现亚毫秒级的过滤计数和聚合——而完全不需要读取任何文档。
### 启用
```
./target/release/wardsondb --tls \
--bitmap-fields "event_type,network.action,severity,network.protocol,source_format"
```
### 加速范围
| 操作 | 无 Bitmap | 有 Bitmap |
|-----------|---------------|-------------|
| 按 event_type 计数 (345 万文档) | 5-15 秒 | **0.096ms** |
| 统计 severity = 6 的数量 (无索引) | 5-15 秒 | **0.17ms** |
| 统计 event_type != firewall 的数量 | 5-15 秒 | **0.12ms** |
| 聚合:按 event_type 分组 | ~250ms (索引) | **0.096ms** |
### 工作原理
- 在带有 `--bitmap-fields` 选项启动时,WardSONDB 会以 **1 万个文档为一批** 从存储中构建位置映射(文档 ID 到顺序位置)和逐字段的 bitmap(无论集合大小,峰值内存保持恒定)
- 新的插入/更新在提交后会自动维护 bitmap
- 当所有过滤字段都被 bitmap 覆盖且查询仅为计数或聚合时,查询计划器会使用 bitmap
- Bitmap 的 AND/OR/NOT 操作完全在内存中运行,文档读取次数为零
- Bitmap 会在重启时从存储中重建——fjall 始终是最终的数据源
- 删除并重新创建集合会自动重新激活加速器——无需重启
- **内存预算** (默认:自动调整至 min(4GB, 系统内存的 10%)) 防止 OOM——当超出预算时,新插入将跳过 bitmap 跟踪,查询对未覆盖的文档将回退到全表扫描
- **自动压缩 (compaction)**:当 TTL 删除在位置映射中造成超过 25% 的空隙时,后台重建将回收内存
### 内存使用与治理
在拥有 5 个 bitmap 字段的 345 万文档时:**总计约 6.4 MB** (位置映射 + bitmap)。内存随文档数量和每个字段不同值的数量线性增长。
bitmap 加速器强制执行可配置的内存上限,以防止在规模化增长时出现失控:
| 标志 | 默认值 | 描述 |
|------|---------|-------------|
| `--bitmap-memory-mb` | `0` (自动) | 内存预算,单位为 MiB。`0` = 自动:min(4096, 系统内存的 10%) |
当超出预算时,加速器会停止在 bitmap 列中跟踪新文档,但仍继续为已索引的文档提供查询服务。对未跟踪文档的查询将回退到常规索引或全表扫描路径。`/_system` 端点会报告 `scan_accelerator.over_budget` 和 `scan_accelerator.memory_bytes` 以供监控。
### 更改 Bitmap 字段
要添加、删除或更改 bitmap 字段,请更新 `--bitmap-fields` 标志并重启 WardSONDB。所有的 bitmap 都会在启动时从存储中重建——没有增量迁移过程。重建时间随文档数量成比例增加(根据硬件条件,预计每百万文档约需 30-60 秒)。
**在重启之间更改 bitmap 字段时没有任何警告。** 请通过检查 `GET /_stats` 来验证 bitmap 是否正确加载——`scan_accelerator.bitmap_columns` 数组应列出所有预期字段及其基数和内存使用情况。
### 局限性
- **Alpha 功能** —— API 和行为可能会发生变化
- 仅对低基数字段有效(少于约 100 个不同的值)
- 自动检测仅在采样窗口(默认为 10,000 次插入)内对新插入的字段基数进行性能分析。重启时从存储加载的现有文档不会被分析如果你在拥有数百万现有文档且没有 `--bitmap-fields` 标志的情况下重启,自动检测将不会激活,直到插入 10,000 个新文档。对于现有数据集,请务必显式指定 `--bitmap-fields`
- Bitmap 扫描用于仅计数的查询和聚合;返回文档的查询可能仍会使用索引路径
### 查询计划器优先级
计划器按以下顺序选择最佳策略:
0. **BitmapScan** (count_only) —— 当 `count_only: true` 且所有过滤字段都有 bitmap 列时,bitmap 优于索引(快约 2500 倍)
1. **IndexSorted** —— 复合索引覆盖过滤 + 排序,达到 limit 时提前终止
2. **CompoundEq** —— 具有多个相等字段的复合索引
3. **CompoundRange** —— 复合索引:相等前缀 + 范围后缀 *(Alpha)*
4. **IndexEq / IndexRange / IndexIn** —— 单字段索引扫描
5. **BitmapScan** —— 用于分类字段过滤的 bitmap 加速器(返回文档的查询)
6. **FullScan** —— 最后的手段,扫描所有文档
## 内存调优
WardSONDB 使用保守的默认值 (64 MiB 缓存,64 MiB 写入缓冲区,2 个 flush/compaction 工作线程),适合资源受限的环境。对于处理数百万文档的大内存系统,请增加这些值以避免写入缓冲区饱和和 compaction 瓶颈。
```
# 推荐用于具有 32GB+ RAM 和繁重写入工作负载的系统
./target/release/wardsondb --tls \
--cache-size-mb 512 \
--write-buffer-mb 512 \
--memtable-mb 32 \
--flush-workers 4 \
--compaction-workers 4
```
| 参数 | 默认值 | 大内存建议 | 用途 |
|-----------|---------|---------------------------|---------|
| `--cache-size-mb` | 64 | 512+ | 读取缓存 —— 较大的值可减少重复查询时的磁盘读取 |
| `--write-buffer-mb` | 64 | 512+ | 写入缓冲区 —— 防止在持续摄取时发生写入饱和 |
| `--memtable-mb` | 8 | 32 | 每个分区的 memtable —— 较大的值可降低 flush 频率 |
| `--flush-workers` | 2 | 4 | 并行 flush 线程 —— 根据可用 CPU 核心进行扩展 |
| `--compaction-workers` | 2 | 4 | 并行 compaction 线程 —— 根据可用 CPU 核心进行扩展 |
**配置过小的症状:** 控制台被 `write halt because of write buffer saturation` 刷屏,查询延迟飙升,`/_health` 报告 `write_pressure: "high"`。
## 文件描述符限制
WardSONDB 要求生产环境中的 `ulimit -n` 至少为 **4096**。如果限制过低,服务器在启动时会发出警告并尝试自动提升该限制。
```
# 建议在启动前执行
ulimit -n 65536
```
## 安全性
WardSONDB 专为受信任的网络环境设计。以下是部署时的安全注意事项。
| 关注点 | 严重性 | 状态 |
|---|---|---|
| 正则表达式拒绝服务 | **严重** | **已缓解** —— 使用 Rust 的 `regex` crate,保证线性时间匹配。模式长度上限为 1024 个字符。 |
| API 密钥计时攻击 | **严重** | **已缓解** —— API 密钥比较使用恒定时间相等性检查 (`subtle` crate) 以防止基于时间的枚举。 |
| 无界查询结果 | **中等** | **已缓解** —— 查询 `limit` 上限为 10,000。批量插入上限为 10,000 个文档。管道阶段上限为 100 个。 |
| 查询资源耗尽 | **中等** | **已缓解** —— 查询在 30 秒后超时 (可通过 `--query-timeout` 配置)。过滤器嵌套深度上限为 20,分支上限为 1000。 |
| 无内置身份验证 | **中等** | **可配置** —— API 密钥认证通过 `--api-key` 或 `--api-key-file` 启用。未配置时,所有端点对外开放。如果暴露在局域网之外,请部署在带有认证的反向代理之后。 |
| TLS 证书信任 | **信息** | 自动生成的自签名证书会触发浏览器警告。在生产环境中,请通过 `--tls-cert` 和 `--tls-key` 提供你自己的证书。 |
**报告漏洞:** 请打开带有 `security` 标签的 [GitHub Issue](https://github.com/Ward-Software-Defined-Systems/WardSONDB/issues) 或发送电子邮件至 security@wsds.io。
## 已知问题
| 问题 | 状态 | 描述 |
|---|---|---|
| 大规模下的高 RSS 内存使用 | **预期行为** | 在文档超过 200 万时,操作系统报告的 RSS 可能会增长到消耗系统内存的 80-90%。这是所有基于 mmap 的存储引擎 (RocksDB, LMDB, LevelDB) 的标准行为,其中内存映射的 SST 文件被操作系统内核计入 RSS。实际的堆内存使用受配置限制的约束 (参见内存调优部分)。膨胀的 RSS 数字反映的是操作系统页面缓存,而不是应用程序的内存消耗——macOS 在将 mmap 区域报告为 RSS 时尤为积极。macOS 可能会在内存压力下终止进程 (Jetsam)。解决方法:确保系统有足够的内存,并避免在同一主机上与 WardSONDB 一起运行内存密集型应用程序。 |
| 200 万以上文档时全表扫描查询缓慢 | **已缓解** | 对未索引字段的查询需要进行全集合扫描。在 200 万以上文档时,未索引查询可能需要 5-15 秒。**缓解措施:** 对分类字段启用 bitmap 扫描加速器 (`--bitmap-fields`)——可将未索引计数查询从数秒减少到亚毫秒级。对于剩余的未索引字段,请创建辅助索引。 |
| 带有多个索引的批量摄取期间的 Compaction 风暴 | **已知限制** | 在繁重的批量摄取之前或期间创建多个索引可能会触发 compaction 风暴——fjall 的后台 compaction 工作线程会占满所有 CPU 核心,导致服务器对查询和健康检查无响应。这是因为每个插入的文档都会写入每个索引,从而产生巨大的写放大。服务器仍然存活,但在 compaction 完成之前无法提供请求。缓解措施:在初始批量摄取完成*之后*创建索引,一次创建一个,并在每个索引之间暂停,同时监控 `GET /_health` 中的 `write_pressure` 字段——当它报告 `"high"` 时推迟查询。 |
## 路线图
- [x] 辅助索引与复合索引
- [x] 聚合管道 ($match, $group, $sort, $limit, $skip)
- [x] TTL / 自动过期
- [x] API 密钥身份验证
- [x] Prometheus 指标
- [x] `$collect` 累加器
- [x] `$distinct` 端点
- [x] 存储信息端点
- [x] 查询性能优化 (提前终止,纯索引聚合)
- [x] 安全加固 (正则表达式,计时攻击,资源限制,超时)
- [x] Bitmap 扫描加速器 —— 亚毫秒级分类字段查询 *(Alpha)*
- [x] 复合范围扫描 —— 复合索引上的相等前缀 + 范围后缀 *(Alpha)*
- [x] Bitmap 计划器优先级 —— 在启用 bitmap 的字段上对 count_only 查询优先使用 bitmap 而非辅助索引
- [x] Bitmap 加速聚合 —— 聚合执行器直接读取 bitmap 计数(零文档读取)
- [ ] RSS 内存优化 —— 调查 fjall mmap 在大规模下的行为
- [ ] 流式传输/游标 —— 超出 limit/offset 的大型结果集
- [ ] 查询解释 (Query explain) —— 显示扫描策略和索引使用情况
- [ ] Schema 验证 —— 集合上的可选 JSON Schema
- [ ] 在 Linux 和 Windows 上的性能分析 —— 当前基准测试仅适用于 macOS (Apple Silicon)
## 技术栈
- [Rust](https://www.rust-lang.org/) —— 系统编程语言
- [Axum](https://github.com/tokio-rs/axum) —— 异步 Web 框架
- [fjall](https://github.com/fjall-rs/fjall) —— LSM-tree 存储引擎
- [tokio](https://tokio.rs/) —— 异步运行时
## 许可证
Apache License 2.0 —— 详见 [LICENSE](LICENSE)。
## 贡献
请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解相关指南。
由 [Ward Software Defined Systems](https://wsds.io) 构建
标签:JSON数据库, PB级数据处理, Rust, TTL自动清理, 位图扫描, 单文件部署, 可视化界面, 安全事件, 安全运维, 嵌入式数据库, 并发控制, 快速写入, 数据保留, 数据库, 文档数据库, 日志存储, 时间序列, 索引查询, 网络流量审计, 聚合管道, 通知系统