LabHub
学习 学习路径 课程

Loki — 不索引日志的日志库

一个标签就让成本变成 30 倍

在 LabHub 中继续学习

目标

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"
└── 색인으로 좁힌다 ──┘ └── 여기부터는 훑어 읽는다 ──────────┘

前面的花括号决定读取范围,后面的部分在该范围内扫描并过滤。 如果不缩小范围,就会读取全部内容。

步骤

  1. 启动并写入一行 → 01-boot.txt
  2. 标签组合 = 流 → 02-streams.txt
  3. 内容不进入索引 → 03-notindexed.md
  4. 制造基数爆炸 → 04-explode.txt
  5. 把相同信息放入内容 → 05-fix.txt
  6. 使用解析器提取值 → 06-parser.txt
  7. 标签判断标准 → 07-rule.md
  8. 总结 → 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 项,并说明理由。

判断标准只有一个——取值种类是否不会随时间持续增加。appenvlevel 的取值有限;user_idrequest_idtrace_idipurl 会无限增加。

生产中最常见的错误是使用 podcontainer_id——每次部署都会产生新值,最终缓慢爆炸。

总结三项要点

08-notes.md 中至少写三行:什么是流、内容过滤与索引有何不同、标签的判断标准。

正文必须包含 스트림색인가짓수