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

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

LogQLは三つの部分でできている

TT Labで続きを見る

一言でいうと

LogQLクエリは、ストリームセレクター → ログフィルター → 集計の3つの部分でできています。性能は、ほぼ最初の部分が決めます。

なぜ必要なのか

Lokiのクエリが遅いという報告の大部分は、クエリの文法ではなく、範囲を絞っていないことが原因です。前の波括弧がストリームを10万個選べば、後ろで何をしても遅くなります。

3つの部分

sum by (pod) (
  rate({namespace="labhub-prod", app="labhub"} |= "error" | json | status >= 500 [5m])
)
 ^--집계--^        ^------- 스트림 셀렉터 -------^ ^------ 로그 필터 ------^

1)ストリームセレクター{...}: インデックス化されたラベルで候補を選びます。必ず1つ以上必要で、等号マッチャー(=)は正規表現(=~)よりはるかに安いです。

2)ログフィルター|=・!=・|~・!~: 選んだストリームの本文をスキャンします。順序が重要です。

# 나쁨 — 파싱부터 하고 거른다
{app="labhub"} | json | level = "error"

# 좋음 — 문자열로 먼저 줄이고 파싱한다
{app="labhub"} |= "error" | json | level = "error"

|=は単純な部分文字列の検査なので、とても安いです。| jsonは行ごとにパースするため高価です。安いものを先に置くと、パースする行数が大きく減ります。

3)集計: rate、count_over_time、sum byなど。ここでログがメトリクスになります。

# 초당 오류 수를 파드별로
sum by (pod) (rate({app="labhub"} |= "error" [5m]))

# 오류 메시지 상위 10개
topk(10, sum by (msg) (count_over_time({app="labhub"} | json [1h])))

パーサー3種類

パーサー いつ使うか
` json`
` logfmt`
` pattern`
` regexp`

パースして作ったフィールドはインデックス化されません。そのため| status >= 500はスキャンであり、インデックス参照ではありません。よく使う値なら、収集時点でラベルに昇格させることを検討しますが、カーディナリティを先に計算します。

よくある勘違い

「ラベルを増やせばクエリが速くなる」という考え: ラベルを1つ増やすと、ストリーム数がそのラベルの値の個数だけ掛け算されます。statusをラベルに入れるとストリームが5倍(2xx/3xx/4xx/5xx/その他)になり、user_idを入れるとユーザー数の分になります。ストリームが多くなるとすべてのクエリが遅くなります。

「|~の正規表現が便利」という考え: |~ "error|warn"の代わりに|= "error" or |= "warn"のほうがはるかに安いです。正規表現は行ごとにエンジンを回します。

クエリが動く順序を知ればコストが見える

LogQLはパイプラインであり、前で減らすのが常に最も安いです。

{namespace="labhub-prod", app="api"}   ← 1. 색인으로 스트림을 고른다 (거의 공짜)
  |= "timeout"                          ← 2. 원문 문자열 필터 (싸다)
  | json                                ← 3. 파싱 (비싸다)
  | status >= 500                       ← 4. 파싱된 필드로 필터
  | line_format "{{.msg}}"              ← 5. 출력 성형

順序を変えると、かかるコストが変わります。| jsonを先に行って|= "timeout"をあとに行うと、すべての行をパースしてから捨てることになります。文字列フィルターをできるだけ前に引き寄せるのが、LogQLチューニングの最初のルールです。

メトリクスに変える: ログからグラフを取り出す

ログを数えればメトリクスになります。計装のないシステムで特に便利です。

# 5분간 5xx 비율
sum(rate({app="api"} |= "HTTP/1.1" 5" [5m]))
  /
sum(rate({app="api"} [5m]))

# 파싱한 필드로 p95 지연
quantile_over_time(0.95,
  {app="api"} | json | unwrap duration_ms [5m]) by (route)

ただし、これを常時ダッシュボードに置いてはいけません。ログのスキャンは、時系列の参照よりはるかに高価です。値が確認できたら、アプリケーションに本物のメトリクスを入れ、ログベースのクエリは調査のときだけ使います。ログから作ったメトリクスは、「メトリクスを入れる価値があるか」を調べる偵察ツールです。

保持と圧縮をあらかじめ決める

Lokiはログをチャンクにまとめてオブジェクトストレージに入れます。ここで2つを決める必要があります。

アラートに使うクエリは短い範囲(5–15分)にしておき、調査用の長い範囲のクエリと分けます。同じクエリを1分ごとに24時間範囲で実行すると、コストが静かに膨らみます。

実務で本当に大切なこと

クエリが遅いときに最初に測るべきものは、選択されたストリーム数です。

count(count by (pod, container) ({namespace="labhub-prod"}))

Grafanaのクエリ統計(Query inspector)にもTotal bytes processedが出ます。その値がGB単位なら、範囲が広すぎます。時間範囲を減らすか、ラベルをもう1つ付けるほうが、クエリのチューニングより効果が大きいです。