ラベル一つでコストが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を付けてください。単に&で起動すると、シェルが切り替わるときに一緒に終了します。
ツール
| ファイル | 役割 |
|---|---|
push.sh |
ログを1行入れる。最初の引数がラベルJSON |
streams.sh |
現在のストリーム数(-vなら組み合わせまで) |
logcli |
LogQLクエリ |
LogQLはラベル選択から始まる
{app="web", level="error"} |= "timeout" | logfmt | status="500"
└── 색인으로 좁힌다 ──┘ └── 여기부터는 훑어 읽는다 ──────────┘
前の波括弧が読む範囲を決め、後ろはその範囲の中を読みながらふるいにかけます。範囲を絞らなければ、すべてを読みます。
ステップ
- 起動して1行入れる →
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を起動して1行入れる
/opt/lab/loki/をコピーしてLokiを起動し、ログを1行入れたあと、その行を取り出し直して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はここから始まります。
ラベルの組み合わせ1つがストリーム1つ
ラベルの組み合わせが異なるログを何行か入れ、ストリーム数が組み合わせ数の分だけ増えることを02-streams.txtに残してください。
./streams.sh -vで、現在の組み合わせを見られます。app2種×level2種を入れると、ストリームが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個増えます。1行ごとにストリーム1つです。
同じ情報を内容に置く
同じrequest_idの30件を内容に入れてもう一度流し込み、今度はストリームがいくつ増えるかを05-fix.txtに残してください。
ラベルは{"app":"good"}の1つにしておき、行の内容にrequest_id=r1 status=200 dur=12msのように書きます。ストリームは1個しか増えません。
1行の違いで30倍です。そして失うものはありません。次のステップで、パーサーでそのまま取り出して使います。
内容から値を取り出して使う
| logfmtパーサーで、内容の中のstatusを条件にして、ほしい行だけを探し、06-parser.txtに残してください。
logcli query --since=1h '{app="good"} | logfmt | status="200"'。パーサーはクエリ時に動くので、インデックスを増やしません。
これが核心です。ラベルに入れなくても、フィルタリング能力はそのままです。失うのは「インデックスで即座に絞ること」だけで、得るのは、ストリームの爆発を経験しないことです。
ラベルに入れてよいものを見分ける
07-rule.mdに、ラベルとして使ってよいもの3つと使ってはいけないもの3つを、理由とともに書いてください。
判断基準は1つです。値の種類数が、時間がたっても増えないか。app、env、levelは閉じています。user_id、request_id、trace_id、ip、urlは無限に増えます。
現場で最もよくある間違いは、podやcontainer_idです。デプロイのたびに新しい値ができて、じわじわと破綻します。
3つを整理する
08-notes.mdに3行以上。ストリームとは何か、内容フィルターがインデックスとどう違うか、ラベルの判断基準です。
本文に스트림、색인、가짓수が含まれている必要があります(韓国語でそれぞれ「ストリーム」「インデックス」「種類数」を意味する語です)。