Netflix社製のメタデータ管理OSS「Metacat」を試してみた

2021.09.14

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

データ・アナリティクス事業本部の森脇です。

データカタログ/メタデータ管理のためのOSSは色々ありますが、今回はNetflixが現在進行形で開発しているOSSの「Metacat」を試してみました。

Metacatとは

Metacatとは、Netflix OSSとして開発されているメタデータ管理サービスです。

REST/Thriftインターフェースを持っており、様々なデータソースに対して統一されたインターフェースでメタデータを参照することが可能です。

https://netflixtechblog.com/metacat-making-big-data-discoverable-and-meaningful-at-netflix-56fb36a53520より引用

Metacatには以下の特徴があります。

  • APIのみ提供(Web UIは存在しない)
  • Java/Groovy製
  • ビジネス要件/ユーザー定義メタデータを保存可能
  • データディスカバリー
  • データ変更の監視/通知機能

最新の安定版(1.2.2)で、以下のコネクターが用意されています。

  • cassandra
  • druid
  • hive
  • jdbc
  • mysql
  • pig
  • postgresql
  • redshift
  • s3
  • snowflake

こちら」のようなプロパティファイルを作成し、接続情報などを設定を行うことでデータソースとの連携が可能です。

試してみる

デモ用に用意されているDockerコンテナ環境で動作を確認してみます。

この環境にはMetacat本体の他にpostgresql, mysql, hive metastoreなどのデモ用データソースが含まれおり、起動後すぐに動作を確認することが可能です。

事前準備

事前に以下を用意しました。

java

build.gradleを確認すると、対応しているJavaのバージョンは8です。

そのため、Amazon Corretto 8をインストールしました。

$ java -version

openjdk version "1.8.0_302"
OpenJDK Runtime Environment Corretto-8.302.08.1 (build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM Corretto-8.302.08.1 (build 25.302-b08, mixed mode)

docker-compose

docke/docker-composeをインストールします。

$ docker --version
Docker version 20.10.7, build f0df350

$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

動作検証

Metacatのソースコードをcloneし、gradleコマンドでコンテナを起動します。

最新バージョンの1.3.0はRCを繰り返しており少し不安だったので、1つ前のバージョンである1.2.2を利用します。

$ git clone https://github.com/Netflix/metacat.git -b v1.2.2
$ cd metacat
$ ./gradlew metacatPorts
Download http://repo.spring.io/milestone/com/netflix/nebula/gradle-info-plugin/maven-metadata.xml

> Configure project : 
Inferred project: metacat, version: 1.3.0-SNAPSHOT
org.ajoberstar.github-pages is deprecated will be removed in gradle-git 2.0.0. Users should migrate to org.ajoberstar.git-publish (https://github.com/ajoberstar/gradle-git-publish).

Publication nebula not found in project :.
[buildinfo] Not using buildInfo properties file for this build.
Publication named 'nebula' does not exist for project ':' in task ':artifactoryPublish'.

.
.
.

Creating network "metacat-test-cluster_default" with the default driver
Creating metacat-test-cluster_hive-metastore-db_1 ... 
Creating metacat-test-cluster_postgresql_1        ... 
Creating metacat-test-cluster_hive-metastore-db_1 ... done
Creating metacat-test-cluster_postgresql_1        ... done
Creating metacat-test-cluster_storage-barrier_1   ... 
Creating metacat-test-cluster_storage-barrier_1   ... done
Attaching to metacat-test-cluster_storage-barrier_1
storage-barrier_1    | Waiting for postgresql:5432  ......  up!
storage-barrier_1    | Waiting for hive-metastore-db:3306  ......  up!
storage-barrier_1    | Everything is uptCluster
metacat-test-cluster_storage-barrier_1 exited with code 0
> :m+ '[' 0 -ne 0 ']'-tests:startMetacatCluster
metacat-test-cluster_hive-metastore-db_1 is up-to-date
Creating metacat-test-cluster_hive-metastore_1 ... 
Creating metacat-test-cluster_hive-metastore_1 ... done
Creating metacat-test-cluster_service-barrier_1 ... 
Creating metacat-test-cluster_service-barrier_1 ... done
Attaching to metacat-test-cluster_service-barrier_1
service-barrier_1    | Waiting for hive-metastore:9083  ...  up!
service-barrier_1    | Everything is up
metacat-test-cluster_service-barrier_1 exited with code 0
> :m+ '[' 0 -ne 0 ']'-tests:startMetacatCluster
metacat-test-cluster_hive-metastore-db_1 is up-to-date
metacat-test-cluster_postgresql_1 is up-to-date
metacat-test-cluster_hive-metastore_1 is up-to-date
Creating metacat-test-cluster_metacat_1 ... 
Creating metacat-test-cluster_metacat_1 ... done
Creating metacat-test-cluster_metacat-barrier_1 ... 
Creating metacat-test-cluster_metacat-barrier_1 ... done
Attaching to metacat-test-cluster_metacat-barrier_1
metacat-barrier_1    | Waiting for metacat:8080  ..  up!
metacat-barrier_1    | Waiting for metacat:12001  ........................................................................................................................  up!
metacat-barrier_1    | Waiting for metacat:12003  .  up!
metacat-barrier_1    | Waiting for metacat:12004  .  up!
metacat-barrier_1    | Everything is up
metacat-test-cluster_metacat-barrier_1 exited with code 0
> :m+ '[' 0 -ne 0 ']'-tests:startMetacatCluster

> Task :metacat-functional-tests:metacatPorts 
++ Container Exposed ports ++
+++ Metacat Web Port:62571
+++ Metacat Web Debug Port:62570
+++ Metacat Hive Thrift Port:62567
+++ Metacat Embedded Hive Thrift Port:62568
+++ Metacat Embedded Fast Hive Thrift Port:62569
+++ Metacat Hive Metastore Port:62564
+++ Metacat Hive Metastore Debug Port:62565


BUILD SUCCESSFUL in 4m 6s
77 actionable tasks: 77 executed

起動が正常に完了すると、ログの最後にポート番号が表示されます。

++ Container Exposed ports ++
+++ Metacat Web Port:62571
+++ Metacat Web Debug Port:62570
+++ Metacat Hive Thrift Port:62567
+++ Metacat Embedded Hive Thrift Port:62568
+++ Metacat Embedded Fast Hive Thrift Port:62569
+++ Metacat Hive Metastore Port:62564
+++ Metacat Hive Metastore Debug Port:62565

これらがMetacatのエンドポイントです。

docker psの結果はこんな感じでした。

$ docker ps
CONTAINER ID   IMAGE                                             COMMAND                  CREATED         STATUS         PORTS                                                                                                                                                                                  NAMES
32cf33605c6a   tomcat:8.0-jre8                                   "catalina.sh run"        3 minutes ago   Up 3 minutes   0.0.0.0:62570->8000/tcp, 0.0.0.0:62571->8080/tcp, 0.0.0.0:62567->12001/tcp, 0.0.0.0:62568->12003/tcp, 0.0.0.0:62569->12004/tcp                                                         metacat-test-cluster_metacat_1
e95919639c93   danielbwatson/metacat-test-hive-metastore:1.0.0   "hive --service meta…"   3 minutes ago   Up 3 minutes   2122/tcp, 8030-8033/tcp, 8040/tcp, 8042/tcp, 8088/tcp, 19888/tcp, 49707/tcp, 50010/tcp, 50020/tcp, 50070/tcp, 50075/tcp, 50090/tcp, 0.0.0.0:62565->8005/tcp, 0.0.0.0:62564->9083/tcp   metacat-test-cluster_hive-metastore_1
02df342f586c   postgres:9.6                                      "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes   5432/tcp                                                                                                                                                                               metacat-test-cluster_postgresql_1
e3abd858a2eb   mysql:5.6                                         "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes   0.0.0.0:62556->3306/tcp                                                                                                                                                                metacat-test-cluster_hive-metastore-db_1

http://localhost:${Web Port}/swagger-ui.htmlにWebブラウザでアクセスしてみます。

Swagger UIを使って、APIの確認/実行が可能です。

いくつかAPIを試してみます。(curlで試しています)

カタログ一覧の取得

$ curl -s -X GET 'http://localhost:62571/mds/v1/catalog' | jq .
[
  {
    "catalogName": "embedded-hive-metastore",
    "connectorName": "hive"
  },
  {
    "catalogName": "postgresql-96-db",
    "connectorName": "postgresql"
  },
  {
    "catalogName": "hive-metastore",
    "connectorName": "hive"
  },
  {
    "catalogName": "embedded-fast-hive-metastore",
    "connectorName": "hive"
  },
  {
    "catalogName": "mysql-56-db",
    "connectorName": "mysql"
  },
  {
    "catalogName": "s3-mysql-db",
    "connectorName": "s3"
  }
]

カタログ内の情報を取得

$ curl -s -X GET 'http://localhost:62571/mds/v1/catalog/mysql-56-db' | jq .
{
  "databases": [
    "fasthive",
    "hive",
    "metacat",
    "performance_schema",
    "s3",
    "sakila",
    "shardfasthive",
    "world"
  ],
  "definitionMetadata": null,
  "name": {
    "catalogName": "mysql-56-db",
    "qualifiedName": "mysql-56-db"
  },
  "type": "mysql"
}

カタログ配下のデータベース一覧が取得できました。  

データベース配下の情報を取得

$ curl -s -X GET 'http://localhost:62571/mds/v1/catalog/mysql-56-db/database/world' | jq .
{
  "dateCreated": null,
  "definitionMetadata": null,
  "lastUpdated": null,
  "name": {
    "catalogName": "mysql-56-db",
    "databaseName": "world",
    "qualifiedName": "mysql-56-db/world"
  },
  "tables": [
    "city",
    "country",
    "countrylanguage"
  ],
  "type": "mysql",
  "metadata": null,
  "uri": null
}

自身の情報、及びデータベースに存在するテーブル一覧が取得できました。

テーブルの情報を取得

$ curl -s -X GET 'http://localhost:62571/mds/v1/catalog/mysql-56-db/database/world/table/city' | jq .
{
  "audit": {
    "createdBy": null,
    "createdDate": 1631491200000,
    "lastModifiedBy": null,
    "lastModifiedDate": null
  },
  "dataMetadata": null,
  "definitionMetadata": null,
  "fields": [
    {
      "comment": "",
      "name": "ID",
      "partition_key": false,
      "pos": 0,
      "source_type": "INT(10, 0)",
      "type": "int",
      "isNullable": false,
      "size": 10,
      "defaultValue": null,
      "isSortKey": null,
      "isIndexKey": null
    },
    {
      "comment": "",
      "name": "Name",
      "partition_key": false,
      "pos": 1,
      "source_type": "CHAR(35)",
      "type": "chararray",
      "isNullable": false,
      "size": 35,
      "defaultValue": "",
      "isSortKey": null,
      "isIndexKey": null
    },
    {
      "comment": "",
      "name": "countryCode",
      "partition_key": false,
      "pos": 2,
      "source_type": "CHAR(3)",
      "type": "chararray",
      "isNullable": false,
      "size": 3,
      "defaultValue": "",
      "isSortKey": null,
      "isIndexKey": null
    },
    {
      "comment": "",
      "name": "District",
      "partition_key": false,
      "pos": 3,
      "source_type": "CHAR(20)",
      "type": "chararray",
      "isNullable": false,
      "size": 20,
      "defaultValue": "",
      "isSortKey": null,
      "isIndexKey": null
    },
    {
      "comment": "",
      "name": "Population",
      "partition_key": false,
      "pos": 4,
      "source_type": "INT(10, 0)",
      "type": "int",
      "isNullable": false,
      "size": 10,
      "defaultValue": "0",
      "isSortKey": null,
      "isIndexKey": null
    }
  ],
  "metadata": null,
  "name": {
    "catalogName": "mysql-56-db",
    "databaseName": "world",
    "qualifiedName": "mysql-56-db/world/city",
    "tableName": "city"
  },
  "serde": null,
  "view": null,
  "partition_keys": [],
  "dataExternal": false
}

テーブルの作成時刻/スキーマを確認することができました。

まとめ

今回はMetacatのほんの触りの部分を試してみました。

REST APIインターフェースだけでなくThriftインターフェースも提供することで、Spark/Prestとの連携も容易に行えそうです。

他にもテーブルの更新/削除、ユーザータグの設定、パーティション操作などの様々な機能が提供されていました。

WebUI自体は無いため、データカタログシステムのバックエンドとして使うと便利かなと感じました。