データ基盤のメタデータを継続的に管理できる仕組みを作る

こんにちは。MackerelチームでCRE(Customer Reliability Engineer)をしているid:syou6162です。

CREチームではカスタマーサクセスを進めるため、最近データ分析により力を入れています(参考1, 参考2)。データ分析を正確に行なうためには、データに関する正確な知識が必要です。今回はより正確なデータ分析を支えるためのメタデータを継続的に管理する仕組みについて書いてみます。

データに対する知識: メタデータ

データ分析を正確に行なうためには、データ自身に関する知識(=メタデータ)が必要です。例えば、Mackerelのデータ分析タスクでは以下のような知識が必要とされることが多いです。

  • このテーブル / カラムは何のためのテーブルなのか
  • 似たようなカラムとの違い
    • 集計条件の違い、など
  • データがどのような値を取り得るか
    • SELECT column, COUNT(*) FROM my_table GROUP BY column;でテーブル中に出てくる値を列挙できるが、たまたままだ出ていない値があるかもしれない
    • データだけ見てCASEで 分岐していると考慮漏れが起こる
  • XX年YY月以前は必ずNULLになっている

こういったメタデータはどこにあるでしょうか。アプリケーションのコード、GitHub 上のissueでのやり取り、wikiなどのキーワードページ、様々だと思います。アプリケーションエンジニアであれば、必要に応じてコードなどを参照し、歴史を紐解くことでメタデータを手にすることができるでしょう。

しかし、データ分析をするのはアプリケーションエンジニアではない(例: ディレクター / マーケター / CRE)ことのほうが多いです。そういった人たちがアプリケーションエンジニアと同様の調査をするのは非常に難しく、コストがかかるものになるでしょう。場合によっては意味を取り違えて、不正確なデータ分析が行なわれ、意思決定の精度が落ちる原因となるかもしれません。

メタデータは分析者がそういった情報をコストをかけず手に入れることができる手段の一つです。

メタデータを「継続的に」管理する必要性

Mackerelチームではデータ基盤にBigQueryを採用しており、BigQueryではデータセット / テーブル / カラムにdescriptionを書けます。このdescriptionにメタデータを保持しておけば、分析者がデータ分析を行なう際にすぐに参照できますし、BigQueryとData Catalogを連携させればメタデータに対する検索も自由にできます。

これで全て解決...してくれればよいのですが、なかなかそうはいきません。サービスは生き物であり、メタデータも日々変化していきます。この変化にデータ基盤側も追従できないと、メタデータが古びていってしまい、データ分析は不正確なものになっていくことが容易に起こりえます。

こういった理由で「継続的に」メタデータを管理するための仕組みが必要です。根性や気合で管理するのは継続的に管理するには向いていないので、「低コスト」にできる必要もあります。

メタデータを「継続的に」管理するための仕組み

その仕組みはどのように実現するのがよいでしょうか。私の場合、データマネジメントが30分でわかる本の著者の一人である@yuzutas0さんから頂いたアドバイスを実装してみることにしました。

コメント付きDDL、というのは例えば以下のものを指します(PostgreSQLの例)。

-- テーブルに対するコメント
COMMENT ON TABLE my_table IS '様々な利用状況レポート。毎月1日に集計されている。集計対象は有料プランユーザーのみ';

-- テーブルのカラムに対するコメント
COMMENT ON COLUMN my_table.my_column IS 'YYYの利用数。2019年ZZ月に追加されたため、それ以前はNULL。詳細はhttp://...を参照';

既存のテーブルやカラムについてはデータ分析でよく参照されるもの*1からid:syou6162が付与していくことにしました。新規に追加されるテーブル、アプリケーションの都合で意味が変わってしまうカラムなどのメタデータに関しては、アプリケーションエンジニアが追加 / 更新時に付与してもらうように相談しに行きました。

普段、分析者ほどはデータ分析をするわけではないアプリケーションエンジニアにとって、メタデータ管理は手間が増える作業です。作業をお願いするか迷いましたが

  • チームに最近入ったメンバーにとっても、DBにメタデータがあることは開発タスクへのオンボーディングを早める意味でも有用
    • キャッチアップする際にコードだけでなく、データも眺めながら意味を理解することも多いため
  • 長期的にはエンジニアへのデータ調査依頼を減らすことにも繋がる
  • 開発チームでも、SREなど普段アプリケーションのコードを触らない人もいるので、そういった人たちに向けても重要な情報を提供できる

ということもあったため、コメントDDLを付与していくのを普段の開発フローに組み入れてもらいました。付与を忘れないようにリポジトリ用のプルリクエストテンプレートにコメントDDLについての確認項目も追加しました。

RDBのスキーマ情報をtblsで簡単に取り扱う

DBに追加したメタデータですが、tblsを使うと簡単に取り扱うことができます。

tblsは対象となるテーブル名を指定すると、スキーマ関係の情報を例えばJSON形式で出力することができます。その出力をjqやスクリプトファイルで簡単な加工をした上でBigQueryのスキーマファイルとして渡してあげるだけでやりたいことはほぼ完成です。

set -u

CWD=$(cd $(dirname $0); pwd)

PROJECT=my-project
DATASET=my_dataset

RDS=my_rds_server.rds.amazonaws.com
RDS_USER=user
RDS_PASSWORD=password
RDS_DATABASE_NAME=my_database

JSON_FILES_DIR=${CWD}/.json_files_dir
mkdir -p $JSON_FILES_DIR 

# - RDSからBigQueryへのデータ転送はembulkで行なっている
# - メタデータの管理対象となるテーブル一覧の情報もembulkのファイル名から取得している
for table_name in $(ls ${CWD}/../embulk/embulk_*.yml.liquid | xargs -I% basename % .yml.liquid | sed -e 's/^embulk_//'); do
  BQ_RESULT=$(bq show --format json "${DATASET}.${table_name}")
  if [ $? -ne 0 ]; then
    echo "Table \"${table_name}\" does not exist on BigQuery. Skipped..."
    continue
  fi  
  echo ${table_name}
  echo $BQ_RESULT | jq -r .schema.fields > "${JSON_FILES_DIR}/bq_schema_${table_name}.json"

  RDS_RESULT=$(tbls out "postgres://${RDS_USER}:${RDS_PASSWORD}@${RDS}/${RDS_DATABASE_NAME}" --table ${table_name})
  echo $RDS_RESULT | jq -r .columns > "${JSON_FILES_DIR}/rds_schema_${table_name}.json"
  TABLE_DESCRIPTION=$(echo $RDS_RESULT | jq -r .comment)

  MERGED_SCHEMA_FILE=${JSON_FILES_DIR}/merged_schema_${table_name}.json
  python "${CWD}/_merge_rds_table_columns_metadata_and_bq_table_columns_metadata.py" "${JSON_FILES_DIR}/bq_schema_${table_name}.json" "${JSON_FILES_DIR}/rds_schema_${table_name}.json" | jq . > ${MERGED_SCHEMA_FILE}
  bq update --description "${TABLE_DESCRIPTION}" "${PROJECT}:${DATASET}.${table_name}" ${MERGED_SCHEMA_FILE}
done

補助用の_merge_rds_table_columns_metadata_and_bq_table_columns_metadata.pyはこんなスクリプトです。

import sys
import json

## RDS中のtableの各カラムにコメントが付与されていれば、BigQuery中のtableのカラムのdescriptionにも付与するための準備スクリプト
args = sys.argv

bq_schema_file = args[1]
rds_schema_file = args[2]

with open(bq_schema_file) as f:
    bq_schema = json.load(f)

with open(rds_schema_file) as f:
    rds_schema = json.load(f)

name2comment = {}
for column in rds_schema:
    name2comment[column["name"]] = column["comment"]

for column in bq_schema:
    column_name = column["name"]
    if (column_name in name2comment) and (name2comment[column_name] != ""):
        column["description"] = name2comment[column_name]

print(json.dumps(bq_schema))    

定期実行しているデータ転送のジョブの実行後にこのスクリプトも動かすようにすれば、完成です。これで元々やりたかった低コストかつ継続的にメタデータを管理できる仕組みができました。

  • 低コスト: RDBのメタデータを定期メンテナンスにのみ注意を払えば、BigQueryのメタデータも最新の情報になる
    • 自動化しているため、データ分析者やデータ基盤管理者の管理コストは下げられる
  • 継続性: RDBのメタデータのメンテナンスは普段の開発フローの中に組み込んでもらう
    • データ基盤側と開発チームの連携を取る

おわりに

今回はデータ基盤が提供する重要な要素の一つであるメタデータをいかに低コストに継続的に管理するか、という点について紹介しました。今後もこのような現場に即したデータ基盤を支える技術、データ基盤を活用したカスタマーサクセスの活動などについて紹介していければと思っています。

はてなでは、新卒/中途、東京/京都を問わず、エンジニアを募集しています。データ基盤、データ分析に興味のある人はぜひご応募ください!

参考文献

*1:どのテーブルが何回参照されたかもデータ基盤で集計しています