はじめに
データエンジニアリングにおけるデザインパターンを扱った書籍 "Data Engineering Design Patterns" の第 10 章 Data Observability Design Patterns の感想を残していきます。実質的な最終章ですね。
- 書籍ページ
- 無料ダウンロード
この章ではデータ可観測性として、
- 検知:上流からのデータ供給の中断や供給データ量の想定外の増減、処理遅延などの問題を発見すること
- 追跡:問題の原因特定を容易にするため、データセットやカラム、データ処理の間の関係性(データリネージ)を把握すること
に役立つデザインパターンが紹介されています。前の章のデータ品質に関するデザインパターンと合わせて、データの信頼性を担保するためのアプローチになります。
検知のために紹介されているいくつかのテクニックが面白いなと思ったり、データリネージの管理が最近は楽になってきたよなと思ったりしながら読んだので、そのあたりを感想(脇道に逸れた雑談)として触れていければと思います。
- 最終データ挿入時刻の求め方(Flow Interruption Detector パターン)
- データ異常の判定ロジック(Skew Detector パターン)
- ストリーミングにおけるラグ監視の重要性(Lag Detector パターン)
- パーセンタイル値の重要性(Lag Detector パターン)
- データリネージどこまで欲しい?(Dataset Tracker/Fine-Grained Tracker パターン)
- カラムレベルリネージをとるのも楽になった(Fine-Grained Tracker パターン)
- 行のリネージってほしくない?(Fine-Grained Tracker パターン)
ちなみに、以前の 2~9 章についての感想は以下です。この書籍を読む背景や感想を残していく方針について知りたい場合は、2 章の記事を参照してください。
3 -9 章の感想はこちらを展開してください
本章で紹介されているデザインパターン
| セクション | デザインパターン | 概要 |
|---|---|---|
| Data Detectors | Flow Interruption Detector | 上流からのデータ供給が中断していないかチェックする |
| Skew Detector | 上流から供給されるデータのサイズ・件数が想定の範囲を超えていないかチェックする | |
| Time Detectors | Lag Detector | ストリーミング処理の遅延(ラグ)が発生していないかチェックする |
| SLA Misses Detector | バッチ処理やストリーミング処理に要する時間が SLA 違反になっていないかチェックする | |
| Data Lineage | Dataset Tracker | データセット(テーブル、フォルダなど)の系統を把握する |
| Fine-Grained Tracker | Dataset Tracker に加えてレコード・カラムレベルの系統を把握する |
感想
最終データ挿入時刻の求め方(Flow Interruption Detector パターン)
Flow Interruption Detector パターンでは上流からのデータ供給が中断していないかを定期的に監視し、中断を検知したらアラートを上げます。
ここでポイントとなるのは何を監視して中断している/していないを判断するかですが、書籍では以下の項目が例として挙げられています。
- メタデータレイヤー:テーブルやレコードのメタデータに組み込まれている最終更新時刻
- データレイヤー:テーブルの件数(ただし、上書き更新の場合は件数が変わらないので注意が必要)
- ストレージレイヤー:ファイルへの最終書き込み時刻
データが供給される想定の間隔を超えてこれらの項目に変化がなければ、データ供給が中断していると判断します。
ただ、注意として、これらの情報はデータ供給以外の操作(スキーマ変更やデータコンパクションなど)を起因として変化することもあると書籍では述べられています。データ供給以外の操作が頻度高く定期的に発生することはあまり想定できないので、データ供給が中断し始めたタイミングでたまたまそれらの操作が発生しても、次のチェックのタイミングで検出すればよいケースも多い気はしますが。
監視対象の項目の話に戻りますが、例えば Snowflake では以下の項目が使えるでしょうか。
-
information_schema.table.last_altered- DML による変更だけではなく DDL による変更も含む
- Snowpipe Streaming に関しては別の情報を見る必要があるかも
-
select count(*) from ...- 件数はメタデータとして持っているので、定期的な実行もテーブルフルスキャンを起こさず現実的
一方、Oracle や PostgreSQL など OLTP 系の DBMS の場合は厳密かつ簡単に取れる監視項目は案外少ないです。一般にデータの更新頻度が高いため、最終更新時刻などを厳密にメタデータとして維持した場合、メタデータの更新がボトルネックになりかねないからです。
書籍では PostgreSQL に対して MAX(pg_xact_commit_timestamp(xmin))(要は各レコードの更新トランザクション ID を時刻に読み替えたものの最大値)を挙げています。ただ、これ全レコードフルスキャンしないといけないので、監視のオーバーヘッドがあり大規模テーブルに関して適用は難しいと思います。
Oracle でいうと、厳密性は下がるのですが、dba_tab_modifications ビューを監視するというアプローチはあるかもしれません。このビューでは、前回にオプティマイザ統計情報を収集してから挿入/更新/削除されたレコードのおおよその件数を確認することができます。厳密な件数でないこと、少しタイムラグがあることなど問題はありますが、データ供給がストップしているかの判断には使えるのではないでしょうか。
データ異常の判定ロジック(Skew Detector パターン)
Skew Detector パターンは上流から供給されたデータのサイズや件数が想定範囲内に収まっているかをチェックするデザインパターンです。
(ちなみに Skew とありますが、以前の章で何回か出てきたパーティション間のデータ量の偏りによる処理遅延の話とは別の話です)
ここで悩ましいのが、「データのサイズや件数が想定範囲内に収まっているか」をいかに判断するかですが、書籍では以下の 2 パターンが紹介されています。
- ウィンドウごとのパーセント差
- 例えば、ある 1 時間の件数とその直前の 1 時間の件数のパーセント差を求め、それがしきい値(±30% など)を超えたらアラートを上げる
- 標準偏差
- 例えば、1 時間ごとの件数を求め、直近 24 時間分の 24 点のデータの標準偏差(厳密には標準偏差 ÷ 平均 である 変動係数)がしきい値以上であれば、想定外のデータの変動があると考える
変動変数を使った監視はあまり見たことがなかったので面白いなと思いました。(一度スパイク的な変動があると、しばらくアラートが続く気もしますが)
ただ、どちらにせよ傾向(上昇傾向/下降傾向など)、季節性(定期的なピークなど)を踏まえると、どちらもしきい値の設定が難しいです。
その点では、最近では異常検知(時系列予測とそこからの逸脱のチェック)を行うアプローチが注目されているような気がします。前章の感想でも触れた "Data Quality Fundamentals" でも紹介されています。
これに着想を得て以下の記事も書いているので、参考にしてもらえればと思います。
ストリーミングにおけるラグ監視の重要性(Lag Detector パターン)
(ブログ記事のこの節は単に思い出話です)
Lag Detector パターンは、ストリーミング処理(マイクロバッチ含む)において、処理の遅延を監視しようというデザインパターンです。
このパターンで思い出したのですが、ストリーミング系の経験は乏しい私ですが、かなり昔にそちら系の性能検証を手伝うことがありました。
途中から参加したのですが、当初は性能指標としてスループットを主に性能指標として見ていて、それでは不十分ということでモニタリングする指標(ラグなど)を提案したことがあります。(まだストリーミング処理が広まりつつあった時なので慣れてないと仕方ないのですが)
当初はデータプロデューサーがデータを発生させるスループット(入り)と、ストリーミング処理がデータを捌くスループット(出)が等しければ問題ないよねという発想だったと思うのですが、それらがイコールだったとしても、
- ずっと同じ遅延を維持したまま(かつそれが SLA として許容できない)
- 並列処理している場合にデータ量の偏りによる遅延に気づきにくい
ということもあり得るので、ラグの監視は必須なんですよね。
ラグを監視すると決めたからといって、そのラグをどう定義するか、特に始点をどこにとるかは悩ましいですが。
- イベントが本当に発生した時刻
- 上流システムにデータが取り込まれた時刻
- 上流システムがデータをストリーミング処理に送った時刻
- ストリーミング処理がデータを受け取った時刻
可能であれば 1 番上の時刻を始点とみるのが良いのですが、そもそもこの時刻を取得できるのか、取得できたとしてその精度はどうなのかという問題が付きまとうんですよね。
パーセンタイル値の重要性(Lag Detector パターン)
書籍では "AVERAGE TRAP" というノートにおいて、ラグを監視する際は平均値より 90% タイル値や 95% タイル値をもって判断すべきと注意されています。
パーセンタイル値とはデータを昇順で並べた際に先頭から指定のパーセントの位置にある値を代表とする統計値です。例えば、10, 5, 30, 2, 3, 5 という 6 個の数値があった場合、昇順に並べると 2, 3, 5, 5, 10, 30 となるため先頭 80% の位置にある 10 が 80% タイル値ということになります。ちなみに 50% タイル値は中央値(今回だと 5)ですね。
通常、ラグは多くケースでポアソン分布に従い、グラフを書くと右の裾野が広い分布になりやすいです。そのため、平均値や中央値は外れ値やロングテールの問題を見落とすことが多く、代わりに 90% タイル値や 95% タイル値をとるべきというのは、性能管理ではよく知られた話です。
よく DB のベンチマーク結果で応答時間の平均だけ公開しているものがありますが、これも同様の理由で統計的には好ましくないと思っています。
もちろん、パーセンタイル値を見ていても問題を見逃す(極端に悪い数値が 1 点あるケースなど)こともあるので完璧ではないのですが、データの分布を確認した上で適切な統計値を選択したいものです。
データリネージどこまで欲しい?(Dataset Tracker/Fine-Grained Tracker パターン)
Dataset Tracker/Fine-Grained Tracker パターンはデータリネージを管理するためのパターンです(具体的なデータリネージの取得方法はいくつか例に言及しているだけですが)。
データリネージはデータセットの依存関係(どのデータセットからどのデータセットが作成・更新されているか)を指します。これが管理できていると、
- 系統分析(ターゲット ⇒ ソース)
- データが壊れたり遅延が発生した場合に、どこに原因があるか把握しやすい
- 影響分析(ソース ⇒ ターゲット)
- データ処理に問題や遅延が発生した場合に、どこに影響が及ぶのか把握しやすい
- データ仕様を変えたい場合に、どこと調整する必要があるか把握しやすい
- 個人情報などがどこで使われているか把握しやすい
などのメリットがあります。
実際にデータリネージを管理しようとすると課題になるのが、どこからどこまでのデータリネージをとるべきかという話です。例えば dbt + Snowflake などであれば、Snowflake にデータがロードされたテーブルから、最終的に dbt で加工されるテーブルまで追うことは可能なんですが、ユーザーの要望を聞くともっと広く取りたいというケースが多いんですよね。
- 始点
- Snowflake にデータロードする際のファイルストレージまで
- Snowflake にデータを送るシステム側のテーブル
- そのテーブルのデータを作っている更に先のテーブル
- そもそもユーザーがデータを登録した画面
- 終点
- BI のダッシュボード/チャート
- どの機械学習プロジェクトで使っているか
このあたりまで管理する場合、一昔前は高価なデータカタログを利用する必要がありましたが、いまだと OpenMetadata なども対応している範囲も広がりつつあるので、いい時代になったものだなと思います。
カラムレベルリネージをとるのも楽になった(Fine-Grained Tracker パターン)
Fine-Grained Tracker パターンはレコード/カラムレベルのデータリネージまでカバー範囲にしています。
先の節で「いい時代になったものだなと思います」と述べましたが、カラムレベルのリネージをとれるのも一昔前までは高価なデータカタログのみだったんですよね。
今では例えば、OpenMetadata などは一部カラムレベルのデータリネージを取得できますし(以下の記事も参照)、dbt も取得できるようですね。
くどいですが、本当にいい時代になったものです。
行のリネージってほしくない?(Fine-Grained Tracker パターン)
Fine-Grained Tracker パターンはカラムレベルリネージ以外にもレコードレベルのリネージを扱っています。とはいっても、本書で触れられているのは生成したレコードに job_version, job_name, batch_name などのメタデータを付与するという程度の話です。
ただ、よくユーザーと話していると、「このレコードはどのレコードから生成されたか」が問題になることが多いんですよね。
このニーズ少なくないのですが(特に金融系では多いイメージ)、どうしたらよいかは結局自分の中では結論出ていません。もちろん単純な集計などであれば、集計キーで元テーブルを絞り込めばよいのですが、
- 集計にロジックが含まれている場合
- 出力レコードがすでに上書きされた過去データに依存している場合
- 絞り込みで入力レコードを絞り込んでいる場合
などまで行くと、結局パイプラインのロジックを追っていく必要があるので、難しいんですよね。
