Data Vaultにおけるバイテンポラルデータ:インスクリプションタイム(登録時刻)の扱い方
Data Vaultにインスクリプションタイム(登録日時)を用いてバイテンポラル(二重時間軸)データをロードする方法:設計パターン、落とし穴、銀行業務における実践例を徹底解説。
実例:銀行業務における記帳日(Posting Date)問題の解決
銀行や保険業界のクライアントとプロジェクトを進める際によく直面する課題が、「すでに1つまたは複数の時間軸(タイムライン)が付与されているデータ」の取り扱いです。
筆者は通常、3つの主要なタイムラインに分類して整理します(保険業界では6つ以上のタイムラインが存在することもありますが、まずは基本から始めましょう)。
私たちが扱うデータの発生源は主に業務ITシステムです。したがって、常に必ず存在するタイムラインが「インスクリプションタイム(Inscription Time:登録時刻)」です。これは、情報がソースシステムに最初に記録・登録された時点を指します。
一部のデータにとっては、それで完結します。データウェアハウス(DWH)に転送されることもなく、過去や未来についての主張も含まれません。その場合は非常にシンプルであり、追加の対応は不要です。
しかし、そのデータをDWHに転送・統合しなければならない場合はどうでしょうか。ここではData Vaultを設計パラダイムとして採用していると仮定しますが、これは時間軸を考慮するあらゆるモデリング手法に共通する課題です(なお、Data Meshはモデリング手法ではなく組織パラダイムです)。ハブ、リンク、サテライトにデータをロードすると、いわゆる「ロード日時(Load Date / Load Time)」が付与されます。なお、筆者はこの文脈で「有効開始日(Valid From)」や「有効終了日(Valid To)」という用語は意図的に使いません。ビジネス上の有効期間と混同されてしまうためです。
週次、日次、あるいは時間単位の頻度でデータをロードし、ソースシステムから最新情報のみを抽出する場合、追加されるタイムラインは1つだけなので構造はシンプルなままです。Data Vault自動化ツールがすべてを追跡し、要求に応じて「現在時点(As-of-now)」および「過去特定時点(As-of-then)」の情報を提示できれば、何の問題もありません。
率直に申し上げて、私たちの顧客の多くはこの程度の複雑さにとどまっており、そうした方々はこの先を読み進める必要はありません。もし未読であれば、Data Vaultにおける時間性(Temporality)に関する筆者の一連のブログ記事をお読みいただくことをお勧めします。過去時点の再現(As-of-then)だけでも十分高度だからです。
また、ソースシステム内でビジネス上の有効期間(有効開始・終了日)が管理されている場合は、ハブキーを拡張してこの情報を格納する方法について、過去のブログ記事を参照してください。
一方、インスクリプションタイムが業務上極めて重要な意味を持つ場合は、「ロード時間が十分に近ければインスクリプションタイムは無視できる」という以前のアドバイスは当てはまりません。
インスクリプションタイムの管理が不可欠となるケース:
- 極めて高い頻度で変更が発生するが、DWHへのロード頻度は低く、そのすべての変更を監査やビジネス要件のために追跡する必要がある場合(情報源がCDCストリームであるケースなど)。
- データソースがシステム直接ではなく何らかのエクスポートファイルであり、エラーや欠損が含まれたり、過去日付の修正データが後から再送されたりする場合。
- レポーティングにおいて、DWHロード日時よりもインスクリプションタイムの方が正確な履歴を表している場合(銀行の日締め処理[TEV / COB / EOB]に基づく記帳日データなど、「真の歴史」を表現する場合)。
言い換えれば、「インスクリプションタイムが自社にとって必須であるかどうかは、業務要件を見ればすぐに分かる」ということです。
解決アプローチ:インスクリプションタイムを無視できるようにする
ソースシステムへの登録からData Vaultへのロードまでのタイムラグを極小化できれば、データが常に発生順通り(In-order)に届く限り、ほとんどのケースでインスクリプションタイムを無視してロード日時に依存できます。しかし、Kafkaストリームなどの分散メッセージングでは順序が保証されない(Out-of-order)ことがあります。順序が逆転して到着した場合、このアプローチでは完全に誤った結果が出力されてしまいます。
解決アプローチ1:永続的ステージング層(PSA)の導入
標準的なData Vaultパターンを用い、変更キー(技術キー+登録時刻、またはCDCストリームから提供される変更キー)を利用して、まずすべての変更レコードを永続的ステージングエリア(PSA: Persistent Staging Area)に保持します。その上で最新ビューを作成し、Raw Vaultへとロードします。PSAに全履歴が保持されるため、完全な監査可能性が担保されます。
ただし、カスタムのロードパターンを作らない限り、Raw Vaultのデータはロード実行間隔でしか更新されません。秒単位やマイクロ秒単位の変更はPSAには記録されますが、全レコードをループ処理してロードしない限り、Raw Vaultには反映されません。CDCストリームをソースとする場合、CDCログをそのままVaultと一緒にアーカイブしているようなものなので、多くのアプリケーションではこれで十分かもしれません。
真の問題は、ループ処理でロードしたとしても、「過去日付の訂正データや順不同(Out-of-order)のデータ」が届いた場合に、Raw Vaultサテライトの正しい時系列位置にレコードを挿入できない点です。このようなシナリオが発生する可能性がわずかでもあり、それを捕捉しなければならない場合、この手法は破綻します。以前はAttunity/Qlik ReplicateやGoldenGateのような商用CDCであれば順序不整合は起きないと考えられていましたが、Kafkaの普及によって順不同到着は日常茶飯事となりました。
解決アプローチ2:インスクリプションタイムをロード日時として代用する
複雑さを減らすため、「インスクリプションタイムをそのままロード日時カラムに格納すればよいのではないか」と考えるかもしれません。一見名案に思えます。実装は簡単で、Data Vaultテーブルの構造を変更する必要がなく、既存のツール群もそのまま動作します。
理論上、「受信するデータは常に正確であり、後から訂正されることは絶対にない」という前提に立てば成立します。しかし、これは極めて危険な前提です:
- エクスポートファイルによる連携では、ほぼ確実に後日訂正が発生します。
- CDCストリームであっても、障害発生後の同期処理で元テーブルとの整合を取り直す必要があります。
- 銀行の記帳日データなども、時系列が前後して届くことがあります。
同一のインスクリプションタイムに対して異なる値が後から届いた場合、ロード済みのレコードを削除するか、アーカイブテーブルへ移動するかといった泥臭い例外処理が必要になります。
要するに、ごく限られた特殊なケースを除き、早晩データが欠落したり誤った状態に陥ったりするリスクが極めて高くなります。Datavault Builderの開発チームでも数週間のベータテストを実施しましたが、実環境での検証において数日(場合によっては数時間)ですべて破綻したため、この方式は即座に廃止されました。
解決アプローチ3:バイテンポラル・サテライト(Bi-Temporal Satellites)
正攻法となるアプローチは、サテライトのキーを拡張し、「ハッシュキー(HK)+ロード日時+インスクリプションタイム」の複合キーを持つマルチアクティブサテライト(Multi-Active Satellite: MAS)の特殊形態を採用することです。
データをロードする際、ステージングされたデータの「HK+インスクリプションタイム」を既存のサテライトデータと比較します。属性値に変更があるか、または該当レコードが存在しない場合、現在のシステム時刻をロード日時として新規INSERTします。
メリット: サテライトへのデータロードにおいて、Data Vaultの原則である「INSERTオンリー」のパターンを維持できます。
デメリット: サテライト内部にバイテンポラル(二重時間軸)の履歴を抱え込むことになります。データが順不同(Out-of-order)で届いても問題なく格納できますが、過去の「ある時点における認識」を再現するには、クエリ実行時(読み取り時)に動的に組み立てる必要があります。
これによりクエリが複雑化しますが、分析関数(Window関数)を用いて「ハッシュ+インスクリプションタイム」または「ハッシュ単体」でパーティションを切ることで解決できます。
ただし、各サブクエリで分析関数を多用してJOINを繰り返すとパフォーマンスが悪化します。Markus Winand氏の名言「インデックスを使え(Use the index Luke!)」にある通り、ここで威力を発揮するのがポイントインタイム(PIT)テーブルです。
もっとも、PITを作成する際にも、リンクキーがハッシュ・登録時刻・ロード日時の複合になってしまうという課題があります。幸いなことに、Patrick Cuba氏がサテライトにシーケンス(連番)カラムを持たせることで、よりコンパクトなPITテーブルを生成し、高速な結合を実現する優れたアプローチを発表しています。
この方式はDV2.0の標準フレームワークに適合するでしょうか?答えは「Yes」です。バイテンポラルサテライトはマルチアクティブサテライトの特殊な応用形態です。通常のマルチアクティブサテライトと異なり、サブパーティションキーが事前に定まっているため、差分ロード(デルタロード)も容易に行えます。サテライト内のシーケンス番号を用いたインデックス設計は技術的な最適化であり、アーキテクチャの論理原則を何ら損なうものではありません。
圧縮(Compression)と順不同到着への対応
CDCストリームは意味のある変更のみを送信してくると期待されがちですが、現実にはそうとは限りません。また、データを複数のサテライトに分割して格納する場合、特定のサテライトにとっては変更のない無意味なCDCレコードが多数発生します。したがって、重複する不変データを間引く「圧縮(Compression)」処理が必須となります。
しかし、古い登録時刻(インスクリプションタイム)の修正データが後から到着する可能性がある場合、圧縮を安易に行うと深刻な不整合を招きます。過去に「変化なし」として間引いたレコードが存在する状態で、その過去時点に新たな値が挿入されると、時系列の推移が狂ってしまうからです。
そのため、圧縮によって間引かれたすべてのレコードのタイムスタンプ情報を保持する「トラッキングサテライト(Tracking Satellite)」を併用する必要があります。
順不同でデータが届いた場合でも、サテライト本体とトラッキングサテライトを組み合わせることで完全な履歴を再構成し、正しい時系列位置に適切な情報を反映させることができます。
例えば、登録時刻 t0、t1、t2 において値「a」が届き、t1 と t2 が圧縮されていたとします。その後、過去の t1 に対して値「c」が修正データとして届いた場合、サテライトの t1 に「c」を挿入すると同時に、t2 には以前圧縮されていた「a」を明示的に挿入しなければなりません(t1の「c」とt2の「a」が異なるため)。
ドイツ語版となりますが、本メカニズムを解説した解説動画をご用意しています:
結論:自社でインスクリプションタイムを管理すべきか?
「筆者はこれまで登録時刻を無視せよと説いてきたのに、なぜ今になってその格納と評価方法について書いているのか?」と疑問に思われるかもしれません。
- 筆者は常に「どのような条件下であれば登録時刻を無視できるか」を明確にしてきました。
- 読者の大多数、そして大半のプロジェクトにおいては、今でもインスクリプションタイムを無視すべきです。
- 筆者は銀行業界でキャリアをスタートしたため、こうしたパターンの必要性は痛いほど理解しています。しかし同時に、複数のタイムラインを管理することがいかに開発と保守を複雑化させるかも熟知しているため、明確なビジネス価値がない限り省略することを推奨してきました。
しかし、Datavault Builderの顧客層が世界中で拡大するにつれ、金融機関や大規模企業からこの課題に対する決定的な一般解を求める声が多数寄せられるようになりました。数ヶ月にわたる設計とプロトタイピングの末、「バイテンポラル・サテライトこそがこの課題を根本から正しく解決できる唯一の手段である」という結論に達しました。
そして私たちはこの高度なバイテンポラル自動化機能を、Datavault Builder バージョン 6.3 の標準機能として正式にリリースしました。
Special thanks to: Patrick Cuba (Snowpit), Thomas Herzog (マルチアクティブサテライトの特殊形態としてのバイテンポラルサテライトの着想), Dirk Lerner (複数時間軸に関する数々の優れた知見)