Kibanaの新定番!ES|QL と Vega を組み合わせて自由自在なカスタム可視化を実現する

Tech Blog banner featuring futuristic UI graphics and Japanese headline about Kibana, ES|QL, and Vega for custom visualizations. BLOG

Kibanaのダッシュボードで、標準の「Lens」では表現できない複雑なチャート(サンキーチャートやカスタムマップ、特殊な散布図など)を作りたいとき、強力な武器になるのが Vega / Vega-Lite です。

しかし、これまでは重厚な Elasticsearch の Query DSL(JSON)を手書きしてデータを集計する必要があり、記述の複雑さに頭を悩ませた方も多いのではないでしょうか?

そんな開発者・アナリストに朗報です。バージョン 9.4+(Serverless Stack)以降、直感的で強力な新しいクエリ言語 ES|QL(Elasticsearch Query Language) を Vega のデータソースとして直接利用できるようになりました!

今回は、この ES|QL × Vega の組み合わせがもたらすメリットと、具体的な実装方法をコード例付きで解説します。


なぜ ES|QL × Vega なのか? 3つのメリット

1. 複雑な Query DSL からの解放

パイプライン演算子( | )を使ってデータを段階的に加工できる ES|QL を使うことで、ネストの深い Elasticsearch 独自の集計クエリを書く必要がなくなります。クエリの可読性が劇的に向上します。

2. データ形式の自動変換

ES|QL のクエリ結果は本来「列指向(Columnar)」ですが、Kibana の Vega インテグレーターが自動的に Vega が期待する「行ベース(Row-based / 1行1オブジェクト)」のJSON形式へ変換 してくれます。手動でのパース処理( format.property などの指定)の手間が大幅に減ります。

3. ダッシュボードの「時間範囲」や「フィルター」と完全同期

Kibana の拡張トークンを利用することで、ユーザーがダッシュボード上で操作した時間フィルターや検索条件を、ES|QL クエリ内に動的にマッピングできます。


ES|QL を呼び出すための設定パラメータ

Vega の data.url オブジェクト内に以下のパラメータを指定することで、ES|QL モードが有効になります。

パラメータ必須/任意概要
"%type%"必須"esql" を指定します。
"query"必須実行したい ES|QL (1行で書く)
"%context%"任意true に設定すると、ダッシュボードのグローバルフィルターがクエリに自動適用されます。
"%timefield%"任意タイムスタンプのフィールド名を指定。これを設定すると、クエリ内で名前付きパラメータ ?_tstart と ?_tend が利用可能になります(ダッシュボードの時間範囲が代入されます)。
"dropNullColumns"任意true (デフォルト)の場合、null 値しか含まれない列をレスポンスから自動で除外します。
"params"任意クエリに動的に代入したい名前付きパラメータの配列を指定します。

Vega / Vega-Lite を使った Visualization の作成手順

Kibanaにおけるカスタム可視化の作成手順は以下の通りです(従来通り)。

1: Dashboard を新規作成するか、既存の Dashboard を編集モードにします。

2: 右上の [Add panel] プルダウンメニューから [New panel] を選択します。

3: パネル選択画面から [</> Custom visualization] を選択します。

4: Vega / Vega-Lite の作成画面が表示されるので、右側のエディタに JSON を記述していきます。

5: デバッグしたい場合は、上部の Inspect をクリックしてください。

【実践】サンプルコードで見る実装例1:折れ線グラフ(Vega-Lite)

以下は、サンプルデータ(Webログ)を使用し、時間の経過に伴うイベント数を2時間おきに集計して折れ線グラフ(Line Chart)で描画する Vega-Lite (v6) の定義例です。

{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "title": "Event counts per 2 hours",
  "data": {
    "url": {
      "%type%": "esql",
      "%context%": true,
      "%timefield%": "@timestamp",
      "query": "FROM kibana_sample_data_logs | WHERE @timestamp >= ?_tstart AND @timestamp < ?_tend | STATS doc_count=COUNT() BY tbucket=TBUCKET(2 hour) | SORT tbucket"
    }
  },
  "mark": "line",
  "encoding": {
    "x": {
      "field": "tbucket",
      "type": "temporal",
      "axis": { "title": false }
    },
    "y": {
      "field": "doc_count",
      "type": "quantitative",
      "axis": { "title": "Document count" }
    }
  }
}

💡 コードの解説

  1. **%timefield%: "@timestamp" と ?_tstart / ?_tend**

ES|QL クエリ内の WHERE @timestamp >= ?_tstart AND @timestamp < ?_tend に注目してください。これにより、Kibana ダッシュボードの右上にある時間セレクター(例: 「過去24時間」など)の範囲が、自動的にこのプレースホルダーに流し込まれます。

  1. STATS ... BY tbucket=TBUCKET(...)

ES|QL の強力な関数 TBUCKET を使い、2時間ごとのバケットに丸めてカウント( COUNT() )しています。従来の Query DSL で date_histogram アグリゲーションを書くよりも圧倒的にシンプルです。

  1. mark : "line"

"mark": "line" を指定することで、集計データを折れ線グラフとして描画するよう指示しています。

  1. encoding セクション

ES|QL で集計した結果の列名( tbucket と doc_count )が、そのまま Vega-Lite の field として直感的にマッピングされているのがわかります。

🖼️ 実行結果


【実践】サンプルコードで見る実装例2:時間帯×日付のヒートマップ(Vega-Lite)

次に、1時間ごとのイベント数を「曜日や日付×時間帯」のマトリクスで可視化し、アクセスの時間帯トレンドを一目で把握できるヒートマップ(パンチカード)の例をご紹介します。

{
  "$schema": "https://vega.github.io/schema/vega-lite/v6.json",
  "title": "kibana_sample_data_logs 内の時間ごとのログ数",
  "data": {
    "url": {
      "%type%": "esql",
      "%context%": true,
      "%timefield%": "@timestamp",
      "query": "FROM kibana_sample_data_logs | WHERE @timestamp >= ?_tstart AND @timestamp < ?_tend | STATS count=COUNT() BY datetime=TBUCKET(1 hour) | LIMIT 10000 | SORT datetime"
    }
  },
  "config": {
    "view": {
      "strokeWidth": 0,
      "step": 13
    },
    "axis": {
      "domain": false
    }
  },
  "mark": "rect",
  "encoding": {
    "x": {
      "field": "datetime",
      "timeUnit": "hours",
      "type": "ordinal",
      "title": "時"
    },
    "y": {
      "field": "datetime",
      "timeUnit": "date",
      "type": "ordinal",
      "title": "日"
    },
    "color": {
      "field": "count",
      "type": "quantitative",
      "legend": {
        "title": "カウント"
      }
    }
  }
}

💡 コードの解説

  1. mark : "rect"によるグリッド描画

Vega-Lite でヒートマップを作る際は、タイル(四角形)を描画する "rect" マークを使用します。

  1. timeUnit を使った「時」と「日」の切り出し

ES|QL 側からは TBUCKET(1 hour) で丸められた一連のタイムスタンプが返ってきます。それを Vega-Lite 側の timeUnit プロパティを使って、X軸には「時(hours)」、Y軸には「日(date)」として切り出してマッピングしています。これにより、複雑なクエリを書くことなく、フロントエンド側で綺麗な2次元マトリクスを表現できます。

  1. color による密度の可視化

集計したイベント数( count )を color の quantitative(量的データ)として指定することで、ログ件数に応じた色の濃淡が自動的に適用されます。

🖼️ 実行結果


まとめ:データ分析と表現の幅を広げよう

これまでの Kibana カスタム可視化は、「Query DSL が難解で手が出せない」というエンジニアも多かったかと思います。しかし、ES|QL の登場によって、SQL ライクな直感的な記述でバックエンドのデータを引き出し、Vega の表現力をフルに活かせるようになりました。

ダッシュボードの表現力をもう一段階引き上げたい方は、ぜひ公式ドキュメントの Custom visualizations with Vega | Elastic Docs を参考に、ES|QL × Vega の強力なタッグを試してみてください!

その他の参考URL