🏗️

jaffle_shopでDataHubを実践してみた

に公開

はじめに

以前、jaffle_shop で OpenMetadata を試してみた記事を書きました。

https://zenn.dev/kobatanz/articles/jaffle-shop-openmetadata

OpenMetadata は、PostgreSQL などのコネクタで実データベースに接続してスキーマを取り込み、そこに dbt のメタデータを重ねるスタイルでした。実 DB を introspect するぶん、カラムの型やプロファイリングまで取れるのが強みです。

そこで今回は、同じ jaffle_shop を使って DataHub を試してみます。DataHub は LinkedIn が社内で開発してオープンソース化したデータカタログで、こちらは dbt の manifest.json を起点にメタデータを構築するスタイル。同じ題材で2つのツールを比べると、アプローチの違いが見えてきて面白そうです。

結論から言うと、取り込みはコマンド一発で完了しました。そのうえで DataHub は機能がかなり広く、リネージやカタログだけでなく、ガバナンス・データ品質・API・アクセス制御まで一通り揃っています。せっかくなので今回は、jaffle_shop を題材にしながら DataHub の機能をひととおり紹介し、OpenMetadata との違いも整理するという欲張りな構成でいきたいと思います。

少し長くなりますが、「DataHub ってどんなツールなの?」「OpenMetadata とどっちを選べばいいの?」という疑問に答えられる内容を目指します。

DataHub とは?

一言で言うと、データの出所と流れ、そして意味を組織全体で管理するためのプラットフォームです。

LinkedIn が社内のメタデータ管理のために開発し、2020 年にオープンソース化しました。現在は Acryl Data(DataHub のマネージドサービス「DataHub Cloud」を提供している会社)が中心となって開発を続けています。公式サイトの採用企業一覧(Trusted by Industry Leaders)には、開発元の LinkedIn のほか、Netflix・Expedia Group・Notion・Miro・Coursera といった企業が名を連ねていて、データ基盤の規模が大きい企業での採用事例が多いのが特徴です。

DataHub が扱うメタデータは、ざっくり次のようなものです。

  • テクニカルメタデータ: テーブル・カラムのスキーマ、データ型、リネージ(依存関係)
  • ビジネスメタデータ: 説明文、タグ、用語集(Glossary)、ドメイン、オーナー
  • オペレーショナルメタデータ: 統計情報(行数・更新頻度)、データ品質の検証結果、インシデント

これらを一元管理して、「このテーブルどこから来てるんだっけ?」「このカラムの意味って何だっけ?」「壊れたら誰に連絡すればいい?」といった疑問にまとめて答えられる状態を作るのが DataHub の役割です。

OpenMetadata との立ち位置の違い(先に結論)

両者とも「オープンソースのデータカタログ&リネージツール」というカテゴリは同じです。ただ、設計思想にはけっこう違いがあります。先に大枠だけ押さえておきましょう。

  • OpenMetadata: シンプルな構成(MySQL/PostgreSQL + Elasticsearch)。スキーマは JSON Schema ベース。取り込みは Airflow を使った pull 型。データ品質テストとプロファイリングをネイティブに内蔵しているのが強み。
  • DataHub: Kafka を組み込んだイベント駆動アーキテクチャ。スキーマは PDL(Pegasus Definition Language)ベース。取り込みは push / pull の両対応でリアルタイム性が高い。メタデータモデルの拡張性と API の豊富さ(REST / GraphQL / Kafka)が強み。

この違いがどこから来ているのかは、アーキテクチャを見ると腑に落ちます。さっそく中身を覗いていきましょう。

DataHub のアーキテクチャを理解する

機能紹介に入る前に、DataHub がどういう仕組みで動いているのかを押さえておきます。ここを理解しておくと、後で出てくる「なぜ取り込みが速いのか」「なぜ API が3種類もあるのか」が納得できるようになります。

主要コンポーネント

datahub docker quickstart で起動すると、実際に次のコンテナが立ち上がります(今回のバージョンは GMS / Frontend / Actions が v1.5.0.6)。

Terminal
docker ps --format "{{.Names}}\t{{.Image}}" | grep datahub
datahub-datahub-gms-quickstart-1       acryldata/datahub-gms:v1.5.0.6
datahub-frontend-quickstart-1          acryldata/datahub-frontend-react:v1.5.0.6
datahub-datahub-actions-quickstart-1   acryldata/datahub-actions:v1.5.0.6-slim
datahub-opensearch-1                   opensearchproject/opensearch:2.19.3
datahub-kafka-broker-1                 confluentinc/cp-kafka:8.0.0
datahub-mysql-1                        mysql:8.2

それぞれの役割は次のとおりです。

  • GMS(Generalized Metadata Service): DataHub の心臓部。メタデータの読み書きを一手に引き受けるバックエンドサービスで、ポート 8080 で REST / GraphQL / OpenAPI を公開します。
  • Frontend(React): ブラウザで触る UI。ポート 9002。裏で GMS の GraphQL を叩いています。
  • MySQL: メタデータの本体(アスペクト)を保存するプライマリストア。いわゆる「正」のデータがここに入ります。
  • OpenSearch: 全文検索インデックスとグラフインデックスを担当します。検索の高速さもリネージのたどりやすさも、ここのおかげです。
  • Kafka: メタデータの変更を流すイベントストリーム。後述する push 型取り込みやリアルタイム連携の要になります。
  • Actions: Kafka に流れる変更イベントを購読して、外部処理をトリガーするコンポーネント。UI からの取り込み実行もここが担います。

OpenMetadata が「MySQL + Elasticsearch」というシンプルな2本柱なのに対し、DataHub は Kafka を中心に据えたイベント駆動になっているのが構造上の最大の違いです。この Kafka があるおかげで、後述する「リアルタイムなメタデータ反映」や「変更イベントをフックした自動化」が自然にできるようになっています。

メタデータモデル — Entity と Aspect と URN

DataHub を理解するうえで一番大事な概念が、メタデータモデルです。ここは OpenMetadata と思想がはっきり分かれるところなので、少し丁寧に説明します。

DataHub のメタデータは、次の3つの要素で表現されます。

  • Entity(エンティティ): メタデータの主役となるオブジェクト。dataset(テーブル)、dashboardchartdataFlowglossaryTermdomaincorpuser(ユーザー)などがあります。
  • Aspect(アスペクト): エンティティの「ある一面」を表す属性のまとまり。たとえば dataset エンティティには schemaMetadata(スキーマ)、ownership(オーナー)、globalTags(タグ)、datasetProperties(プロパティ)といったアスペクトがぶら下がります。
  • URN(Uniform Resource Name): エンティティを一意に識別する文字列。

URN は最初ちょっと面食らうのですが、慣れると便利です。今回の jaffle_shop の customers テーブルなら、こんな URN になります。

urn:li:dataset:(urn:li:dataPlatform:dbt,analytics.jaffle_shop.customers,PROD)

dataset というエンティティタイプ、dbt というプラットフォーム、analytics.jaffle_shop.customers という名前、PROD という環境(Fabric)の4点で、世界に一つのテーブルを特定しています。記事の後半で出てくる API 呼び出しでは、この URN をキーにしてメタデータを読み書きします。

そして DataHub の取り込みは、内部的には MCP(MetadataChangeProposal) という「このエンティティのこのアスペクトをこう変えてくれ」という提案メッセージの集まりです。取り込みログに出てくる「produced 186 events」の 186 がこの MCP の数にあたります。MCP が処理されると、変更ログが MCL(MetadataChangeLog) として Kafka に流れ、OpenSearch のインデックス更新や Actions のトリガーが走る——という流れです。

ここまで押さえれば準備運動は完了です。実際に動かしていきましょう。

環境のセットアップ

前提条件

今回の構成です。

  • macOS(Apple Silicon)
  • Docker Desktop(メモリ割り当て: 8GB 以上)
  • dbt Fusion + PostgreSQL(jaffle_shop を動かすため)
  • Python 3.x(datahub CLI のインストールに必要)

datahub CLI をインストールする

DataHub の操作は acryl-datahub という Python パッケージで行います。dbt 連携のために dbt プラグインも一緒にインストールします。

Terminal
pip install "acryl-datahub[dbt]"

ちなみに acryl-datahub[dbt] のように、角括弧の中で使いたいコネクタを指定する方式です。Snowflake なら acryl-datahub[snowflake]、BigQuery なら acryl-datahub[bigquery] といった具合に、必要なものだけを足していけます。

インストールが完了したらバージョンを確認します。

Terminal
datahub version
DataHub CLI version: 1.6.0
Models: bundled
Python version: 3.11.3

CLI のバージョンは 1.6.0、後述する起動でサーバ(GMS)側は v1.5.0.6 が立ち上がります。CLI とサーバはバージョンが多少ずれていても動きますが、大きく離れると挙動が変わることがあるので、本番では揃えておくのが無難です。

Docker で DataHub を起動する

DataHub は datahub docker quickstart コマンドで Docker Compose 環境が一式立ち上がります。Compose ファイルを自分で書く必要はありません。

Terminal
datahub docker quickstart

初回はイメージのダウンロードが発生するため、10〜20 分程度かかります。前のセクションで見たコンテナ群が順番に起動し、GMS のヘルスチェックが通ると完了です。http://localhost:9002 でフロントエンドにアクセスできるようになります。

起動が不安定なときは、次のコマンドで状態を確認できます。

Terminal
datahub docker check

GMS がなかなか healthy にならない場合は、メモリ不足のことが多いです(私も最初これでハマりました)。Docker のメモリ割り当てを増やして再起動すると解決します。

DataHub にログインする

ブラウザで http://localhost:9002 を開くとログイン画面が表示されます。

DataHub ログイン画面

デフォルトの認証情報でログインします。

  • Username: datahub
  • Password: datahub

「Sign in with SSO」というボタンも見えますが、これは後述する OIDC 連携を設定したときに使うものです。今回はローカル検証なので、デフォルトのユーザー名・パスワードでログインします。

ログインすると、挨拶とともにダッシュボードが表示されます。

DataHub ダッシュボード

ダッシュボードには「Your Assets」「Domains」「Platforms」のパネルが並びます。この画面は後述の取り込みを済ませた状態なので、Platforms には dbt が表示されています(取り込み前は空っぽです)。上部の検索バーからデータセットやダッシュボードを横断検索できます。

さっそく jaffle_shop のメタデータを取り込んでいきましょう。

メタデータ取り込みの仕組みを理解する

ここも DataHub の肝なので、いきなりコマンドを打つ前に、取り込みの仕組みを整理しておきます。

レシピ = source + sink (+ transformer)

DataHub の取り込みは「レシピ(recipe)」と呼ばれる YAML ファイルで設定します。レシピは大きく3つのパートでできています。

  • source: どこからメタデータを取ってくるか(dbt、Snowflake、BigQuery、MySQL、Looker……80以上のコネクタがあります)
  • sink: 取ってきたメタデータをどこに流すか(datahub-restdatahub-kafkafileconsole
  • transformer(任意): 取り込みの途中でメタデータを加工する(タグやオーナーを一括付与するなど)

この「source → (transformer) → sink」というパイプライン構造は、Singer や Meltano の tap/target に似ています。source と sink が分離しているので、たとえば「いったん file sink に出力して中身を確認 → 問題なければ datahub-rest sink に流す」といった使い方ができて便利です。

sink の種類と push / pull の話

sink には主に次の種類があります。

sink 用途
datahub-rest GMS の REST API に同期的に送る(一番オーソドックス)
datahub-kafka Kafka に非同期で流す(大量データを高スループットで投入したいとき)
file JSON ファイルに書き出す(デバッグ・確認用)
console 標準出力に出す(動作確認用)

ここが OpenMetadata との設計思想の違いがはっきり出るところです。OpenMetadata は基本的に Airflow による pull 型(OpenMetadata 側がスケジュールに従ってデータソースを取りに行く)でした。一方 DataHub は、datahub-kafka sink を使えば任意のシステムからメタデータを push できます。たとえば自社の ETL ジョブの最後に「処理したテーブルのメタデータを Kafka に流す」といったコードを仕込めば、リアルタイムにカタログが更新されます。この push / pull 両対応が、Kafka を中心に据えたアーキテクチャの恩恵です。

transformer でメタデータを加工する

transformer を使うと、取り込みの途中でメタデータに一括で手を加えられます。たとえば「dbt から取り込んだテーブルすべてに legacy タグを付ける」なら、レシピにこう書きます。

transformerの例
transformers:
  - type: simple_add_dataset_tags
    config:
      tag_urns:
        - "urn:li:tag:legacy"

オーナーの一括付与(simple_add_dataset_ownership)、用語集の紐付け(simple_add_dataset_terms)など、よく使うものは標準で用意されています。Python で独自の transformer を書くこともできます。

stateful ingestion(削除の自動検知)

地味に重要なのが stateful ingestion です。これを有効にすると、DataHub は前回の取り込み内容を覚えていて、「前回はあったけど今回は無くなったテーブル」を自動で論理削除(soft delete)してくれます。dbt のモデルを消したのにカタログに幽霊テーブルが残り続ける、という問題を防げます。

stateful ingestionの設定
source:
  type: dbt
  config:
    # ... 省略 ...
    stateful_ingestion:
      enabled: true

プロファイリングについて

ここは正直に書いておきます。DataHub OSS には、データの中身を統計的に分析するプロファイラがソースによっては付いています(Snowflake や BigQuery などのウェアハウス系コネクタは行数・NULL 率・最小最大値などを取得できます)。ただし dbt source 自体はメタデータファイルを読むだけなので、プロファイリングは行いません。

この点は OpenMetadata との明確な差です。OpenMetadata はネイティブのプロファイラとデータ品質テスト(約30種類)を内蔵しており、ツール単体で「カラムの分布を見る」「NULL が一定割合を超えたら失敗にする」といったことができます。DataHub はこの領域を、Great Expectations や dbt test などの外部ツールと連携して埋める設計になっています(後述)。

dbt source の中身をもう少し詳しく

今回使う dbt source には、知っておくと便利な設定がたくさんあります。

dbt sourceの主な設定項目
source:
  type: dbt
  config:
    manifest_path: ./target/manifest.json   # 必須。モデル・リネージの情報源
    catalog_path: ./target/catalog.json     # 任意。カラムの型情報を補う
    sources_path: ./target/sources.json     # 任意。ソースのフレッシュネス情報
    run_results_paths:                       # 任意。dbt testの結果
      - ./target/run_results.json
    target_platform: postgres                # 実テーブルがあるプラットフォーム
    include_column_lineage: true             # カラムレベルリネージを生成
    meta_mapping: {}                         # dbtのmetaをタグ/用語/オーナーに変換

特に面白いのが meta_mapping です。dbt のモデルに書いた meta 情報を、DataHub のタグ・用語集・オーナーに自動変換できます。たとえば dbt 側で meta: {owner: "data-team", contains_pii: true} と書いておけば、DataHub に取り込んだときに自動でオーナーと PII タグが付く、といった運用ができます。dbt にメタデータを集約している チームには相当うれしい機能です。

そして dbt source の挙動でもう一つ重要なのが、1つの dbt ノードにつき2つのエンティティを作るという点です。customers モデルなら、

  1. dbt プラットフォーム側の customers(dbt のモデルとしての顔)
  2. target_platform(今回は postgres)側の customers(実テーブルとしての顔)

の2つが登録され、両者は Siblings(兄弟) として関連づけられます。この Siblings という概念は後でもう一度出てきます。

jaffle_shop のメタデータを取り込む

仕組みがわかったところで、実際に取り込みます。

dbt を実行してメタデータファイルを生成する

DataHub の dbt 連携では manifest.json を使います(catalog.json はオプション)。dbt を実行してメタデータファイルを生成しておきます。

Terminal
dbt run

target/ ディレクトリに manifest.json が生成されていれば OK です。

Terminal
ls target/
graph.gpickle  manifest.json  partial_parse.msgpack  run_results.json  semantic_manifest.json

レシピファイルを作成する

今回はシンプルに、dbt ソースと datahub-rest sink だけのレシピを書きます。

datahub_recipe.yml
source:
  type: dbt
  config:
    manifest_path: ./target/manifest.json
    target_platform: postgres

sink:
  type: datahub-rest
  config:
    server: "http://localhost:8080"

target_platform には実際のデータベースを指定します。今回 jaffle_shop は PostgreSQL 上で動かしているので postgres を設定します。catalog_path は省略可能で、指定しない場合はカラムの型情報が一部欠けますが、リネージやモデルの説明文は問題なく登録されます。

sink の server は GMS のアドレス(ポート 8080)です。UI のポート 9002 ではないので注意してください。

メタデータを取り込む

レシピファイルを使って取り込みを実行します。

Terminal
datahub ingest -c datahub_recipe.yml

実行すると、こんな感じの出力が表示されます。

[2026-05-30 16:20:48] INFO - Starting metadata ingestion
[2026-05-30 16:20:52] WARNING - No catalog file configured: Some metadata, particularly schema information, will be missing.
[2026-05-30 16:20:52] INFO - Extracted 6 semantic models from manifest
[2026-05-30 16:20:59] INFO - Creating dbt metadata for 52 nodes
[2026-05-30 16:21:18] INFO - Finished metadata ingestion

Pipeline finished with at least 1 warnings; produced 186 events in 30.11 seconds.

186 イベント(= 186 個の MCP)が登録されました。前のセクションで説明したとおり、この数字は「いくつのアスペクトを変更したか」の総数です。manifest.json を読み込むだけなので実データベースへの接続も不要で、コマンド一発で完了します。この手軽さは DataHub の魅力のひとつです。

catalog.json なしの警告が出ていますが、リネージとモデル情報の取り込みには影響しません。

UI から取り込みを管理する(Managed Ingestion)

ここまで CLI で取り込みましたが、DataHub は UI からも取り込みを設定・スケジュール実行できます。左メニューの歯車(Settings)配下、または /ingestion を開くと「Manage Data Sources」画面が表示されます。

Managed Ingestion の画面

先ほど CLI で実行した取り込みが [CLI] dbt として記録され、ステータスが「Success」、最終実行が「4 hours ago」と表示されています。画面には3つのタブがあります。

  • Sources: 取り込みソースの一覧。「Create Source」から GUI ウィザードで新規ソースを追加でき、cron でスケジュール実行も設定できます。
  • Run History: 過去の実行履歴。各実行のログや取り込み件数を確認できます。
  • Secrets: 接続情報(パスワードや API キー)を暗号化して保管する場所。レシピからは ${MY_SECRET} のように参照します。

UI からソースを作るとスケジュールに従って Actions コンテナの executor が取り込みを実行してくれるので、Airflow のような別のスケジューラを用意しなくても定期取り込みが回せます。

ここも OpenMetadata との対比が面白いところです。OpenMetadata は UI からの定期取り込みに Airflow を使う構成(metadata CLI で回すこともできます)。DataHub は Airflow なしで UI 取り込みが完結し、スケジュール実行まで Actions の executor が面倒を見てくれます(もちろん Airflow と連携することも可能です)。「とりあえず UI で定期取り込みを回したい」という段階での手軽さは DataHub に分があります。

検索とディスカバリ

取り込んだメタデータを使い倒していきましょう。まずは検索です。

全文検索とフィルタ

上部の検索バーから jaffle_shop と検索してみます。

jaffle_shop の検索結果

customerslocationsordersproducts といったモデルが一覧で表示されました。「Showing 1 - 10 of 151 results」とあるように、dbt の各モデル・ソースに加えて PostgreSQL 側の実テーブルも含めて、たくさんのエンティティがヒットしています。customers カードには緑のチェックや 顧客生涯価値PII・オーナーのチップ、orders カードには赤いインシデントのマークも付いていますが、これらは後のセクションで実際に付けていくものです。

画面上部には Type / Platform / Last Modified / Siblings といったフィルタが並び、左側には dbtPostgreSQL という Platform のチップが見えます。この検索は OpenSearch(Elasticsearch 互換)が裏で支えていて、テーブル名だけでなく説明文・カラム名・タグ・用語集など、取り込んだメタデータ全体を対象に全文検索できます。

検索バーでは次のようなクエリ構文も使えます。

tags:PII                          # PIIタグの付いたものだけ
platform:dbt AND name:stg_*       # dbtのstg_で始まるモデル
domain:Marketing                  # Marketingドメインに属するもの

Siblings — dbt モデルと実テーブルを束ねる

先ほど「dbt ノードは2つのエンティティになる」と書きました。検索結果では、この dbt モデルと実テーブルが Siblings として1つに束ねられて表示されます。検索結果カードに dbt のアイコンと PostgreSQL(象)のアイコンが両方ついているのはこのためです。

これは地味ですが効いている機能です。「論理的なモデル(dbt)」と「物理的なテーブル(PostgreSQL)」を別物として扱うと一覧がノイズだらけになりますが、Siblings として統合することで「1つのテーブル、ただし dbt の顔と PostgreSQL の顔がある」という自然な見え方になります。dbt + ウェアハウスという構成では特にありがたい仕組みです。

OpenMetadata にはこの「dbt と物理テーブルを兄弟として統合する」という概念はなく、dbt のメタデータは物理テーブルにマージされる形で扱われます。どちらが良いかは好みですが、「dbt 側の情報と物理側の情報を切り替えて見たい」というケースでは DataHub の Siblings が便利です。

データセットの詳細ページを一周する

検索結果から stg_customers を開くと、データセットの詳細ページが表示されます。DataHub のデータセットページはタブが10個近くあり、ここを一周するだけで DataHub の機能の広さがわかります。順番に見ていきましょう。

ページは大きく2つの領域に分かれています。中央のメインエリア(タブ切り替え)と、右側の Summary パネルです。

右側の Summary パネル

Summary パネルには、そのデータセットの「要点」が集約されています。

  • Documentation: 説明文。dbt の description がそのまま入ります。
  • Owners / Domain / Data Product: オーナー・ドメイン・データプロダクトの割り当て(後述のガバナンス機能)
  • Tags / Terms: タグと用語集の紐付け
  • Status: 最終同期日時とソース(今回は「Synced from Postgres」)
  • Composed of: Siblings の構成(dbt モデルと実テーブル)
  • View Definition: モデルの SQL 定義(Source / Compiled を切り替え可能)

特に View Definition が便利で、dbt モデルの元の SQL({{ ref(...) }} を含む Source)とコンパイル後の SQL(Compiled)を UI 上で切り替えて確認できます。「このモデルって結局どういう SQL なの?」がカタログから直接見られます。

Columns(スキーマ)タブ

stg_customers の Columns タブを見てみます。

stg_customers のスキーマ

customer_id カラムと、dbt の schema.yml に書いた説明文「The unique key for each customer.」が表示されています。カラムごとに Type / Description / Tags / Glossary Terms / Stats の列があり、カラム単位でタグや用語を紐付けられるようになっています。

Properties タブ — dbt のメタデータが丸ごと入る

Properties タブには、取り込み時に付与されたカスタムプロパティがキー・バリュー形式で並びます。orders の Properties を見てみましょう。

orders の Properties

node_type(semantic_model)、dbt_file_path(models/marts/orders.yml)、manifest_version(1.11.7)、dbt_unique_id(semantic_model.jaffle_shop.orders)、manifest_schema など、dbt の manifest から取り込んだ情報がそのまま確認できます。「このモデルの定義ファイルはどこ?」「どのバージョンの dbt で生成された?」といったことが、カタログを見るだけでわかります。

その他のタブ

残りのタブもざっと紹介しておきます(今回の jaffle_shop では中身が空のものもありますが、機能として用意されています)。

  • View Definition: モデルの SQL 定義(Summary パネルと同じ内容を全画面で)
  • Documentation: リッチテキストの説明文。Markdown でドキュメントを書き足せます。リンクの追加も可能。
  • Queries: そのテーブルに対してよく実行されるクエリ。ウェアハウス系コネクタだとクエリログから人気クエリを抽出してくれます。
  • Stats: プロファイリング結果(行数・カラムごとの統計・履歴)。プロファイラ対応ソースで値が入ります。
  • Quality: アサーション(データ品質の検証ルール)とその結果。
  • Governance: コンプライアンス関連のメタデータ。
  • Incidents: そのデータセットで発生中のインシデント(障害)の管理。

このタブ構成を見るだけで、DataHub が単なる「テーブル一覧ツール」ではなく、ディスカバリ・ドキュメント・品質・インシデント管理まで1画面に集約しようとしていることが伝わると思います。

データリネージ

DataHub の目玉機能、リネージです。

テーブルレベルのリネージ

stg_customers の Lineage タブを開いてみます。

stg_customers のリネージ

グラフに変換の流れが矢印で表示されています。左のノードが上流の analytics.raw.raw_customers ソース、右のノードが下流の analytics.jaffle_shop.stg_customers で、その間を dbt のモデルがつないでいます。raw_customers → stg_customers という依存関係が一目でわかります。

このリネージは dbt の {{ ref(...) }} / {{ source(...) }} から自動生成されています。SQL を解析しているわけではなく、dbt が manifest に記録した依存関係を使うので、確実かつ高速です。

もう少し複雑な例として、order_items モデルのリネージも見てみましょう。order_items は複数のステージングテーブルを結合して作られる中間モデルです。

order_items のリネージ

右側の analytics.jaffle_shop.order_items に対して、左側から stg_order_itemsstg_ordersstg_productsstg_supplies という 4 つのステージングテーブルが合流(fan-in)している様子が一目でわかります。「このモデルって結局どのテーブルを組み合わせて作ってるんだっけ?」が、SQL を読まなくてもグラフで把握できます。

各ノードの端にある矢印(< >)をクリックすると、さらに上流・下流のテーブルを順番に展開できます。大規模なリネージでも、一気に全部広げるのではなく、たどりたい方向に少しずつ展開していけるようになっています。

Explorer と Impact Analysis

Lineage タブの上部には「Explorer」と「Impact Analysis」という2つのモードがあります。

  • Explorer: 今見たグラフ表示。上流・下流を視覚的にたどるモード。
  • Impact Analysis: 「このテーブルを変更したら、下流のどこに影響が出るか」を**一覧(表形式)**で洗い出すモード。

試しに stg_orders を起点に Impact Analysis を開いてみます。

stg_orders の Impact Analysis

「Downstreams(下流)」として、order_items(2nd)・order_item(4th)など、stg_orders を変更したときに影響を受ける5つのアセットが、影響の深さ(次数)バッジ付きで一覧表示されました。左側のフィルタで「Degree of Dependencies(依存の深さ)」「Type」「Platform」を絞り込めるほか、後述する Glossary Term(ここでは 注文明細)でも絞り込めるようになっています。

stg_orders のスキーマを変えたいんだけど、影響範囲はどこ?」というときに、下流のすべてのモデル・ダッシュボードをグラフで追わなくても、漏れなくリストアップしてくれます。これは運用でかなり役立ちます。

カラムレベルリネージ

DataHub はテーブル間だけでなく、カラム単位のリネージにも対応しています。レシピで include_column_lineage: true を指定すると、「customers.customer_idstg_customers.customer_id から来ている」というカラム同士のつながりまで記録されます。

カラムレベルリネージは「この KPI カラム、大元をたどるとどのソースカラムなんだっけ?」を追跡するのに強力です。OpenMetadata もカラムレベルリネージに対応していますが(v0.11 以降)、DataHub は dbt や各種 SQL から自動でカラムリネージを生成する仕組みが充実しています。

ガバナンス機能

ここからは「データを整理・統制する」ためのガバナンス機能を紹介します。DataHub はこの領域がかなり厚いです。

Ownership(オーナーシップ)

各エンティティにオーナーを割り当てられます。「Technical Owner(技術的な責任者)」「Business Owner(業務的な責任者)」「Data Steward(データの番人)」など、オーナーの種類も指定できます。「このテーブル誰が管理してるの?」が明確になり、障害時の連絡先にもなります。

Tags(タグ)

PIIdeprecatedgold のような自由なラベルを付けられます。タグは検索フィルタにもなるので、「PII タグの付いたテーブルを全部出す」といった横断的な確認ができます。前述の meta_mapping を使えば、dbt の meta から自動でタグを付けることも可能です。実際に customersPII タグを付けた例は、このあとの「実際に付けてみる」のスクリーンショットで確認できます。

Business Glossary(ビジネス用語集)

タグが「自由なラベル」なのに対し、Glossary(用語集)は構造化された語彙です。「顧客生涯価値(LTV)」のような業務用語を定義し、それを関連するカラムやテーブルに紐付けられます。

  • Glossary Term(用語): 個々の用語の定義
  • Term Group(用語グループ): 用語を分類する階層

実際に試してみましょう。用語集は YAML ファイルで定義して、CLI から取り込めます。jaffle_shop 向けに3つの業務用語を定義してみます。

business_glossary.yml
version: 1
source: DataHub
owners:
  users:
    - datahub
nodes:
  - name: Jaffle Shop 用語集
    description: jaffle_shop プロジェクトで使うビジネス用語の定義集
    terms:
      - name: 顧客生涯価値
        description: ある顧客が取引を開始してから生涯にわたって自社にもたらす売上の総額。
        term_source: INTERNAL
      - name: アクティブ顧客
        description: 直近で1回以上注文を行った顧客。
        term_source: INTERNAL
      - name: 注文明細
        description: ひとつの注文に含まれる商品単位の明細行。
        term_source: INTERNAL

datahub-business-glossary ソースとして取り込みます。

Terminal
datahub ingest -c glossary_recipe.yml

取り込み後、左メニューの本のアイコン(または /glossary)を開くと、用語集が登録されています。

Business Glossary の一覧

「Jaffle Shop 用語集」という用語グループの中に、3つの用語が入っているのが確認できます。顧客生涯価値 の用語ページを開くと、説明文と、その用語が紐付いているアセットが表示されます。

顧客生涯価値の用語ページ

中央の「Assets」カードに customers が表示されているのは、この用語を customers モデルに紐付けたためです(紐付けの手順は次のセクションで説明します)。用語集を整備すると、「このカラムは社内で言うところの “顧客生涯価値” のことだ」というビジネス的な意味をデータに与えられます。エンジニアと事業側で言葉の認識を揃えるのに効きます。

Domains(ドメイン)と Data Products(データプロダクト)

  • Domain: データを業務領域(Marketing、Finance、Sales……)で束ねる概念。データメッシュの「ドメイン」に対応します。
  • Data Product: ドメインの中で、特定の目的のために提供されるデータのまとまり。「このダッシュボード群とテーブル群でひとつのプロダクト」という単位で管理できます。

ダッシュボードの Summary パネルに「Domain」「Data Product」の欄があったのは、この機能のためです。データメッシュ的な組織運用を志向するなら、この2つは重要になってきます。

Structured Properties(構造化プロパティ)

タグやカスタムプロパティより一歩進んで、型付きの独自属性をエンティティに定義できます。「retention_days は整数」「data_tier は gold/silver/bronze のいずれか」のように、値の型や選択肢を制約したメタデータを持たせられます。前述した「メタデータモデルを拡張できる」という DataHub の強みが、UI から手軽に使える形になったものです。

実際に「データ階層(data_tier)」という構造化プロパティを、Gold / Silver / Bronze の3択で定義して customersGold を付けてみました。customers の Properties タブを開くと、いちばん上に表示されます。

Structured Property(データ階層: Gold)

データ階層: Gold が、dbt 由来のカスタムプロパティ(node_typedbt_file_pathmanifest_adapter: postgres など)と並んで表示されています。カスタムプロパティが「dbt から取り込んだ何でもありの文字列」なのに対し、構造化プロパティは選択肢や型を縛れるぶん、組織のルールとして運用しやすいのが違いです。

OpenMetadata にもタグ・用語集・ドメイン・チームといったガバナンス機能はあり、PII の自動分類(Auto Classification)や Tier(重要度)といった独自の機能もあります。一方 DataHub は Structured Properties によるモデル拡張や、次に述べる Actions による自動化で、ガバナンスを「仕組み化」する方向に強いという印象です。

実際に付けてみる — タグ・ドメイン・オーナー・用語

ここまで紹介したガバナンス情報を、実際に customers モデルに付けてみましょう。UI からポチポチ付けることもできますが、せっかくなので前述の GraphQL API でまとめて付与してみます。

Terminal
# タグ PII とドメイン Marketing を作成
mutation { createTag(input:{id:"PII", name:"PII"}) }
mutation { createDomain(input:{id:"Marketing", name:"Marketing"}) }

# customers にタグ・ドメイン・オーナー・用語を付与
mutation { addTags(input:{resourceUrn:"<customers urn>", tagUrns:["urn:li:tag:PII"]}) }
mutation { setDomain(entityUrn:"<customers urn>", domainUrn:"urn:li:domain:Marketing") }
mutation { addOwners(input:{resourceUrn:"<customers urn>",
  owners:[{ownerUrn:"urn:li:corpuser:datahub", ownerEntityType:CORP_USER, type:TECHNICAL_OWNER}]}) }
mutation { addTerm(input:{resourceUrn:"<customers urn>", termUrn:"<顧客生涯価値 urn>"}) }

付与後に customers の詳細ページを開くと、右側の Summary パネルが一気ににぎやかになります。

customers に付与したガバナンス情報

  • Owners: Technical Owners に DataHub ユーザー
  • Domain: Marketing
  • Tags: PII
  • Terms: 顧客生涯価値

の4つが、1つのパネルにまとまって表示されました。左側のカラム一覧にも lifetime_spendcount_lifetime_orders といったカラムが説明付きで並んでいます(これは customers のセマンティックモデル側の定義です)。「このテーブルは誰が持っていて、どの業務領域の、どういう意味のデータで、PII を含むのか」が一目で把握できる状態になりました。これがガバナンス機能の効きどころです。

データ品質・オブザーバビリティ

Assertions(アサーション)と Data Contracts

DataHub では、データ品質のルールを Assertion(アサーション) として表現します。「このカラムは NULL であってはならない」「行数は前日比 ±10% 以内」といった検証ルールです。アサーションの結果は前述の Quality タブに集約されます。

さらに、複数のアサーションをまとめて「このテーブルはこの品質基準を満たす」という約束にしたものが Data Contract(データ契約) です。データの提供側と利用側で品質の合意を取り、それをカタログ上で可視化できます。

dbt test / Great Expectations 連携

ここが OpenMetadata との設計の分かれ目です。前述のとおり DataHub OSS はネイティブのテスト実行エンジンを持ちません。代わりに、

  • dbt test の結果run_results.json)を取り込んでアサーションとして表示
  • Great Expectations の検証結果を専用インテグレーションで取り込み

という形で、既存のテスト資産を DataHub に集約します。「テストの実行は dbt / GE に任せ、結果の可視化と管理は DataHub に集約する」という分業です。すでに dbt test を書いているチームなら、その結果がそのままカタログに乗るので相性が良いです。

実際に試してみましょう。jaffle_shop には not_nullrelationships(参照整合性)といった dbt test が定義されています。dbt build(または dbt test)を実行すると、その結果が target/run_results.json に記録されます。これを取り込みレシピの run_results_paths に指定して再取り込みします。

datahub_recipe.yml(test結果込み)
source:
  type: dbt
  config:
    manifest_path: ./target/manifest.json
    run_results_paths:
      - ./target/run_results.json
    target_platform: postgres
sink:
  type: datahub-rest
  config:
    server: "http://localhost:8080"

再取り込みすると、dbt test が Assertion(アサーション) として登録されます。orders の Quality タブを開いてみましょう。

orders の Quality(アサーション)タブ

「Unique value proportion for column order_id is equal to 1(order_id の一意性)」「Column customer_id values are passing assertion … ref_stg_customers(stg_customers への参照整合性)」といった dbt test が、それぞれ緑のチェック(Passing)付きでアサーションとして並んでいます。6 Passing とあるように、6 件のテストがすべて通過している状態が一目でわかります。View Logic を押すとテストの SQL も確認できます。

dbt 側で書いたテストが、追加の作業なしでそのままデータカタログ上の品質ダッシュボードになる——これが DataHub のデータ品質連携の気持ちよさです。

一方 OpenMetadata はツール単体でテストの定義から実行までできる(約30種類の組み込みテスト+プロファイラ)ので、「外部ツールを使わず1つで完結させたい」なら OpenMetadata が手軽です。ここは思想がはっきり違うので、チームの既存資産で選ぶと良いと思います。

Incidents(インシデント)

データセットに対してインシデント(障害)を起票・管理できます。「このテーブルのデータが壊れている」という状態を記録し、解決までのステータスを追えます。試しに orders に「更新が遅延している」というインシデントを起票してみました。

orders の Incidents タブ

「orders の更新が遅延」というインシデントが Operational カテゴリで起票され、Resolve ボタンで解決済みにできる状態になっています。インシデントが起きているアセットは、検索結果や詳細ページのタイトル横に赤いマークが付くので(前の検索結果スクショの orders にも付いていました)、「いま問題が起きているテーブル」がひと目でわかります。アサーションの失敗からインシデントを自動生成する、といった連携も可能です。データのオブザーバビリティ(観測可能性)を、カタログと同じ場所で扱えるのが DataHub の思想です。

API・SDK・拡張性

DataHub は「UI から触るカタログ」であると同時に、「プログラムから叩くメタデータ基盤」でもあります。API が3系統あるのが特徴です。

GraphQL API

UI が裏で使っているのと同じ GraphQL API を、そのまま自分でも叩けます。たとえば customers の上流リネージを取得するならこんな感じです。

Terminal
curl -s -X POST "http://localhost:8080/api/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic <base64(datahub:datahub)>" \
  -d '{"query":"{ dataset(urn:\"urn:li:dataset:(urn:li:dataPlatform:dbt,analytics.jaffle_shop.customers,PROD)\"){ lineage(input:{direction:UPSTREAM,count:10}){ relationships{ entity{ urn type } } } } }"}'

実際に叩くと、stg_customersorders が上流として返ってきます。

{
  "data": { "dataset": { "lineage": { "relationships": [
    { "entity": { "urn": "...stg_customers...", "type": "DATASET" } },
    { "entity": { "urn": "...orders...",        "type": "DATASET" } }
  ] } } }
}

リネージ・検索・タグ付与など、UI でできる操作はほぼすべて GraphQL で自動化できます。

REST(OpenAPI)と Python SDK

GMS はポート 8080 で OpenAPI 準拠の REST も公開しています。さらに Python SDK(DataHubGraph クライアントや各種 emitter)を使えば、Python から型安全にメタデータを読み書きできます。

Python SDKでタグを付ける例
from datahub.emitter.rest_emitter import DatahubRestEmitter
from datahub.emitter.mcp import MetadataChangeProposalWrapper
from datahub.metadata.schema_classes import GlobalTagsClass, TagAssociationClass

emitter = DatahubRestEmitter("http://localhost:8080")
mcp = MetadataChangeProposalWrapper(
    entityUrn="urn:li:dataset:(urn:li:dataPlatform:dbt,analytics.jaffle_shop.customers,PROD)",
    aspect=GlobalTagsClass(tags=[TagAssociationClass(tag="urn:li:tag:gold")]),
)
emitter.emit(mcp)

OpenMetadata も REST API と Python SDK を備えていますが、DataHub は GraphQL という “読み取り・横断クエリに強い” API が加わっているのが大きな違いです。「カタログの情報を社内の別ツールに表示したい」といったときに GraphQL は重宝します。

Kafka emitter と Actions Framework

前述の datahub-kafka sink に加え、Actions Framework が DataHub のリアルタイム性を象徴する機能です。Kafka に流れるメタデータ変更イベント(MCL)を購読して、「タグが付与されたら Slack に通知する」「オーナーが変わったら社内システムを更新する」といったイベント駆動の自動化を組めます。

OpenMetadata にもアラート・通知の仕組みはありますが、DataHub の Actions は「メタデータの変更そのものをイベントストリームとして扱える」点でより低レベルかつ柔軟です。これは Kafka を中核に据えたアーキテクチャだからこそできることです。

アクセス制御と本番運用

最後に、本番投入を見据えた話を少しだけ。

Policies と Roles

DataHub のアクセス制御は Policy(ポリシー) ベースです。ポリシーは2種類あります。

  • Platform Policy: 「ユーザー管理ができる」「ポリシーを編集できる」といった、プラットフォーム全体の操作権限
  • Metadata Policy: 「特定ドメインのタグを編集できる」「このプラットフォームのデータセットのオーナーを変更できる」といった、メタデータに対する細かい権限

これらをまとめた Role(Admin / Editor / Reader) も用意されていて、まずはロールで大枠を決め、必要に応じてポリシーで細かく調整、という運用ができます。

SSO(OIDC)

ログイン画面にあった「Sign in with SSO」は、OIDC による SSO 連携のためのボタンです。Google、Okta、Azure AD などと連携でき、本番では社内の認証基盤に統合するのが一般的です。

本番デプロイ

ローカルは datahub docker quickstart で十分ですが、本番は Helm チャートによる Kubernetes デプロイが公式に提供されています。GMS・Frontend・Actions をスケールさせ、MySQL は RDS、検索は OpenSearch のマネージドサービス、Kafka は MSK……といった具合に、各コンポーネントをマネージドサービスに置き換えていくのが定番構成です。

OpenMetadata との違いまとめ

ここまでの内容を表で整理します。あくまで「dbt + PostgreSQL を題材に触ってみた個人の感想」ベースなので、選定の参考程度に見てください。

観点 OpenMetadata DataHub
アーキテクチャ MySQL/PG + Elasticsearch(シンプル) + Kafka のイベント駆動(リッチ)
メタデータモデル JSON Schema ベース PDL ベース・拡張性が高い
取り込み方式 コネクタで実 DB 接続(CLI / Airflow) push / pull 両対応、CLI / UI / Kafka
取り込みの起点 実 DB を introspect → dbt を重ねる dbt の manifest 起点・コマンド一発
データ品質テスト ネイティブ内蔵(約30種+プロファイラ) 外部(dbt test / GE)連携で集約
リネージ テーブル・カラムレベル対応 テーブル・カラムレベル対応+Impact Analysis
dbt 連携の特徴 物理テーブルにマージ Siblings で論理/物理を両立
API REST REST / GraphQL / Kafka
自動化 アラート・通知 Actions Framework(イベント駆動)
必要メモリ(ローカル) 6GB 8GB

ざっくりまとめると、

  • OpenMetadata が向くケース: シンプルな構成で始めたい。データ品質テストやプロファイリングをツール1つで完結させたい。
  • DataHub が向くケース: dbt + ウェアハウス構成。メタデータをプログラムから扱いたい(GraphQL / Kafka)。将来的にメタデータモデルを拡張したい。リアルタイム連携や自動化を組みたい。

という住み分けかなと思います。

まとめ

今回は jaffle_shop を使って DataHub を実践し、ついでに機能をひととおり紹介してみました。

DataHub は manifest.json を読み込む方式なので実データベースへの接続が不要で、datahub ingest コマンド一発で取り込みが完了したのは、体験としてとてもスムーズでした。target_platform を変えるだけで PostgreSQL でも BigQuery でも同じ要領で動くのも気が利いています。実 DB にコネクタで接続してプロファイリングまで取る OpenMetadata とは、ちょうど対照的なアプローチです。

そして触ってみてわかったのは、DataHub はカタログ・リネージだけのツールではないということです。Entity / Aspect / URN という拡張性の高いメタデータモデルを土台に、検索・ドキュメント・ガバナンス(タグ・用語集・ドメイン・データプロダクト)・データ品質(アサーション・インシデント)・API(REST / GraphQL / Kafka)・アクセス制御まで、データ基盤の「メタデータまわり」を丸ごと引き受けようとしている——そんな思想を感じました。

一方で、データ品質テストやプロファイリングをツール単体で完結させたいなら OpenMetadata の方が手軽、という違いもはっきりしました。どちらも良いツールなので、自分たちのデータ基盤(特に dbt をどれくらい中心に据えているか)と、既存のテスト資産を踏まえて選ぶのが良さそうです。

まずはローカルで両方触ってみて、肌に合う方を本番データに広げていく、というのが個人的なおすすめです。この記事がその一歩の参考になれば嬉しいです。

https://datahubproject.io/
https://docs.datahub.com/docs/metadata-ingestion/integration_docs/dbt

Discussion