一个标签就让成本变成 30 倍
目标
Loki **不会索引日志内容。**它只索引标签。正因如此,成本很低; 也正因如此,标签设计错误就会爆炸。
本实验不只用语言解释,而是让你亲手制造一次爆炸。
开始
cp -r /opt/lab/loki/* . && chmod +x *.sh
setsid nohup loki -config.file=loki.yaml > loki.log 2>&1 </dev/null &
curl -s localhost:3100/ready # "ready" 가 될 때까지 20초쯤
export LOKI_ADDR=http://localhost:3100
请使用 setsid nohup。如果只用 & 启动,切换 Shell 时进程也会一起终止。
工具
| 文件 | 作用 |
|---|---|
push.sh |
写入一行日志——第一个参数是标签 JSON |
streams.sh |
当前流数量(使用 -v 时也显示组合) |
logcli |
执行 LogQL 查询 |
LogQL 从标签选择开始
{app="web", level="error"} |= "timeout" | logfmt | status="500"
└── 색인으로 좁힌다 ──┘ └── 여기부터는 훑어 읽는다 ──────────┘
前面的花括号决定读取范围,后面的部分在该范围内扫描并过滤。 如果不缩小范围,就会读取全部内容。
步骤
- 启动并写入一行 →
01-boot.txt - 标签组合 = 流 →
02-streams.txt - 内容不进入索引 →
03-notindexed.md - 制造基数爆炸 →
04-explode.txt - 把相同信息放入内容 →
05-fix.txt - 使用解析器提取值 →
06-parser.txt - 标签判断标准 →
07-rule.md - 总结 →
08-notes.md
参考
第 4 步与第 5 步的数字差异就是本实验的全部。相同信息放置的位置不同, 流可能增加 30 个,也可能只增加 1 个。
启动 Loki 并写入一行日志
复制 /opt/lab/loki/ 并启动 Loki,写入一行日志后再次查询该行,将结果保存到 01-boot.txt。
cp -r /opt/lab/loki/* . && chmod +x *.sh
setsid nohup loki -config.file=loki.yaml > loki.log 2>&1 </dev/null &
curl -s localhost:3100/ready # ready 가 될 때까지 20초쯤 걸린다
./push.sh '{"app":"web"}' 'GET /health 200'
export LOKI_ADDR=http://localhost:3100
logcli query --limit=5 --since=1h '{app="web"}'
像 {app="web"} 这样,花括号内就是标签选择器。LogQL 从这里开始。
一个标签组合对应一个流
写入多行具有不同标签组合的日志,并在 02-streams.txt 中记录流数量随组合数量增加的结果。
可以用 ./streams.sh -v 查看当前组合。加入 2 种 app × 2 种 level 后,会得到 4 个流。
**这个数字决定了 Loki 的大部分成本。**因为每个流都会产生独立的块和索引项。
内容不会被索引
用标签中不存在的字符串搜索日志(|=),并在 03-notindexed.md 中写明这不是索引查询,而是扫描读取。
logcli query --since=1h '{app="web"} |= "500"'。它确实有效——但这是读取并搜索由 {app="web"} 缩小后的全部范围。
因此 LogQL 必须始终从标签选择开始。{app=~".+"} |= "500" 会扫描全部内容。这既是 Loki 比 Elasticsearch 便宜的原因,也是标签设计重要的原因。
故意制造基数爆炸
将 request_id 作为标签推送 30 行日志,并在 04-explode.txt 中记录流数量如何变化。
for i in $(seq 30); do ./push.sh "{\"app\":\"bad\",\"request_id\":\"r$i\"}" "req $i"; done
./streams.sh
同时记录写入前后的数量。30 行会增加 30 个流——每行对应一个流。
把相同信息放入内容
把相同的 30 个 request_id 放入日志内容后再次推送,并在 05-fix.txt 中记录这次增加了多少个流。
标签只使用 {"app":"good"},日志内容写成 request_id=r1 status=200 dur=12ms。流只增加 1 个。
仅一行设计差异就造成 30 倍差距,而且没有丢失任何能力——下一步仍可通过解析器原样提取。
从内容中提取并使用值
使用 | logfmt 解析器,以内容中的 status 为条件找出所需行,并保存到 06-parser.txt。
logcli query --since=1h '{app="good"} | logfmt | status="200"'。解析器在查询时运行,因此不会增加索引。
这正是关键——即使不放入标签,过滤能力仍然保留。失去的只是“通过索引立即缩小范围”,换来的是避免流数量爆炸。
区分哪些内容适合作为标签
在 07-rule.md 中分别写出可以用作标签的 3 项和不应作为标签的 3 项,并说明理由。
判断标准只有一个——取值种类是否不会随时间持续增加。app、env、level 的取值有限;user_id、request_id、trace_id、ip、url 会无限增加。
生产中最常见的错误是使用 pod 或 container_id——每次部署都会产生新值,最终缓慢爆炸。
总结三项要点
在 08-notes.md 中至少写三行:什么是流、内容过滤与索引有何不同、标签的判断标准。
正文必须包含 스트림、색인、가짓수。