跳到主要内容

文件压缩

IEDB 的自动压缩系统将小的 Parquet 文件合并成更大的、优化的文件,从而显著加快查询速度。

概述

压缩是 IEDB 的文件优化系统,它将小文件合并成大文件,从而将查询性能提高 10-50 倍。

主要特点:

  • 自动- 按计划运行(默认:每小时 05:05)
  • 安全- 锁定分区可防止并发压缩
  • 高效- 使用高性能算子引擎进行快速并行合并
  • 非阻塞式- 查询在压缩期间也能正常工作
  • 默认启用- 生产环境必备

信息 默认情况下启用压缩功能,并每小时自动运行一次。

为什么压缩很重要

小文件问题

IEDB 的高性能数据写入(每秒 947 万条记录)会生成许多小文件:

每秒 947 万条记录,每 5 秒刷新一次:
→ 每 5 秒产生 1000 万条记录
→ 每个测量值每分钟产生 12 个文件
→ 每个测量值每小时产生 720 个文件
→ 每个测量值每天产生 17,280 个文件

对查询的影响:

  • 查询速度慢——分析引擎必须打开/扫描数百个文件
  • 成本高昂——需要更多 S3/MinIO API 调用
  • 压缩效果差——小文件压缩效率较低。
  • 减少修剪——分区消除效果降低

压缩后

实际生产测试结果:

合并前:2,704 个小文件(Snappy 压缩)= 3.7 GB
合并后:3 个合并文件(ZSTD 压缩)= 724 MB

压缩率:节省 80.4% 的存储空间
文件数量:减少至原来的 1/901(从 2,704 个减少到 3 个)
合并耗时:5 秒

按测量项目细分:

  • 内存:888 个文件 → 1 个文件,1,213 MB → 239 MB(压缩率 80.3%)
  • 磁盘:906 个文件 → 1 个文件,1,237 MB → 242 MB(压缩率 80.4%)
  • CPU:910 个文件 → 1 个文件,1,246 MB → 243 MB(压缩率 80.5%)

查询性能:

  • 速度提升 10-50 倍——单文件扫描对比数百个文件扫描。
  • API 调用次数减少 99% - 成本大幅降低(2704 次 LIST 操作 → 3 次)
  • 压缩率达 80.4% - ZSTD 压缩与 Snappy 写入对比
  • 有效的剪枝——分析引擎可以跳过整个文件

工作原理

压缩流程

调度器启动(定时任务:每小时的第5分钟)

扫描存储,查找符合条件的可合并分区

对每个分区:检查分区时长(是否超过1小时?)检查文件数量(是否≥10个?)检查是否已经合并过?

获取分区锁(基于 SQLite)

将小文件下载到临时目录

使用算子引擎进行合并(并行处理、排序)

将合并后的文件上传回存储

删除旧的原始小文件

释放分区锁并清理临时文件

继续处理下一个分区

分区结构

数据按小时整理:

iedb/ # 存储桶(S3 bucket 或本地根目录)
├── default/ # 数据库名称
│ └── cpu/ # 测量值(表名)
│ └── 2025/10/08/ # 日期分区(按天)
│ ├── 14/ # 小时分区(14点 = 下午2点)
│ │ ├── file1.parquet
│ │ ├── file2.parquet
│ │ └── ...
│ ├── 15/ # 15点
│ └── 16/ # 16点

压缩将分区中的所有文件(例如,2025/10/08/14/)合并成一个优化后的文件。

配置

默认配置

默认情况下,压缩功能已启用toml

[compaction]
enabled = true
min_age_hours = 1 # 等待1小时后再合并(确保当前小时数据完整)
min_files = 10 # 仅当文件数量≥10时才进行合并
target_file_size_mb = 512 # 合并后文件的目标大小(单位:MB)
schedule = "5 * * * *" # Cron 表达式:每小时的第5分钟执行
max_concurrent_jobs = 2 # 并行执行的合并任务数(最多2个)
compression = "zstd" # 使用 zstd 压缩(比 snappy 压缩率更高)
compression_level = 3 # 压缩级别(平衡压缩率与速度)

配置选项

定时任务

[compaction]
schedule = "5 * * * *" # 每小时的第5分钟执行(默认值)
# schedule = "0 */2 * * *" # 每2小时的第0分钟执行
# schedule = "0 2 * * *" # 每天凌晨2点执行

Cron 格式: minute hour day month weekday

最低时限

[compaction]
min_age_hours = 1 # 不合并当前小时的数据(默认值)
# min_age_hours = 2 # 等待2小时后再合并(更保守)
# min_age_hours = 0 # 立即合并(激进)

警告 设置min_age_hours = 0可以在数据仍在写入时压缩当前小时,可能会创建许多压缩文件。

最小文件

[compaction]
min_files = 10 # 仅当文件数量 ≥ 10 时才进行合并(默认值)
# min_files = 50 # 仅在文件数量较多时合并(更保守)
# min_files = 5 # 更激进地合并(阈值更低)

目标文件大小

[compaction]
target_file_size_mb = 512 # 目标文件大小 512MB(默认值)
# target_file_size_mb = 1024 # 更大的文件(文件数更少,合并时间更长)
# target_file_size_mb = 256 # 更小的文件(文件数更多,合并速度更快)

并发配置

[compaction]
max_concurrent_jobs = 2 # 并行执行 2 个合并任务(默认值)
# max_concurrent_jobs = 4 # 更高并行度(消耗更多 CPU/内存)
# max_concurrent_jobs = 1 # 串行执行(资源占用更低)

压缩

[compaction]
compression = "zstd" # 最佳压缩率(默认值)
compression_level = 3 # 平衡压缩速度与压缩率(默认值)

# 可选值:
# compression = "snappy" # 速度最快,压缩率较低
# compression = "gzip" # 压缩率较好,速度较慢
# compression = "zstd" # 压缩率最佳,速度良好

禁用压缩

[compaction]
enabled = false

何时禁用:

  • 测试写入性能

  • 写入量极低(< 10 个文件/小时)

  • 调试压缩问题

警告 禁用文件压缩会导致查询速度显著降低,因为文件会不断累积。

监测

检查压缩状态

curl http://localhost:8000/api/compaction/status \
-H "Authorization: Bearer YOUR_TOKEN"

响应:

{
"enabled": true,
"running": false,
"last_run": "2025-10-08T14:05:00Z",
"next_run": "2025-10-08T15:05:00Z",
"stats": {
"total_jobs": 42,
"successful_jobs": 40,
"failed_jobs": 2,
"total_files_compacted": 12580,
"total_bytes_saved": 8589934592
}
}

获取详细统计数据

curl http://localhost:8000/api/compaction/stats \
-H "Authorization: Bearer YOUR_TOKEN"

列出符合条件的分区

curl http://localhost:8000/api/compaction/candidates \
-H "Authorization: Bearer YOUR_TOKEN"

响应:

{
"candidates": [
{
"partition": "default/cpu/2025/10/08/14",
"file_count": 150,
"total_size_mb": 7500,
"age_hours": 2.5,
"eligible": true
},
{
"partition": "default/mem/2025/10/08/14",
"file_count": 120,
"total_size_mb": 6000,
"age_hours": 2.5,
"eligible": true
}
],
"total_candidates": 2
}

手动触发

curl -X POST http://localhost:8000/api/compaction/trigger \
-H "Authorization: Bearer YOUR_TOKEN"

查看压缩进程

curl http://localhost:8000/api/compaction/jobs \
-H "Authorization: Bearer YOUR_TOKEN"

查看压缩历史

curl http://localhost:8000/api/compaction/history \
-H "Authorization: Bearer YOUR_TOKEN"

性能影响

压缩性能

**测试环境:**苹果 M3 Max(14 核,48GB 内存)

文件尺寸压实时间最终尺寸压缩
8881.2 GB2.1秒239 MB80.3%
9061.2 GB2.2秒242 MB80.4%
9101.2 GB2.3秒243 MB80.5%

总计: 2704 个文件(3.7 GB)→ 3 个文件(724 MB),耗时6.6 秒

查询性能

压缩前:

SELECT * FROM default.cpu WHERE time > NOW() - INTERVAL 1 HOUR;
-- 5.2 seconds (scan 720 files)

压缩后:

SELECT * FROM default.cpu WHERE time > NOW() - INTERVAL 1 HOUR;
-- 0.05 seconds (scan 1 file) - 104x faster!

节省存储空间

Original files (Snappy): 3.7 GB
Compacted files (ZSTD): 724 MB
Space saved: 80.4%

最佳实践

1. 自动运行压缩

默认的定时计划(每小时一次)适用于大多数使用场景:

[compaction]
enabled = true
schedule = "5 * * * *"

2. 监测压缩进程

设置以下提醒:

  • 压缩进程失败
  • 包含超过 1000 个文件的分区
  • 耗时超过 10 分钟

3. 根据写入量进行调整

高容量(>1000万条记录/秒):

[compaction]
min_files = 100 # 等待更多文件后再合并(提高触发门槛)
max_concurrent_jobs = 4 # 更高并行度(最多同时运行4个合并任务)

低容量(<100K 条记录/秒):

[compaction]
min_files = 5 # 文件数量较少时即触发合并(阈值更低)
schedule = "0 */6 * * *" # 每6小时执行一次

4. 使用合适的目标文件大小

[compaction]
target_file_size_mb = 512 # 合适的默认值
# target_file_size_mb = 1024 # 适用于非常大的数据集
# target_file_size_mb = 256 # 用于更快的合并

5. 从源头减少文件生成

**最佳实践:**增加缓冲区大小以减少生成的文件数量:

[ingest]
max_buffer_size = 200000 # 从 50,000 上调(文件数量减少至原来的 1/4)
max_buffer_age_ms = 10000 # 从 5000 上调(文件数量减少至原来的 1/2)

影响:

  • 文件生成量:2000个/小时 → 250个/小时(减少8倍)
  • 压缩时间:150秒 → 20秒(速度提升7倍)
  • 内存使用量:每个工作线程 +300MB

这是最有效的优化方法——文件越少,压缩速度越快,查询速度也越快。

故障排除

压缩未运行

查看状态:

curl http://localhost:8000/api/compaction/status

验证配置:

# Check if enabled
grep "enabled" iedb.toml

# Check schedule
grep "schedule" iedb.toml

查看日志:

# Docker
docker logs iedb | grep compaction

# Native
sudo journalctl -u iedb | grep compaction

压缩时间过长

**问题:**运行时间超过 30 分钟

解决方案:

  1. 减小目标文件大小:
[compaction]
target_file_size_mb = 256 # 较小块
  1. 提高并发:
[compaction]
max_concurrent_jobs = 4
  1. 从源头减少文件:
[ingest]
max_buffer_size = 200000

压缩过程中磁盘空间不足

**症状:**磁盘空间不足导致压缩失败

解决方案:

  1. 在较大的磁盘上使用临时目录:
export TMPDIR=/mnt/large-disk/tmp
  1. 减少并发量:
[compaction]
max_concurrent_jobs = 1
  1. 手动清理旧的压缩文件:
# 删除已合并的小文件
find ./data -name "*.parquet" -size -10M -delete

锁无法释放

**问题:**分区卡在“锁定”状态

检查锁:

# 查看锁
sqlite3 ./data/iedb.db "SELECT * FROM compaction_locks;"

清除陈旧锁芯:

# 超过两小时自动处理或者手动处理
sqlite3 ./data/iedb.db "DELETE FROM compaction_locks WHERE expires_at < datetime('now');"

API 参考

GET /api/v1/compaction/status

获取当前压缩状态。

回复:

{
"enabled": true,
"running": false,
"last_run": "2025-10-08T14:05:00Z",
"next_run": "2025-10-08T15:05:00Z"
}

GET /api/v1/compaction/stats

获取详细的压缩统计数据。

GET /api/v1/compaction/candidates

列出符合压缩条件的分区。

POST /api/v1/compaction/trigger

手动触发。

响应:

{
"message": "Compaction triggered",
"job_id": "comp_1696775400"
}

GET /api/v1/compaction/jobs

查看正在进行的压缩作业。

GET /api/v1/compaction/history

查看压缩进程记录。

概括

压缩对于生产环境部署至关重要:

好处:

  • 查询速度提升 10-50 倍
  • 节省 80% 的存储空间
  • API 调用次数减少 99%。
  • 自动且安全

默认配置适用于大多数情况:

[compaction]
enabled = true
schedule = "5 * * * *"
min_age_hours = 1
min_files = 10

定期监测:

  • 查看/api/v1/compaction/status
  • 失败进程警报
  • 注意那些文件数超过 1000 个的分区。

后续步骤