LinkedIn製のOSSデータカタログ「DataHub」の概要とチュートリアル

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも!DA部の春田です。

先日までre:Invent2020のAnalytics系のセッションレポートを書いていたのですが、海外企業のほとんどがデータポータルなるものを構築・稼働させていた点が印象的でした。このデータポータルを構成する要素の中でも、最近データカタログというサービスが注目を集めており、まだ成熟しきっていない分野ですが、探してみると新興OSSが結構見つかるんですよね。

さて、その中でも今回はLinkedIn製のOSSデータカタログ、DataHubについてご紹介していきたいと思います。

DataHubとは?

DataHubは一言で言うと、データソースのメタデータの検索とディスカバリーを実現するツールです。LinkedIn社の長年のメタデータ管理の経験の末、設計思想として以下の5点が掲げられています。

DataHub: A generalized metadata search & discovery tool | LinkedIn Engineering

1. Push is better than pull

  • それぞれのデータ・プロバイダーが、各自でメタデータを中央のリポジトリへPushする方式を採用
  • データソースを直接クローラーで解析する方式(=Pull)は、開発・メンテナンスコストが高い
  • Pushベースはスケーラブルで、リアルタイムにメタデータを更新・反映できる

2. General is better than specific

  • 連携するデータベースやシステムのそれぞれの仕様に合わせず、普遍的なメタデータ・モデルを追求する
  • 普遍的なメタデータ・モデルを作成しておけば、それの拡張だけに集中することができる

3. Online is as important as offline

  • データにはオフラインで済むデータと、オンラインである必要があるデータの2種類
  • オフラインデータはHadoopのようなシステムに格納できる
  • 対して、アクセス権限やプライバシーといったデータは、最新のメタデータである必要があり、オンラインが求められる

4. Relationships really matter

  • メタデータは、リネージや所有者、依存関係といった、重要なリレーションを表現する
  • これらのデータは、影響分析やデータ要約、検索機能などに活用できる
  • リレーションのモデル化を最優先し、分析の効率化を支援することが重要

5. Multi-center universe

  • メタデータを一つのデータセットにまとめるだけでは不十分
  • データ、ソースコード、人材といったエコシステム全体を統合して、一つのメタデータグラフとして表現する必要がある

おおよそ、データ分析に携わる人にとっては「確かに」という感想を抱くのではないでしょうか?

DataHubは複数のコンテナで構成されており、KafkaやNeo4j、ElasticSearchといったオープンソースのツールが上手く組み込まれています。

LinkedIn社内で稼働しているDataHubから、汎用的なコア機能をスピンアウトしたOSSであるため、特定の機能についてはユーザーやデベロッパーのの要望に合わせて拡張していくとのことです。詳しくは下記事の表をご覧ください。

Open Sourcing DataHub: LinkedIn’s Metadata Search and Discovery Platform | LinkedIn Engineering

その実力はいかに…?早速セットアップしてみました!

簡単にセットアップしてみる

セットアップはこちらのDataHub Quickstart Guideに沿って行います。Docker環境があれば動くので、今回はローカルのMac OSで立ち上げてみました。

Docker環境はすでに準備てきているとします。インストールされてない方は、ググったらいっぱい出てくると思うのでそちらをご参照ください。

まず、必要なスペックを確保します。現在はTested & confirmed config: 2 CPUs, 8GB RAM, 2GB Swap areaが必要とされているので、Docker Desktopの設定を以下のように変更しておきます。

次に、CLIで任意のディレクトリに移動し、DataHubのGitリポジトリをcloneしてquickstart.shを実行します。quickstart.shではdocker-composeがフォアグラウンドで実行されるので、必要であればdocker-compose -p datahub up -dと、-dオプションを付与しておいてください。

$ git clone https://github.com/linkedin/datahub.git
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 50805 (delta 0), reused 3 (delta 0), pack-reused 50800
Receiving objects: 100% (50805/50805), 41.66 MiB | 6.28 MiB/s, done.
Resolving deltas: 100% (27048/27048), done.
$ cd ./datahub/
$ ./docker/quickstart.sh

コンテナが立ち上がったかどうかを判断するために、docker logsで主要サービスを確認しておきます。docker logs datahub-gmsでは、

2020-02-06 09:20:54.870:INFO:oejs.Server:main: Started @18807ms

docker logs datahub-frontendでは、

09:20:22 [main] INFO  play.core.server.AkkaHttpServer - Listening for HTTP on /0.0.0.0:9001

のようなログが確認できればOKです。http://localhost:9001 からUIに入りましょう。デフォルトのユーザー名とパスワードはdatahubです。

ログイン確認はできましたが、データは何も入っていません。DataHubでは親切なことにサンプルデータも用意されているので、こちらもロードしておきましょう。ingestion.shを実行するだけでOKです。

$ ./docker/ingestion/ingestion.sh                                                                                                              2815ms  2021-01-19 09:59:51
WARNING: Native build is an experimental feature and could change at any time
WARNING: Found orphan containers (schema-registry, datahub-gms, kafka-setup, datahub-mce-consumer, datahub-mae-consumer, broker, elasticsearch-setup, zookeeper, kafka-topics-ui, elasticsearch, schema-registry-ui, neo4j, kafka-rest-proxy, kibana, mysql, datahub-frontend) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
Building ingestion
[+] Building 213.1s (12/12) FINISHED                                                                                                                                                                                
 => [internal] load build definition from Dockerfile                                                                                                                                                           0.0s
 => => transferring dockerfile: 37B                                                                                                                                                                            0.0s
 => [internal] load .dockerignore                                                                                                                                                                              0.0s
 => => transferring context: 35B                                                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/library/openjdk:8                                                                                                                                                   2.9s
 => [internal] load metadata for docker.io/library/openjdk:8-jre-alpine                                                                                                                                        2.7s
 => [internal] load build context                                                                                                                                                                              0.6s
 => => transferring context: 390.20kB                                                                                                                                                                          0.5s
 => [prod-build 1/3] FROM docker.io/library/openjdk:8@sha256:b253b93dc528967eff64ade00a9079dc464fb75b9d27a7a578c8006ca0645de8                                                                                  0.0s
 => CACHED [base 1/1] FROM docker.io/library/openjdk:8-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193                                                                      0.0s
 => CACHED [prod-build 2/3] COPY . datahub-src                                                                                                                                                                 0.0s
 => [prod-build 3/3] RUN cd datahub-src && ./gradlew :metadata-ingestion-examples:mce-cli:build                                                                                                              208.6s
 => [prod-install 1/2] COPY --from=prod-build datahub-src/metadata-ingestion-examples/mce-cli/build/libs/mce-cli.jar /datahub/ingestion/bin/mce-cli.jar                                                        0.2s 
 => [prod-install 2/2] COPY --from=prod-build datahub-src/metadata-ingestion-examples/mce-cli/example-bootstrap.json /datahub/ingestion/example-bootstrap.json                                                 0.1s 
 => exporting to image                                                                                                                                                                                         0.4s 
 => => exporting layers                                                                                                                                                                                        0.4s 
 => => writing image sha256:f9c455c29e8e51f59452328bcba782669805f7e191bbd70f39e285accb26ee45                                                                                                                   0.0s 
 => => naming to docker.io/library/datahub-ingestion                                                                                                                                                           0.0s 
Successfully built f9c455c29e8e51f59452328bcba782669805f7e191bbd70f39e285accb26ee45
Creating ingestion ... done
Attaching to ingestion
ingestion    | 
ingestion    |   .   ____          _            __ _ _
ingestion    |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
ingestion    | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
ingestion    |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
ingestion    |   '  |____| .__|_| |_|_| |_\__, | / / / /
ingestion    |  =========|_|==============|___/=/_/_/_/
ingestion    |  :: Spring Boot ::        (v2.1.2.RELEASE)
ingestion    | 
ingestion    | SLF4J: Class path contains multiple SLF4J bindings.
ingestion    | SLF4J: Found binding in [jar:file:/datahub/ingestion/bin/mce-cli.jar!/BOOT-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
ingestion    | SLF4J: Found binding in [jar:file:/datahub/ingestion/bin/mce-cli.jar!/BOOT-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
ingestion    | SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
ingestion    | SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
ingestion    | 10:09:57.735 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 1 of 6
ingestion    | 10:09:58.403 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.
ingestion    | 10:09:58.404 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 2 of 6
ingestion    | 10:09:58.411 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.
ingestion    | 10:09:58.412 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 3 of 6
ingestion    | 10:09:58.436 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.
ingestion    | 10:09:58.436 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 4 of 6
ingestion    | 10:09:58.440 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.
ingestion    | 10:09:58.441 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 5 of 6
ingestion    | 10:09:58.447 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.
ingestion    | 10:09:58.447 [main] INFO  c.l.metadata.examples.cli.MceCli - Producing record 6 of 6
ingestion    | 10:09:58.454 [main] INFO  c.l.metadata.examples.cli.MceCli - Produced record.

サンプルデータのロードが終われば、先ほどのUIにもデータが反映されていると思います。例えば、SampleHdfsDatasetRelationshipsの画面はこんな感じです。

スタートアップについては、100点満点の導入のしやすさでした。さて、これをどう使いこなしていくかが問題です。

メタデータの作成・ロード方法

現在の公式チュートリアルでは上記のサンプルデータのロードまでで、後は各自で必要な箇所を参照してくださいというDIYな感じになっています。機能が豊富で切り口が多いため、まずは先ほどロードしたサンプルデータから辿っていくことにします。

先ほどの元データは、example-bootstrap.jsonにあるJSONで、以下のMetadataChangeEvent (MCE)というイベントを発火するコンテナアプリmce-cliがJSONを指定の形式に変換した後、ロードする仕組みになっています。

datahub/mce-cli at master · linkedin/datahub 上記はJava製のアプリですが、同じようなデータロード用のスクリプトがPythonでもあったので、次はこちらを読み解いていきます。

datahub/mce_cli.py at master · linkedin/datahub

mce_cli.pyは、CLIからパラメータを指定して実行すればbootstrap_mce.datがロードされるようなので、datahub/metadata-ingestion at master · linkedin/datahubに沿って試しに実行してみました。

まず、MetadataChangeEventに使用するスキーマを作成しておきます。(※ここでCould not initialize class org.codehaus.groovy.reflection.ReflectionCacheのエラーが出る方は、ローカルのJDKのバージョンを11に下げて下さい。JDK14とgladleの5系が原因みたいです。)

cd ~/datahub/
./gradlew :metadata-events:mxe-schemas:build

ビルドが完了したら、必要なPythonライブラリをインストールします。Python環境を汚したくない方は、venv等で切っておいてください。

cd ./metadata-ingestion/mce-cli/
pip install -r requirements.txt

環境の準備はOKです。mce_cli.pyproduceを実行させましょう。

python mce_cli.py produce

mce_cli.py実行前後の状態でわかりやすい差分は、SampleKafkaDatasetDataset Propertiesに以下のようなタグがあるかないかです。

以上からわかることは、PythonやJavaなどのKafkaに対応しているプログラミング言語を噛ませれば、どんなDBのメタデータでも格納できる、ということでしょうか。

さらにDataHubでは、SQLベースのDBであれば自動でスキーマ情報を取ってくるPythonスクリプトも用意されています。噛ませているSQLAlchemyが参照できるDBなら何でも可能、ということですね。

datahub/metadata-ingestion/sql-etl at master · linkedin/datahub

次のステップ

DataHubを今後使いこなしていくためには、少なくとも以下が必要なことがわかりました。

  1. DataHub独自のメタデータ・スキーマ構造の理解
    1. datahub/metadata-modelling.md at master · linkedin/datahub
  2. KafkaのConsumeとProduceを、DataHubがどう使用しているかの理解
    1. datahub/metadata-ingestion.md at master · linkedin/datahub
  3. DataHubが提供している専用のSDKやライブラリがないため、用途に合わせたコードをスクラッチで実装

当初はAPIベースでメタデータをやりとりできると聞いていたのですが、まだドキュメントが整理しきれておらず、気軽には使えなさそうなOSSでした。しかしながら、使いこなせたら厳格にメタデータを自動更新できるツールに化そうなので、個人的にもう少し掘り下げていけたらなと思います。