3 亿条 / 30 天规模测试
测试日期 2026-10-12 · 单机 6 核 11 GB 内存 · Osmanthe 网关 + VictoriaLogs v1.53.0 · 合成数据 · 以后每次规模测试都追加到这一页
一句话结论:3 亿条在这台小机器上写得进、存得下,每 10 分钟左右记录一次的累计平均速率基本不变。计数、按低基数字段统计、按字段精确查找都在毫秒到 2 秒内;但有 3 类查询在 30 天全量上不行:按高基数字段(带随机参数的 URL)分组会超内存失败,不写字段名的全文关键字搜索会超过 30 秒上限,去重计数 dc() 要 17–18 秒。这些是上 10 亿条之前要先解决的。
数据来源说明(2026-10-12 更正):
- 写入阶段(第 2、3 节)的原始文件已丢失。测试脚本当时把结果写在 /tmp,会话结束后 /tmp 被清空,
ingest_summary.json和每 15 秒一次的ingest_samples.jsonl都没有了。第 2、3 节的数字是从测试运行时打印在会话记录里的输出抄回来的:速率表来自每分钟一行的进度输出(只保留了 4 行),资源的平均值和峰值、磁盘前后对比来自脚本结束时打印的汇总 JSON。这些数字没有原始文件可以复核。 - 之前写的“每 15 秒采样一次,共约 165 个样本”是按时长推算的,样本本身已不存在;“全程不变慢”只有 4 个累计平均值支撑,看不出短时波动,已改为如实描述。
- 查询阶段(第 4 节)在 2026-10-12 重新跑过,原始结果保存在工作目录的
scale-results/300m-queries-20261012/(不提交进仓库,但不会被清空),可以直接复核。 - 以后的测试,脚本默认把结果写进
scale-results/,样本边测边写盘;写入有任何失败(含 HTTP 4xx、条数不足)脚本都会以失败退出。要补上写入阶段的逐时曲线,需要清掉 bench 租户约 27 GB 数据后重跑写入(约 45 分钟),等你同意再做。
1. 测试环境和数据
| 项目 | 值 |
|---|---|
| 机器 | 6 核 CPU、11 GB 内存、3 GB swap、单块系统盘;测试开始时空闲磁盘 60.8 GB |
| 部署 | docker compose(项目名 coding):网关 coding-gateway-1 + VictoriaLogs v1.53.0,均不设 CPU/内存限制;VictoriaLogs 保留期临时改为 31 天(原来 7 天) |
| 数据生成 | tools/scale_test.py,在同一台机器上运行(与被测服务抢 CPU),5 个进程,每个 HTTP 请求 2000 条,走网关的 Splunk HEC 接口 /services/collector/event |
| 租户 | 专用租户 bench(account 4),不和 demo 数据混在一起 |
| 时间范围 | 2026-09-12 16:07 到 2026-10-12 16:07 UTC,30 天均匀分布,每个进程按时间从旧到新写 |
| 数据内容 | 80% nginx 访问日志(index=web,40 台主机,50 万用户,16 种状态码,30% 的 URL 带随机查询参数,每条一个随机 128 位 trace_id);20% 应用日志(index=app,6 个服务,5 个日志级别)。平均每条约 410 字节 JSON |
| 条数 | 正式写入 2.95 亿条,加上之前 3 次探测写入共 500 万条,bench 租户共 299,999,848 条(差的 152 条是探测数据中刚好落在 30 天外的) |
2. 写入:速率和资源消耗
速率
| 阶段 | 已写入 | 平均速率 | 网络流量 |
|---|---|---|---|
| 9 分钟 | 6,492 万 | 119,039 条/秒 | 48.8 MB/s |
| 20 分钟 | 1.42 亿 | 117,650 条/秒 | 48.2 MB/s |
| 30 分钟 | 2.14 亿 | 118,428 条/秒 | 48.5 MB/s |
| 40 分钟 | 2.87 亿 | 119,314 条/秒 | 48.9 MB/s |
| 全程 41.3 分钟 | 2.95 亿 | 119,155 条/秒 | 48.8 MB/s(共 120.9 GB) |
以上是累计平均(从开始到该时刻的总条数 ÷ 用时),来自运行时的进度输出。4 个累计平均值基本一致,说明没有明显的整体变慢;但累计平均会抹平短时波动,原始的逐时样本已丢失,无法确认每个时段的速率。汇总输出里失败 0 条、VictoriaLogs 丢弃 0 条(注意:当时的脚本遇到 HTTP 4xx 不会计入失败,这个缺陷已修;2026-10-12 复核:bench 租户全部时间范围内 index=web 240,003,566 条、index=app 59,996,434 条,合计正好 3.00 亿条,等于写入目标,说明没有漏写也没有重复;另有 1 条不带 index 的事件,来源不明,应是手工测试写入的)。网关只在 VictoriaLogs 确认写入后才返回成功,所以这是真正落盘的速率。
探测阶段:3 个进程 98,830 条/秒;5 或 6 个进程约 132,000 条/秒(短时间,200 万条)。再加进程不再变快,因为整机 CPU 已经用满。
资源消耗(运行时每 15 秒采样一次;样本文件已丢失,下表的平均值和峰值来自结束时打印的汇总)
| 指标 | 平均 | 峰值 | 说明 |
|---|---|---|---|
| 整机 CPU | 5.3 核 | 5.6 核 | 6 核基本用满,是瓶颈 |
| VictoriaLogs CPU | 3.4 核 | 3.95 核 | 压缩、建索引、合并 |
| 网关 CPU | 0.9 核 | 1.2 核 | 解析 HEC JSON、转发 |
| 数据生成器 CPU | 0.8 核 | — | 共 2056 CPU 秒;正式环境不存在这部分 |
| VictoriaLogs 内存(进程本身) | 1.66 GB | 1.94 GB | 不含页缓存 |
| VictoriaLogs 内存(含页缓存) | 5.8 GB | 7.6 GB | 页缓存可被系统回收 |
| 网关内存 | 95 MB | 109 MB | 不随数据量增长 |
| 磁盘写 | 28.4 MB/s | 117 MB/s | 峰值来自后台合并 |
| 磁盘读 | 1.6 MB/s | 22.9 MB/s | 合并时读回 |
换算:每 1 核网关 CPU 约处理 13 万条/秒,每 1 核 VictoriaLogs CPU 约 3.5 万条/秒。这台机器如果不跑数据生成器,估计能到 13–14 万条/秒;要更快就要加核或把网关和存储分开部署。
3. 存储:磁盘和压缩
| 指标 | 写入前 | 写入后 | 增加 |
|---|---|---|---|
| VictoriaLogs 总条数(所有租户) | 1,161 万 | 3.066 亿 | 2.95 亿 |
| 数据卷实际占用(du) | 0.57 GB | 27.4 GB | 26.8 GB |
| 压缩后数据 | 0.55 GB | 27.4 GB | 26.8 GB |
| 未压缩数据(VictoriaLogs 统计) | 3.3 GB | 128.2 GB | 124.9 GB |
| 空闲磁盘 | 60.8 GB | 33.8 GB | −27.0 GB |
- 每条约 91 字节落盘;相对网络上发送的 HEC JSON(120.9 GB)压缩 4.5 倍,相对 VictoriaLogs 内部未压缩大小压缩 4.65 倍。
- 压缩比偏低是因为合成数据里有大量随机值(每条一个 128 位 trace_id、随机 IP、随机 URL 参数),这些几乎压不动。真实日志重复内容多,通常会更好。
- VictoriaLogs 的倒排索引(indexdb)只有 100 KB 左右,因为它只给日志流(host、sourcetype、index)建索引,不给每个词建索引。这让它省磁盘、写得快,代价见第 5 节。
4. 查询:19 条 SPL 的冷、热耗时
全部通过网关用 SPL 查询,时间范围以数据最后一条(10-12 16:07)为终点。“冷”是重启 VictoriaLogs 后第一次运行(清空了它自己的缓存,但系统页缓存还在,没有 root 权限清不掉;数据 27 GB 大于内存,所以大部分数据仍要从磁盘读);“热”是不重启再跑一轮、每条连跑 3 次的中位数。单位毫秒,含网关翻译和 HTTP 往返。数字是 2026-10-12 重跑的结果(原始文件见 scale-results/300m-queries-20261012/ 里的 query_cold.json、query_hot.json、query_samples.jsonl;最后两条不在脚本清单里的手工查询见 extra.json),和第一次跑的结果相差在 10% 以内。带 * 的是不重启时的第一次,不是冷启动。
| 查询 | SPL | 冷 ms | 热 ms | 结果 |
|---|---|---|---|---|
| stats count,1 小时 | index=web | stats count | 16 | 11 | 33.3 万 |
| stats count,24 小时 | index=web | stats count | 17 | 8 | 800 万 |
| stats count,7 天 | index=web | stats count | 21 | 13 | 5600 万 |
| stats count,30 天全部 | * | stats count | 20 | 10 | 3.0 亿 |
| tstats 按 index 计数,30 天 | | tstats count where index=* by index | 37 | 24 | 2 行 |
| 最新 100 条 500 错误,30 天 | index=web status=500 | head 100 | 447 | 203 | 100 条 |
| stats by status,24 小时 | index=web | stats count by status | 105 | 79 | 16 行 |
| stats by status,30 天 | index=web | stats count by status | 1,673 | 1,605 | 16 行 |
| timechart 1 小时粒度,7 天 | index=web status>=500 | timechart span=1h count | 429 | 370 | 168 点 |
| timechart 按天 by status,30 天 | index=web | timechart span=1d count by status | 3,290 | 3,242 | 30×16 |
| ERROR 按服务统计,30 天 | index=app level=ERROR | stats count by service | 421 | 282 | 6 行 |
| where + eval + stats,24 小时 | index=web | where bytes > 80000 | eval kb=round(bytes/1024,1) | stats max(kb) as max_kb by host | 96 | 65 | 40 行 |
| dc(user) 去重计数,24 小时 | index=web | stats dc(user) as users | 990 | 896 | 50 万 |
| dc(user) 去重计数,30 天 | index=web | stats dc(user) as users | 18,213 | 17,142 | 50 万 |
| 按字段找一个 trace_id,30 天 | index=web trace_id=50d337e9… | 489* | 68 | 2 条 |
| 全文找一个 trace_id(限定 index=web),30 天 | index=web 50d337e9… | 28,452 | 27,759 | 2 条 |
| 全文找一个 trace_id(不限 index),30 天 | 50d337e9… | >30,000 | >30,000 | 超时 |
| top uri_path,30 天 | index=web | top limit=10 uri_path | 8,172 | 7,966 | 失败 |
| avg/p95 by uri_path,7 天 | index=web | stats avg(response_time) as avg, perc95(response_time) as p95 by uri_path | 7,849 | 7,862 | 失败 |
查询期间资源
- 整机 CPU 峰值 6.0 核(满),VictoriaLogs 会用满所有核并行扫描;网关 CPU 几乎为 0、内存不到 60 MB。
- VictoriaLogs 进程内存峰值 7.1 GB,出现在按 uri_path 分组的两条失败查询;其他查询一般在 0.6 GB 以下。
- 磁盘读峰值 520 MB/s,出现在全文关键字搜索(全量扫描)。
怎么看这些数字
- 计数和 tstats 极快(10–40 ms),和数据量几乎无关:VictoriaLogs 能直接用每个数据块的元信息算出条数。这是 Splunk 用户最常看的仪表盘类查询。
- 按低基数字段统计(状态码、服务、主机)30 天在 2 秒内,7 天以内都在 0.5 秒内。
- 按字段精确找值(
trace_id=…)30 天第一次 0.5 秒、重复查 0.07 秒;同一个值不写字段名全文搜索要 28 秒左右,慢 50 倍以上。 - 冷热差别不大:大部分时间花在 CPU 解压和扫描,不在磁盘。
5. 发现的问题
| 问题 | 现象 | 原因 | 建议 |
|---|---|---|---|
| 高基数字段分组失败 | top uri_path(30 天)和 stats … by uri_path(7 天)返回 400:“需要超过 2801 MB 内存”。top uri_path 1 天 1.1 秒、7 天 11 秒能成功 | 30% 的 URL 带随机参数,30 天有约 7000 万个不同值;VictoriaLogs 单查询内存上限 = 可用内存 60% 的一部分,不会落盘 | 1) 写入时把 URL 拆成路径和参数(Splunk 用户本来就常这么做);2) top 先按块近似取前 N;3) 给出能看懂的中文报错,提示缩短时间或换字段 |
| 全文关键字搜索慢、会超时 | 限定 index=web 时 28–30 秒;不限 index 时超过 VictoriaLogs 默认 30 秒上限,网关返回 “storage unavailable” | VictoriaLogs 没有按词的倒排索引,只靠每块的 bloom 过滤,随机值多时要扫全部数据 | 1) 网关把超时翻译成“查询超时,请缩小时间范围或写字段名”;2) 文档引导用 字段=值;3) 评估给常查的 ID 类字段做专门索引(写入时建) |
| dc() 慢 | 30 天 50 万个用户去重 17–18 秒 | 精确去重,要扫全部 user 值 | 提供近似去重(类似 Splunk 的 estdc)或按天预聚合 |
| 结果条数上限 | timechart 30 天 × 16 个状态码应有 480 行,测试脚本只取了 100 行 | 测试脚本自己传了 limit=100,不是产品问题 | 无需修改,记录以免误读 |
6. 10 亿条测试估算
| 项目 | 按本次结果推算 |
|---|---|
| 磁盘 | 约 91 GB(每条 91 字节);本机只剩 33.8 GB,放不下,需要先扩盘到 150 GB 以上(留合并空间) |
| 写入时间 | 同样配置约 2.3 小时(11.9 万条/秒);数据生成器放到另一台机器可缩短到约 2 小时 |
| 计数、tstats | 预计仍在 50 ms 以内 |
| 30 天 stats by 低基数字段 | 约 5–6 秒 |
| 30 天 dc()、全文搜索 | 会超过 30 秒上限,必须先做第 5 节的改进或拆成按天查询 |
| 内存 | 写入时 VictoriaLogs 进程约 2 GB,不随数据量明显增长;高基数分组查询需要更多内存 |
建议:先做第 5 节的前两条改进(中文超时提示、URL 拆字段),扩盘后再跑 10 亿条,查询清单不变,结果追加到本页。
7. 怎么复现
# 1. 启动服务(保留期已是 31 天),起一个能读数据卷和指标的探针容器
docker compose -p coding -f deploy/docker-compose.yml up -d --build
docker run -d --name coding-probe --network coding_default \
-v coding_vlogs:/vlogs:ro docker.io/library/alpine:3.20 sleep 172800
# 2. 写入(结果放 scale-results/,已在 .gitignore 里,不进仓库;不要放 /tmp)
# 有任何失败(HTTP 4xx、重试 8 次仍失败、条数不足)就以状态 1 退出
python3 tools/scale_test.py ingest --events 295000000 --days 30 \
--workers 5 --out scale-results/300m
# 3. 查询:先冷后热;--end 填 ingest_summary.json 里 time_range 的第二个值
python3 tools/scale_test.py query --out scale-results/300m --restart --end 1791821241
python3 tools/scale_test.py query --out scale-results/300m --end 1791821241
输出:ingest_summary.json(ok 和 problems 字段表示是否全部成功;速率、前后磁盘、资源平均和峰值)、ingest_samples.jsonl(每 15 秒的样本)、query_cold.json、query_hot.json。CPU 和内存直接读容器的 cgroup,磁盘读写来自 /proc/diskstats,存储大小来自 VictoriaLogs 的 /metrics 和 du。
测试数据目前还留在 bench 租户(约 27 GB),31 天后自动过期;要提前删可以删 bench 租户的数据或整个数据卷。