TT Lab
はじめる
学ぶ 学習パス コース

Loki — ログを索引しないログストア

ラベル一つでコストが30倍になる

TT Labで続きを見る

目標

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. 起動して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を起動して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行以上。ストリームとは何か、内容フィルターがインデックスとどう違うか、ラベルの判断基準です。

本文に스트림、색인、가짓수が含まれている必要があります(韓国語でそれぞれ「ストリーム」「インデックス」「種類数」を意味する語です)。