Upgrade to Pro — share decks privately, control downloads, hide ads and more …

低コストなログ基盤を支えるアーキテクチャ 〜Grafana Lokiの設計思想〜

Avatar for Mitsuhiro Tanda Mitsuhiro Tanda
July 10, 2026
380

低コストなログ基盤を支えるアーキテクチャ 〜Grafana Lokiの設計思想〜

Avatar for Mitsuhiro Tanda

Mitsuhiro Tanda

July 10, 2026

Transcript

  1. 自己紹介 反田 光洋 (たんだ みつひろ) @mtanda Staff Developer Advocate Grafana

    Labs ▸ 約10年来のGrafana / Prometheusユーザー ▸ Grafana初期バージョンからのコントリビューター
  2. Grafana Lokiとは ▸ Grafana Labsが開発するOSSのログ基盤システム ▸ Grafana Cloudでマネージドサービスとしても提供されている ▸ マルチテナント対応

    ▸ 書き込みと読み込みのパスが独立してスケール可能 ▸ オブジェクトストレージによる低コストなデータ保存
  3. Loki設計時の意思決定 FROM THE DESIGN DOCUMENT With the migration towards SaaS

    log aggregation, the excessive costs of such systems has become more obvious. This cost arises not just from the technology used to implement full-text search — scaling and sharding an inverted index is hard; either writes touch each shard, or reads must — but also from the operation complexity. [ … ] One common use case for log aggregation systems is to store structured, event-based data — for instance emitting an event for every request to a system, and including all the request details and metadata. With these kind of deployments comes the ability to ask questions like “show me top 10 users with highest 99th percentile latency”, something that you typically cannot do with time series metrics system due to the high cardinality of users. Whilst this use case is totally valid, it is not something we are targeting with this system. Lokiはログ保存のコストと運用のしや すさを重視した設計を選択した。 全文検索や構造化ログへの対応はスコ ープ外として、ログを安く大量に貯め られるようにした。 2018-03 Loki Design Document
  4. データモデルとクエリ言語 Prometheusの設計を、そのままログへ Prometheus メトリクスDB { ラベルの組} → Loki ログDB {

    ラベルの組} → Prometheus メトリクスを問い合わせ rate(http_requests_total{app="api"}[5m]) Loki ログを問い合わせ {app="api"} |~ "5.." どちらも時系列なデータとして記録し、ラベルをインデックスする。
  5. ログの書き込み ログ → ストリーム → チャンク → オブジェクトストレージ サーバから届く同じラベルのログは一本のストリームにまとめられ、順次オブジェクトストレージに保存される サーバ

    200 GET /orders 503 GET /orders 200 GET /cart 201 POST /cart → chunk 書き込み中 201 POST /orders ログ受付中 chunk クローズ済 200 GET /orders 201 POST /pay 200 GET /items chunk クローズ済 201 POST /pay 500 GET /items 200 GET /cart → オブジェクト ストレージ chunk 圧縮済 chunk 圧縮済 chunk 圧縮済
  6. ログの書き込み ラベルで書き込み先をルーティング 受信ログ(ストリーム混在) api 200 GET /orders db SELECT *

    FROM orders api 503 GET /items db SELECT * FROM users ラベルで 振り分け ⇒ {app="api"} Chunk A 200 GET /orders 503 GET /items 200 GET /cart {app="db"} Chunk B SELECT * FROM orders SELECT * FROM users SELECT * FROM sessions
  7. ログの書き込み ラベルとチャンクの対応関係をインデックスに登録 ラベルと場所を 登録 ⇒ chunk A {app="api"} chunk B

    {app="db"} CHUNKS オブジェクトストレージ ラベル → チャンクの場所 {app="api"} → chunk A {app="db"} → chunk B INDEX
  8. ログの読み込み ① インデックスから、読むべきチャンクをしぼりこむ Querier {app="api"} |~ "5.." 1 → ラベルで

    インデックスを 引く 2 → 参照をたどり チャンクを取得 オブジェクトストレージ ラベル → チャンクの場所 {app="api"} → chunk A, C INDEX {app="db"} → chunk B オブジェクトストレージ chunk A chunk C CHUNKS chunk B 対象外
  9. ログの読み込み ② 取得したチャンクから5xxの行を取り出す 3 → |~ "5.." で行を抽出 結果 503

    GET /orders 500 POST /pay 5xxの行だけが返る 取得したチャンクだけを展開し、フィルタにマッチする行だけを返す。 chunk A 200 GET /orders 200 GET /cart 304 GET /static 200 POST /login 200 GET /orders 503 GET /orders 200 GET /cart 200 GET /items 200 GET /orders chunk C 200 GET /items 200 GET /orders 500 POST /pay 201 POST /pay 200 GET /cart 200 GET /orders 200 GET /items 200 GET /orders 200 GET /cart 取得したCHUNKS
  10. ラベルだけをインデックスする Lokiのインデックス方式 ラベルだけがインデックス対象 {app="api"} → chunk #A3 · #A7 {app="db"}

    → chunk #B1 < インデックスサイズ 全文検索のインデックス方式 ログに含まれる全トークンがインデックス対象 "error" → 4, 17, 88, 102, 256 … "timeout" → 12, 33, 91, 240 … "user1" → 7, 64, 199 …
  11. どの属性を、どこに記録するか k8s / OTelの代表的な属性をカーディナリティで振り分け、記録先を決める——低いものはラベルに 属性 例 カーディナリティ 記録先 k8s.cluster.name prod-1

    低 ラベル k8s.namespace.name prod 低 ラベル service.name api 低 ラベル k8s.pod.name api-7d9f2 高 ??? trace_id 4bf9a2… 高 ??? span_id a13c… 高 ???
  12. 高カーディナリティな属性の記録先 structured metadataを追加 {app="api", service_name="checkout"} ストリーム共通のラベル ts message trace_id span_id

    …01 500 GET /orders 4bf9a2 a13c …02 200 GET /cart 8c1de4 7f02 …03 503 POST /pay 1f7b90 a13c timestamp+ ログ本文 structured metadata 高カーディナリティな属性を記録
  13. 集計クエリが実際に読み込むデータ sum(count_over_time({app="api"} | status=~"5.." [5m])) by (user) ts message status

    user …01 GET /orders … 200 alice …02 POST /pay … 500 bob …03 GET /items … 503 alice → status ・user だけが必要でも、チャンク全体を読み込む必要がある
  14. 必要な列だけを読む 行指向 ログ行全体を読む ts message status user …01 GET /orders

    … 200 alice …02 POST /pay … 500 bob …03 GET /items … 503 alice → 全行をスキャンするので重い 列指向 参照する列だけを読む ts …01 …02 …03 message GET /orders … POST /pay … GET /items … status 200 500 503 user alice bob alice → スキャン量が大幅に減る
  15. DataObjectsの導入 チャンク(blocks=行指向)からDataObject(logs section=列指向)へ blocks 行指向 ts log line ts log

    line ts log line 1行ぶんが ひとかたまりで並ぶ ブロック単位で圧縮。読むときは ブロックを丸ごと 展開する。 → logs section 列指向 timestamp ts ts ts ts ts message msg msg msg msg status 200 500 503 200 200 同じカラムの値が 連続して並ぶ カラム単位で圧縮。必要なカラムだけ を読める。
  16. なぜ全文検索はつらいのか 設計上のトレードオフにより、フルスキャンになってしまう {service_name=~".+"} |= "d5063f86b0d6aaf517b4f1e3c4286ce0" {service_name=~".+"} = 全ストリームにマッチ |= =

    本文を文字列でフィルタ → 読むチャンクを 絞り込めない ラベルで絞り込めない どのチャンクにIDが含まれて いるかは分からない INDEX A B C D E … = 実質、すべてのチャンクを読むことになる CHUNKS
  17. Loglineによる全文検索サポート 全ログ 3.5 TB 専用インデックスで絞り込み 対象 8 GB 数秒 ▸

    ラベルで絞り込めない全文検索に向けた専用の仕組み ▸ IDやIPアドレスなどにマッチするログを探す用途に対応 ▸ 専用インデックスのサイズも小さく抑え、検索のコストも予測可能 ※ ベンチマークでの計測結果 ▶