双時間履歴(バイテンポラル履歴)

あるプロパティの履歴値にアクセスする必要があることはよくあります。しかし、時にはこの履歴自体が、遡及的な更新に対応して修正される必要があります。双時間履歴は時間を2つの次元として扱います。実際の履歴は、完璧な情報の伝達を前提とした履歴がどうなるべきかを記録する一方で、レコード履歴は、私たちの履歴に関する知識がどのように変化するかを捉えます。

2021年4月7日



あるプロパティ(例:住所や給与)が時間とともにどのように変化するかを考えるとき、通常は線形な変化のシーケンスとして考えます。しかし、驚くほど多くの場合、コンピュータの記録を混乱させるような、はるかに複雑な状況になります。

簡単な例で説明しましょう。

それでは、2月25日のサリーの給与を尋ねられた場合、どのように答えるべきでしょうか?ある意味では、それが当時のレートだったことが分かっているので、6500ドルと答えるべきです。しかし、2月25日時点では給与が6000ドルだと考えていたことを無視することはできません。結局のところ、その日に給与計算を行い、小切手を印刷して彼女に送付し、彼女はそれを現金化しました。これらはすべて、当時の給与額に基づいて発生したものです。税務当局が2月25日の給与を尋ねてきた場合、これは重要になります。

2つの次元

私は、時間を2つの次元として考えることで、この混乱の多くを理解できるようになりました。そのため「双時間」という用語を使っています。1つの次元はサリーの給与の実際の履歴であり、給与計算を行う月の25日をサンプリングして示します。

日付給与
1月25日6000
2月25日6500
3月25日6500

2番目の次元は、2月25日時点でサリーの給与履歴をどのように考えていたかを尋ねたときに現れます。2月25日時点では人事部からの手紙が届いていなかったので、彼女の給与は常に6000ドルだと考えていました。実際の履歴と、その履歴に関する私たちの記録の間には違いがあります。表に新しい日付を追加することで、これを示すことができます。

記録日実際の日付給与
1月25日1月25日6000
2月25日1月25日6000
3月25日1月25日6000
2月25日2月25日6000
3月25日2月25日6500
3月25日3月25日6500

私は、2つの次元に対して「実際」と「記録」の履歴という用語を使用しています。有効(実際)とトランザクション(記録)という用語を使用する人もいます。[1]

この表の行は、「3月25日時点で、2月25日のサリーの給与は6500ドルだと考えていた」といったように読み取ります。この考え方を使うと、サリーの実際の履歴の以前の表を見て、より正確には、3月25日時点で知られていた(記録された)サリーの実際の履歴であると言えるでしょう。

プログラミングの観点から言えば、サリーの給与を知りたくて履歴がない場合、sally.salaryのようなものを使って取得できます。(実際の)履歴をサポートするには、sally.salaryAt('2021-02-25')を使用する必要があります。双時間の世界では、もう1つのパラメータsally.salaryAt('2021-02-25', '2021-03-25')が必要です。

別の視覚化方法として、x軸を実際の時刻、y軸を記録の時刻としたプロットを作成します。給与レベルに応じて領域を塗りつぶします。(将来の値を記録しようとしていないため、プロットの形状は三角形になります。[2]

このプロットを使用すると、25日ごとの給与計算の実行ごとに実際の履歴がどのように変化するかを表す表を作成できます。2月25日の給与計算は、サリーが昇給していない時期に行われましたが、3月25日の給与計算が行われた時点では、昇給が知られていたことがわかります。

遡及的変更の変更

人事部からの別の連絡を考えてみましょう。

  • 4月5日:申し訳ありませんが、以前のメールに誤字がありました。2月15日のサリーの昇給は6400ドルでした。ご迷惑をおかけして申し訳ありません。

これは、天使を泣かせるような変更です。しかし、双時間履歴の観点から考えると、それほど難しくありません。この新しい情報のプロットを以下に示します。

給与計算に使用される水平線は、特定の記録時点における実際の履歴を表しています。4月25日時点で、サリーの給与が2月15日に6000ドルから6400ドルに増加したことが分かっています。この視点では、サリーの6500ドルの給与は決して真実ではなかったので、見ることはありません。

図を見ると、垂直線は何を表していますか?

これは、特定の日付の値に関する私たちの知識を表しています。表は、私たちの知識が時間とともに変化するにつれて、2月25日の記録された給与を示しています。

バイテンポラリティの利用

双時間履歴は、遡及的な変更に対処しなければならない場合に履歴を構成するための便利な方法です。しかし、多くの人がこの手法を知らないこと、そして多くの場合、これなしで済ませることができるため、それほど頻繁に使用されているわけではありません。

それを回避する1つの方法は、遡及的な変更をサポートしないことです。保険会社が、変更は手紙を受け取った時点で有効になるという場合、それは実際の時刻と記録の時刻を一致させる方法です。

遡及的な変更は、給与小切手が現在更新された給与レベルに基づいて送付されるなど、遡及的に変更された過去の状態に基づいてアクションが行われる場合に問題になります。単に履歴を記録しているだけなら、遡及的に変更されることを心配する必要はありません。本質的に記録履歴を無視し、実際の履歴のみを記録します。不変のアクションがあっても、アクションに必要な入力データが記録されるようにアクションを記録する場合でも、そうすることがあります。そのため、サリーの給与計算では、小切手を発行した時点での給与を記録すれば、監査の目的には十分です。このような状況では、彼女の給与の実際の履歴だけがあれば済みます。記録履歴は、彼女の給与明細書の中に埋め込まれています。

アクションが発生する前に遡及的な変更が行われた場合も、実際の履歴だけで済む場合があります。2月24日にサリーの給与の変更を知っていた場合、給与計算のアクションが間違った数字に依存する前に、彼女の記録を調整できます。

双時間履歴を使用せずに済む場合は、システムをかなり複雑にするため、通常はそれが望ましいです。しかし、通常は遡及的な更新のために、実際と記録の履歴の間に不一致に対処しなければならない場合は、難しい問題に対処する必要があります。これの中で最も難しい部分の1つは、双時間履歴の仕組みについてユーザーを教育することです。ほとんどの人は、履歴記録を変化するものではなく、記録と実際の履歴の2つの次元があるものとは考えていません。

追記のみの履歴

単純な世界では、履歴は追記のみです。コミュニケーションが完璧で瞬間的であれば、すべての新しい情報は、関心のあるすべての関係者によってすぐに学習されます。その後、世界で新しいイベントが発生するにつれて追加するようなものとして履歴を扱うことができます。

双時間履歴は、コミュニケーションが完璧でも瞬間的でもないということに対処する方法です。実際の履歴はもはや追記のみではなく、遡及的な変更を加えます。しかし、記録履歴自体は追記のみです。2月25日時点でサリーの給与について知っていたことを変更することはありません。単に後で得られた知識を追加するだけです。追記のみの記録履歴を実際の履歴の上に重ねることで、実際の履歴を修正しながら、その修正の信頼性の高い履歴を作成できます。

遡及的変更の結果

双時間履歴は、値の変化を追跡するためのメカニズムであり、sally.salaryAt(actualDate, recordDate)を尋ねることができることは非常に役立ちます。しかし、遡及的な変更は、履歴記録を調整するだけではありません。専門家が言うように、「人々は、時間が原因から結果への厳密な進行であると仮定していますが、実際には非線形的で非主観的な視点から見ると、それは時間のかたまりのようなものです。」[3] 6400ドルを支払うべきだったのに6000ドルをサリーに支払った場合、それを修正する必要があります。少なくとも、それは後の給与でより多くを受け取ることを意味しますが、他の結果につながる可能性もあります。より高い支払いは、彼女が1ヶ月早く重要な閾値を超えたことを意味する可能性があり、税金上の影響がある可能性もあります。

双時間履歴だけでは、これらの依存関係の影響を把握するのに十分ではありません。そのためには、このパターンを超えた追加のメカニズムが必要です。1つの方法は、正しい給与で世界の状態を捉えた並列モデルを作成し、これを使用して補償変更を計算することです。[4] 双時間履歴は、これらの種類の対策に役立つ要素になりますが、その時間のかたまりの一部しか解明しません。

レコード時間の視点

上記の記録時間の例では、日付を使用して、実際の履歴に対する私たちの理解の変化を捉えています。しかし、記録履歴を捉える方法は、それよりも複雑になる可能性があります。

上記をより分かりやすくするために、給与計算日ごとに履歴をサンプリングしました。しかし、履歴のより良い表現は、日付範囲を使用することです。2021年をカバーする表を以下に示します。

記録日付実際の日付給与
1月1日~3月14日1月1日~12月31日6000
3月15日~4月4日1月1日~2月14日6000
3月15日~4月4日2月15日~12月31日6500
4月5日~12月31日1月1日~2月14日6000
4月5日~12月31日2月15日~12月31日6400

サリーの給与は、実際キー(日付範囲)と記録キー(日付範囲)の組み合わせで記録されていると考えることができます。しかし、記録キーの概念はそれよりも複雑になる可能性があります。

明らかなケースの1つは、異なるエージェントが異なる記録履歴を持つことができることです。これはサリーの場合に明らかに当てはまります。人事部から給与計算部へのメッセージの伝達には時間がかかったので、実際の履歴に対するそれらの修正の記録時間は、この2つの間で異なります。

部署記録日付実際の日付給与
人事部1月1日~2月14日1月1日~12月31日6000
人事部2月15日~12月31日1月1日~2月14日6000
人事部2月15日~12月31日2月15日~12月31日6400
給与計算部1月1日~3月14日1月1日~12月31日6000
給与計算部3月15日~4月4日1月1日~2月14日6000
給与計算部3月15日~4月4日2月15日~12月31日6500
給与計算部4月5日~12月31日1月1日~2月14日6000
給与計算部4月5日~12月31日2月15日~12月31日6400

履歴を記録できるものは何でも、情報を学習したときの独自の記録タイムスタンプを持ちます。そのデータによっては、企業は特定の種類のデータを記録するための定義エージェントとして特定のエージェントを選択すると考えられます。しかし、エージェントは権限の境界を越えます。会社がどれだけ大きくても、取引する税務当局の記録日は変わりません。異なるエージェントが同じ事実を異なる時期に学習することによって引き起こされる問題を解決するために、多くの努力が払われています。

部署と記録日付範囲の概念を1つの視点の概念に組み合わせることで、ここで起こっていることを一般化できます。したがって、「2月25日時点の人事部の視点によると、サリーの給与は6400ドルだった」といったように言います。表形式では、次のように視覚化できます。

視点実際の日付給与
人事部、1月1日~2月14日1月1日~12月31日6000
人事、2月15日~12月31日1月1日~2月14日6000
人事、2月15日~12月31日2月15日~12月31日6400
給与計算、1月1日~3月14日1月1日~12月31日6000
給与計算、3月15日~4月4日1月1日~2月14日6000
給与計算、3月15日~4月4日2月15日~12月31日6500
給与計算、4月5日~12月31日1月1日~2月14日6000
給与計算、4月5日~12月31日2月15日~12月31日6400

これを単一の視点概念に統合することで何が得られるのでしょうか?それは、他の視点について考えることを可能にします。一例として、代替的な視点について検討してみましょう。個々の昇給(例えば、2月15日のサリーの昇給)を取り除き、3月1日にすべての従業員に10%の昇給を与える視点を作成できます。これにより、サリーの給与に関する新しいレコード時間次元が生成されます。

視点実際の日付給与
現実世界1月1日~2月14日6000
現実世界2月15日~12月31日6400
一律昇給あり1月1日~2月28日6000
一律昇給あり3月1日~12月31日6600

レコード時間の概念のこの一般化は、本質的に同じメカニズムを使用して遡及的な変更と代替履歴を推論することにより、複数の視点を実際の履歴に重ねることができることを示しています。

バイテンポラル履歴と比較しても、履歴上に多くの視点次元を重ねることは、広く役立つものではありません。しかし、私は、歴史的または将来的な代替シナリオについて推論するような状況を考える上で、これは役に立つ方法だと考えています。

双時間履歴の保存と処理

データに履歴を追加すると、複雑さが増します。バイテンポラルの世界では、サリーの給与にアクセスするために2つの日付パラメータが必要です - `sally.salaryAt('2021-02-25', '2021-03-25')`。レコード時間を今日としてデフォルト処理すれば、現在のレコード時間のみを必要とする処理ではバイテンポラルの複雑さを無視できます。

しかし、アクセスを簡素化しても、必ずしもストレージが簡素化されるとは限りません。クライアントがバイテンポラルデータが必要な場合、何らかの方法で保存する必要があります。ある程度の時間性を組み込んだデータベースもありますが、比較的ニッチな存在です。そして賢明にも、人々は長期的に使用するデータに関しては、ニッチなテクノロジーに対して非常に慎重です。

それを踏まえると、多くの場合、最善の方法は独自のスキームを考案することです。大きく分けて2つのアプローチがあります。

最初の方法は、バイテンポラルデータ構造を使用することです。データの保存に使用されるデータ構造に必要な日付情報をエンコードします。これは、ネストされた日付範囲オブジェクトを使用するか、リレーショナルテーブルに開始日/終了日のペアを使用することで実現できます。

レコード開始レコード終了実際開始実際終了給与
1月1日3月14日1月1日12月31日6000
3月15日4月4日1月1日2月14日6000
3月15日4月4日2月15日12月31日6500
4月5日12月31日1月1日2月14日6000
4月5日12月31日2月15日12月31日6400

これにより、すべてのバイテンポラル履歴にアクセスできますが、更新とクエリは面倒です。ただし、バイテンポラル情報のアクセスを処理するライブラリを作成することで、これを容易にすることができます。

もう一つの方法は、イベントソーシングを使用することです。ここでは、サリーの給与の状態をプライマリストアとして保存するのではなく、すべての変更をイベントとして保存します。そのようなイベントは次のようになります。

記録日実際の日付アクション
1月1日1月1日sally.salary6000
3月15日2月15日sally.salary6500
4月5日2月15日sally.salary6400

イベントがバイテンポラル履歴をサポートする必要がある場合、それ自体がバイテンポラルである必要があることに注意してください。つまり、各イベントには、イベントが実際に発生した時間と、イベントについて知った時間を示すレコード時間が必要です。

イベントの保存は概念的にはより簡単ですが、クエリに答えるにはより多くの処理が必要です。ただし、アプリケーションの状態のスナップショットを作成することで、その処理の多くをキャッシュできます。そのため、このデータのほとんどのユーザーが現在の実際の履歴のみを必要とする場合、実際の履歴のみをサポートするデータ構造を作成し、イベントからそれを移入し、新しいイベントが流れてくるにつれて最新の状態に保つことができます。バイテンポラルデータが必要なユーザーは、より複雑な構造を作成し、同じイベントからそれを移入できますが、その複雑さが単純なモデルを望むユーザーを困難にすることはありません。(そして、ある人が異なるレコード日で実際の履歴を見たい場合、現在の実際の履歴を処理するのとほぼ同じコードを使用できます。)


参考文献

私は1980年代と90年代にさまざまなソフトウェアシステムで作業する際に、バイテンポラル履歴の問題に遭遇しました。私は観察したパターンを書き留め始めましたが、他の執筆プロジェクトが優先されたため、初期のドラフトの段階を超えることはありませんでした。そこにはバイテンポラル履歴に関する議論があり、この記事ではその概念を強調し、より明確に説明することを目的としています。

その頃、リチャード・スノッドグラスは著書Developing Time-Oriented Database Applications in SQLを執筆しました。これは、SQLシステムでこの種の問題を処理する方法について詳細に説明しており、そのアプローチはSQL:2011標準に影響を与えました。

私はTime Travel: A Pattern Language for Values That Changeから視点の概念を取り入れました。

脚注

1: 実際時間/レコード時間 対 有効時間/トランザクション時間

有効時間とトランザクション時間の用語はスノッドグラスに由来し、SQL:2011標準でも使用されています。私が最初に時間モデリングに関するワークショップを始めたのは、2000年代初頭でしたが、当時これらの用語を使用していたところ、人々は混乱していました。そこで、代わりに実際時間/レコード時間を使用するようになりました。有効時間/トランザクション時間が広く普及していないため、ここではその教訓に従い、実際時間/レコード時間を使用します。

2: バイテンポラルな未来

履歴では、実際時間は常にレコード時間以前です。しかし、バイテンポラリティの概念は未来にも適用できます。5月5日に、サリーが5月12日に別の昇給を受けることを告げられた場合、レコード時間を5月5日、実際時間を5月12日としてその昇給を記録できます。

3: この引用が分からなければ、Blinkを視聴リストに追加すべきです。これまでに制作された最高のタイムトラベルストーリーの一つです。

4: 私は2000年代半ばに並列モデルに関する以前の著作でこのトピックを探求し始めました。その後、その道筋を続けることはなく、将来いつ、あるいは再開するかどうかは分かりません。

謝辞

Alexandre Klaser、Dave Elliman、Joshua Taylor、Martha Rohte、Mauro Vilasi、Pavlo Kerestey、Pramod Sadalge、Rebecca Parsons、Saager Mhatre、Wolf Schlegelは、社内メーリングリストでこの記事に関する有用な議論に貢献しました。

Heikki Heinonenは、視点の表にあるいくつかのエラーを指摘してくれました。

重要な改訂

2021年4月7日: 公開

2021年3月17日: 社内レビュー送信

2021年3月2日: ドラフト開始