public note

Stairlight というデータリネージツールをつくった

Stairlight*1 という、テーブルレベルのデータリネージができるツールをつくりましたので、その特徴や使い方について書きます。

github.com

背景

ETL(Extract, Transform, Load) と呼ばれる一連のデータ加工プロセスにおいて、バッチ処理での Transform は、ある程度まとまったデータのかたまりが、その形を変えることを指します。その変換処理は SQLで操作されるのが一般的で、データ移動の単位はテーブルです。そのSQLは、CREATE TABLE AS SELECT...CREATE VIEW AS SELECT... など形は様々ですが、 突き詰めると SELECT 文です。データパイプラインにはたくさんの SELECT 文が登場しますが、加工や参照が行われる場所・タイミングは様々で、データがいつ生成されて、いつどこで参照されているのか、全体像の把握は容易ではありません。

そこで、共通項である SELECT 文と、それが生成するテーブルをマッピングしたうえでテーブル同士をつなぎ合わせることで、テーブルレベルでのデータの依存関係を把握できるのではないかと考えました。

特徴

  • 入力は "SELECT 文が含まれるファイル"
    • 特定のデータウェアハウスやツールに依存しない
    • テーブルやビューの CREATE 文だけでなく、アプリケーション内のSELECT 文も対象
    • Jinja テンプレートに対応
  • データソース(読み込み先)を複数指定可
    • 保存先が分かれていても統合管理できる
    • 実行環境のローカルと、Google Cloud Storage(GCS) の2種類に対応
    • 正規表現で、読み込みの対象・対象外をそれぞれ指定できる
  • 設定が漏れているファイルを検出できる
  • 依存関係の読み取り結果をファイルに保存できる
    • --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 の結果をそのまま載せます。initcheck はすでに紹介しましたので、その他を解説します。

$ 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 はこちらの記事を読んだことがきっかけでできました、ありがとうございます!!

note.com


(2021/12/13 追記)

Stairlight をライブラリとして利用した例を書きました。出力した依存関係の情報をもとに、グラフを作成するアプリケーションを公開しています。

ts223.hatenablog.com

*1:名前は、不思議のダンジョン 風来のシレン5plus フォーチュンタワーと運命のダイス(Spike Chunsoft, 2020) に登場する技である、"ドコ? カイ弾" が由来です。同ゲームの英語版では、"ドコ? カイ弾" は Stairlight と翻訳されています。この技を使うと、現在のフロアにある階段の位置がマップに表示されるという効果があります。