Stairlight*1 という、テーブルレベルのデータリネージができるツールをつくりましたので、その特徴や使い方について書きます。
背景
ETL(Extract, Transform, Load) と呼ばれる一連のデータ加工プロセスにおいて、バッチ処理での Transform は、ある程度まとまったデータのかたまりが、その形を変えることを指します。その変換処理は SQLで操作されるのが一般的で、データ移動の単位はテーブルです。そのSQLは、CREATE TABLE AS SELECT...
や CREATE VIEW AS SELECT...
など形は様々ですが、 突き詰めると SELECT 文です。データパイプラインにはたくさんの SELECT 文が登場しますが、加工や参照が行われる場所・タイミングは様々で、データがいつ生成されて、いつどこで参照されているのか、全体像の把握は容易ではありません。
そこで、共通項である SELECT 文と、それが生成するテーブルをマッピングしたうえでテーブル同士をつなぎ合わせることで、テーブルレベルでのデータの依存関係を把握できるのではないかと考えました。
特徴
- 入力は "SELECT 文が含まれるファイル"
- 特定のデータウェアハウスやツールに依存しない
- テーブルやビューの CREATE 文だけでなく、アプリケーション内のSELECT 文も対象
- Jinja テンプレートに対応
- データソース(読み込み先)を複数指定可
- 設定が漏れているファイルを検出できる
- 依存関係の読み取り結果をファイルに保存できる
--save
で結果をファイル保存、--load
で結果ファイルを読み込み- 結果ファイルを定期的に保存すると、データ依存関係を時系列で蓄積できる
想定している利用シーン
- データパイプラインの全体像の把握
- 特定のテーブルから見た上流・下流の特定
- CI でのテスト範囲
- 障害発生時の影響範囲
- データウェアハウス監査ログとの結合
- データ品質・鮮度のチェック
- データパイプラインのステータス可視化と監視
設定ファイル
2種類あります。後述しますが、両方ともコマンドでテンプレートを作成できます。
stairlight.yaml
入力対象のファイルを指定します。
include
でファイルの抽出条件を正規表現などで指定し、その中から除外したいファイルを exclude
で指定できます。
include: - type: fs path: "./tests/sql" regex: ".*/*.sql$" default_table_prefix: "PROJECT_A" - type: gcs project: null bucket: stairlight regex: "^sql/.*/*.sql$" default_table_prefix: "PROJECT_A" exclude: - type: fs regex: "main/test_exclude.sql$" settings: mapping_prefix: "mapping"
mapping.yaml
入力対象のファイルに対して、ファイル内の SQL に対応するテーブルや、ファイルに埋め込まれた Jinja パラメータへの設定値を記述します。 ひとつのファイルに複数の設定を記述できますので、SQL テンプレートから複数のテーブルを生成しているパターンにも対応しています。
mapping: - file_suffix: "tests/sql/main/test_a.sql" tables: - table: "PROJECT_A.DATASET_B.TABLE_C" - file_suffix: "tests/sql/main/test_b.sql" tables: - table: "PROJECT_D.DATASET_E.TABLE_F" params: PROJECT: PROJECT_J DATASET: DATASET_K TABLE: TABLE_L - table: "PROJECT_G.DATASET_H.TABLE_I" params: PROJECT: PROJECT_M DATASET: DATASET_N TABLE: TABLE_O - uri: "gs://stairlight/sql/test_a/test_a.sql" tables: - table: "PROJECT_a.DATASET_b.TABLE_c" - uri: "gs://stairlight/sql/test_b/test_b.sql" tables: - table: "PROJECT_d.DATASET_e.TABLE_f" params: PROJECT: PROJECT_g DATASET: DATASET_h TABLE: TABLE_i
設定方法
インストール
$ pip install stairlight
stairlight.yaml 作成・編集
$ stairlight init
カレントディレクトリに stairlight.yaml ができますので、SQL が保存されている場所を追記します。
mapping.yaml 作成・編集
$ stairlight check
stairlight.yaml で指定した条件に合致するすべてのファイルのパスを mapping.yaml に書き出します。SQL を静的解析して、Jinja パラメータが埋め込まれていれば、それらも一緒に書き出します。
そのテンプレートに対して、ファイルに対するマッピング情報を追記していきます。ファイルに記述されている SQL が生成するテーブル名を書きます。Jinja パラメータがある場合は、アプリケーションで埋め込んでいるパラメータのパターンの数だけ設定すると、複数のクエリがあるものとして読込を行います。
なお、mapping.yaml がすでに存在する状態で check
を実行すると、まだ設定していないファイルの分だけを抽出し、新規ファイルとして出力します。
コマンド
まずは --help
の結果をそのまま載せます。init
と check
はすでに紹介しましたので、その他を解説します。
$ stairlight --help usage: stairlight [-h] [-c CONFIG] [-s SAVE | -l LOAD] {init,check,up,down} ... A table-level data lineage tool, detects table dependencies from 'Transform' SQL files. Without positional arguments, return a table dependency map as JSON format. positional arguments: {init,check,up,down} init create a new StairLight configuration file. check create a new configuration file about undefined mappings. up return upstream ( table | SQL file ) list down return downstream ( table | SQL file ) list optional arguments: -h, --help show this help message and exit -c CONFIG, --config CONFIG StairLight configuration path. -s SAVE, --save SAVE save results to a file -l LOAD, --load LOAD load results from a file
サブコマンドなし
サブコマンドなしで stairlight
と実行すると、テーブル・SQL の依存関係を表現した JSON を出力します。
サンプルは以下です。
{ "PROJECT_d.DATASET_e.TABLE_f": { "PROJECT_j.DATASET_k.TABLE_l": { "type": "fs", "file": "tests/sql/main/test_e.sql", "uri": "/foo/bar/stairlight/tests/sql/main/test_e.sql", "line": 1, "line_str": "SELECT * FROM PROJECT_j.DATASET_k.TABLE_l WHERE 1 = 1" }, "PROJECT_g.DATASET_h.TABLE_i": { "type": "gcs", "file": "sql/test_b/test_b.sql", "uri": "gs://baz/sql/test_b/test_b.sql", "line": 23, "line_str": " PROJECT_g.DATASET_h.TABLE_i AS b", "bucket": "stairlight" } } }
--config
で設定ファイルの読み込み先のパスを指定できます。デフォルトはカレントディレクトリです。
また、前述のとおり、--save
で結果のファイル保存、--load
で結果ファイルの読み込みができます。--save
と --load
はどちらかしか指定できません。
up
$ stairlight up --help usage: stairlight up [-h] [-c CONFIG] [-s SAVE | -l LOAD] -t TABLE [-o {table,file}] [-v] [-r] optional arguments: -h, --help show this help message and exit -c CONFIG, --config CONFIG StairLight configuration path. -s SAVE, --save SAVE save results to a file -l LOAD, --load LOAD load results from a file -t TABLE, --table TABLE table name that StairLight searches for, can be specified multiple times. -o {table,file}, --output {table,file} output type -v, --verbose return verbose results -r, --recursive search recursively
stairlight up
は、--table
で指定したテーブルから見て、データパイプラインの上流に位置するテーブル or ファイルを抽出します。
テーブルは複数指定することもできます。
デフォルトでは、指定したテーブルが直接参照しているテーブル or ファイルを配列で出力します。
--verbose
をつけると、サブコマンドなしで実行したときと同じ形式の JSON で出力します。
--recursive
をつけると再帰的に遡り、上流に位置するすべてのテーブル or ファイルを出力します。
テーブルを2つにして、--verbose
と --recursive
を両方指定した場合のサンプルです。
$ stairlight up -t "PROJECT_D.DATASET_E.TABLE_F" -t "PROJECT_d.DATASET_d.TABLE_d" -vr -c "./config" | jq . [ { "PROJECT_D.DATASET_E.TABLE_F": { "upstream": { "PROJECT_J.DATASET_K.TABLE_L": { "type": "fs", "file": "tests/sql/main/test_b.sql", "uri": "/Users/tosh2230/project/stairlight/tests/sql/main/test_b.sql", "line": 23, "line_str": " PROJECT_J.DATASET_K.TABLE_L AS b", "upstream": { "PROJECT_P.DATASET_Q.TABLE_R": { "type": "fs", "file": "tests/sql/main/test_c.sql", "uri": "/Users/tosh2230/project/stairlight/tests/sql/main/test_c.sql", "line": 23, "line_str": " PROJECT_P.DATASET_Q.TABLE_R AS b" }, "PROJECT_S.DATASET_T.TABLE_U": { "type": "fs", "file": "tests/sql/main/test_c.sql", "uri": "/Users/tosh2230/project/stairlight/tests/sql/main/test_c.sql", "line": 6, "line_str": " PROJECT_S.DATASET_T.TABLE_U" }, "PROJECT_V.DATASET_W.TABLE_X": { "type": "fs", "file": "tests/sql/main/test_c.sql", "uri": "/Users/tosh2230/project/stairlight/tests/sql/main/test_c.sql", "line": 15, "line_str": " PROJECT_V.DATASET_W.TABLE_X" } } } } } }, { "PROJECT_d.DATASET_d.TABLE_d": { "upstream": { "PROJECT_e.DATASET_e.TABLE_e": { "type": "fs", "file": "tests/sql/main/test_f.sql", "uri": "/Users/tosh2230/project/stairlight/tests/sql/main/test_f.sql", "line": 1, "line_str": "SELECT * FROM PROJECT_e.DATASET_e.TABLE_e WHERE 1 = 1" } } } } ]
down
stairlight down
は、--table
で指定したテーブルから見て、データパイプラインの下流に位置するテーブル or ファイルを抽出します。
コマンドオプションの内容は、up
と同じなので割愛します。
今後の予定
ツール単体としては、以下をやっていこうと思っています。が何かよいアイデアや Pull Request を募集中です!!
Amazon S3 対応
特定のサービスやツールに依存しないことを目標にしていますので、GCP 以外にも対応していきたいです。まずは馴染みのある AWS の S3 に対応することで、Amazon Redshift や Amazon Athena での Transform もカバーしたいと思っています。
ファイル読込処理の並列化
現在の実装のボトルネックは、GCS に対するファイル読込処理です。いまはひとつずつ読み込みをしているので、さくさく動くようにしたいです。
様々な SQL のパターンへの対応
テストコードで検証してはいますが、SQL は多様な記述方式がありますので、いまの実装ではカバーできていない可能性が高いです。より多くの SQL をテストパターンに追加して、解析機能をより強化できればと思っています。
参考記事
Stairlight はこちらの記事を読んだことがきっかけでできました、ありがとうございます!!
(2021/12/13 追記)
Stairlight をライブラリとして利用した例を書きました。出力した依存関係の情報をもとに、グラフを作成するアプリケーションを公開しています。
*1:名前は、不思議のダンジョン 風来のシレン5plus フォーチュンタワーと運命のダイス(Spike Chunsoft, 2020) に登場する技である、"ドコ? カイ弾" が由来です。同ゲームの英語版では、"ドコ? カイ弾" は Stairlight と翻訳されています。この技を使うと、現在のフロアにある階段の位置がマップに表示されるという効果があります。