Extensible Storage Engine アーキテクチャ

 

Extensible Storage Engine (ESE) データベースの上位には、Exchange ストアが存在します。ESE は、DML (データ操作言語) および DDL (データ定義言語) 機能を完全に備えたマルチユーザー ISAM (Indexed Sequential Access Method) テーブル マネージャです。ESE によって、アプリケーションはレコードを格納したり、インデックスを作成して、さまざまな方法でこれらのレコードにアクセスできるようになります。

ESE には、次の 3 つのバージョンがあります。

  • ESE97   Exchange Server 5.5 のデータベース エンジンです。
  • ESENT   Active Directory および多くの Microsoft Windows コンポーネント用のデータベース エンジンです。他のバージョンの ESE (5 MB ログ ファイルと 4 KB ページ サイズを使用) とは異なり、Active Directory 用の ESENT は 10 MB のログ ファイルと 8 KB のページを使用します。
  • ESE98   Exchange 2000 Server および Exchange Server 2003 のデータベース エンジンです。
note注 :
ESE は、以前は Joint Engine Technology (JET) Blue と呼ばれていました。JET Blue は、Access で使われているバージョンの JET (JET Red と呼ばれる) とは異なります。

トランザクション

ESE は洗練された、トランザクション ベースのデータベース エンジンです。トランザクションとは、一連の原子 (それ以上分割できない) 単位として扱われる処理のことです。トランザクションでは、すべての処理が実行されて永久に保存されるか、まったく処理が実行されないかのいずれかです。たとえば、受信トレイのメッセージを削除済みアイテム フォルダに移動する場合に必要な処理について考えてみましょう。あるフォルダからメッセージが削除され、別のフォルダに追加され、さらにフォルダのプロパティが更新されます。エラーが発生した場合、メッセージのコピーが 2 つ作成されたり、まったく作成されなかったり、フォルダのプロパティ値 (アイテム数など) が実際のフォルダの内容と矛盾するというのは問題です。

こうした問題を防ぐために、ESE はトランザクション内に処理をまとめています。ESE は、トランザクションがデータベース ファイルにコミットされるまで、どの処理も完全に適用されないようにします。トランザクションがデータベース ファイルにコミットされたとき、すべての処理が完全に適用されます。

さらに、クラッシュした場合には、ESE が起動時の自動回復を処理し、コミットされていないトランザクションをロール バックします。トランザクションがコミットされる前に ESE にエラーが発生した場合は、トランザクション全体がロール バックされ、トランザクションが発生しない場合と同じになります。トランザクションがコミットされた後に ESE がクラッシュした場合は、トランザクション全体が保持され、クライアントで変更内容を表示できます。

ACID トランザクション

以上のような信頼できるトランザクションは、一般的に ACID トランザクションと呼ばれています。ACID トランザクションは、次の属性で表すことができます。

  • 原子性 (Atomic)   トランザクション状態の変更が "すべて" または "なし" であることを示します。Atomic 状態の変更には、データベースの変更や、変換プロセス上のメッセージおよびアクションが含まれます。
  • 一貫性 (Consistent)   トランザクションが状態の正確な変換であることを示します。グループとして実行される処理は、状態に関連付けられた整合性の制約には違反しません。これには、トランザクションが正しいプログラムであることが必要です。
  • 分離性 (Isolated)   複数のトランザクションを同時に実行したにもかかわらず、各トランザクションからは、他のトランザクションが当該トランザクションの前か後 (前と後の両方ではなく) に実行されたように見えることを示します。
  • 持続性 (Durable)   トランザクションが正常に終了 (コミット) した直後に状態を変更してエラーを切り抜けることを示します。

バージョン ストア

バージョン ストアを使用すると、ESE は現在のトランザクションを追跡し、管理することができます。したがって ESE は、ACID テストのうち、分離性と一貫性の部分に合格することができます。バージョン ストアは、データベースの変更リストをメモリ内に保持します。

バージョン ストアは、以下の状況で使用します。

  • ロールバック   トランザクションをロールバックする必要がある場合、実行した処理の一覧を取得するために、バージョン ストアを確認します。トランザクションをロールバックするには、すべての処理を反対方向に実行します。
  • 書き込み競合の検出   2 つの異なるセッションが同じレコードの変更を試みると、バージョン ストアが 2 番目の変更を拒否し、これを通知します。
  • 反復可能読み取り   セッションがトランザクションを開始すると、他のセッションが表示中のレコードを変更した場合でも、常に同じデータベースを表示します。セッションがレコードを読み取る場合、そのセッションで表示する必要のあるレコードのバージョンを判断するためにバージョン ストアに問い合わせます。反復可能読み取りでは、変更が他のクライアントまたはセッションによって行われたかどうかにかかわらず、クライアントがトランザクションを開始した後、トランザクション開始時のデータベースの状態が分離レベルを使用して表示されます。反復可能読み取りは、バージョン ストアを使用することにより実装されます。データベースに対する変更のメモリ内リストを使用すると、特定のセッションで表示する必要があるレコード ビューを決定することができます。
  • 変更前イメージの遅延ロギング   これは、ESE によるログ出力のデータ量を他の同等なデータベース エンジンよりも少なくするという、複雑な最適化です。

スナップショット分離

トランザクションが開始されると、そのセッションでは、トランザクションの開始時に存在したとおりのイメージに加え、セッション固有の変更が加えられたイメージとして、単一で一貫性のあるデータベース イメージが表示されることが ESE によって保証されます。他のセッションからもデータの変更とトランザクションのコミットが可能なため、これらの変更はコミットの前に開始されたトランザクションには不可視です。ユーザーは、最新バージョンのトランザクションが表示されている場合にのみ、レコードを変更できます。そうでない場合、JET_errWriteConflict を使用した更新が失敗します。最新のトランザクション以前のバージョンは、自動的に破棄されます。

ESE は、スナップショット分離と呼ばれるトランザクション分離レベルを提供しています。スナップショット分離レベルによって、ユーザーは時間的推移に沿って一貫したデータベース ビューを使って新しくコミットされた行にアクセスできます。スナップショット分離とは、『A Critique of ANSI SQL Isolation Levels』で最初に紹介された並行処理制御アルゴリズムです。スナップショット分離は、反復可能読み取りを使用することにより、ESE によって実装されます。

ESE データベース構造

リッチテキスト データベース ファイル内のすべてのデータは、B ツリーに格納されます。B ツリーの "B" はバランス (balanced) を意味します。"ツリー" はファイル システム上のフォルダ構造と同様の配置を示しています。ここで、ルートは上位アイテム (データベース ページ) の親であり、同様に上位アイテムはそれより下位のアイテムの親になります。B ツリーは、ディスク上のデータに高速にアクセスできるように設計されています。ディスクの読み取りと書き込みは、メモリの読み取りと書き込みに比べるとはるかに遅いため、B ツリーは 4 KB のページに分割されています。これにより、ESE は、最小限のディスク I/O で必要なデータを取得できます。ESE データベースには、最大 2^32 (2 の 32 乗) のページ、つまり 16 TB を収容できます。データベースのサイズを制限する要素は、実際にはユーザーがバックアップ、復元、および他のデータベースでの保守操作 (オフラインでのデータベースの最適化、データベースの修復など) を適切な時期に実行する能力のみです。

データベース ページ

ESE のページ サイズは、それを使用するアプリケーションによって定義されます。たとえば、ESE97 (Exchange Server 5.5) と ESE98 (Exchange 2000 Server および Exchange Server 2003) は 4 KB ページを使用するのに対し、ESENT (Active Directory) は 8 KB ページを使用します。4 KB ページ、または 8 KB ページのどちらにも、他のページを指すポインタ、または B ツリーに格納されている実データを指すポインタが含まれています。ポインタとデータ ページはファイル内に混在しています。

可能な限りパフォーマンスを向上させるには、ページをできるだけメモリ バッファにキャッシュします。これにより、ディスクへの転送の必要が少なくなります。各ページは 40 バイトのページ ヘッダーで始まります。ヘッダーには以下の値が含まれています。

  • pgnoThis   ページ番号を示す値です。
  • DbtimeDirtied   ページが最後に変更された Dbtime を示す値です。
  • pgnoPrev   見開きの左側のページ番号を示す値です。
  • pgnoNext   見開きの右側のページ番号を示す値です。
  • ObjidFDP   データベースの Father of the Data Page (FDP) と呼ばれる特殊なページのオブジェクト ID を示す値です。このオブジェクト ID はこのページが属する B ツリーを示します。FDP ページは修復時に使用されます。
  • cbFree   ページ上で使用可能なバイト数を示す値です。
  • cbUncommittedFree   ページ上の使用可能な、コミットされていないバイト (空きバイトだが、ロールバックによって再生利用できるバイト) の数を示す値です。
  • ibMicFree   ページの最上部にある次に使用可能なバイトのページ オフセットを示す値です。

ECC チェックサム

Exchange Server 2003 Service Pack 1 (SP1) には、エラー訂正コード (ECC) チェックサムと呼ばれる新機能が導入されています。ECC チェックサムは、(.edb ファイル, .stm ファイル、およびトランザクション ログ ファイルの) データベース ページのシングル ビット エラーの訂正を可能にする新しいチェックサム形式です。この新しいチェックサム形式は 64 ビットを使用しますが、従来のチェックサム形式では 32 ビットです。以前の形式のデータベースは新しいコードでも使用できますが、最新の形式のデータベースは以前のバージョンの ESE で使用することはできません。データベース エンジンの更新後は、データベースに書き込まれたすべてのページが新しいチェックサム形式になります。読み取られても修正されていないページは、チェックサム形式の更新はありません。

note注 :
最新の ESE.dll が展開された後、オフラインの最適化を実行することによって、データベース内のすべてのページでチェックサム形式の更新が行われます。それによってデータベースの最適化にかかる時間が大幅に増える可能性があるため、これはお勧めできません。さらに、/k スイッチを指定して esetuil を実行するのも、データベース内のすべてのページにチェックサムを適用することになるため、お勧めできません。

以前の形式のチェックサムを使用したデータベース ページは 4 バイトのチェックサムの次に、4 バイトのページ番号が続きます。このページ番号は、要求されたページが実際にディスクから読み取られることを確認するために使用されます。

新しいチェックサム形式では、4 バイトのページ番号が削除されており、代わりに 8 バイトのチェックサムで始まります。ページ番号は、チェックサムを計算する際の入力パラメータとして使用されます。したがって、正しくないページがディスクから読み取られると、チェックサムの不一致が発生します。

最新のチェックサム形式は、2 つの 32 ビット チェックサムで構成されています。1 つは XOR チェックサムで、従来の形式のチェックサムと同じように計算されます。ページ番号は、このチェックサムの計算のシードとして使用されます。2 番目の 32 ビット チェックサムは ECC チェックサムで、ページ上のシングルビット エラー訂正を可能にします。

データベースの整合性と -1018 エラー

  • ページの読み取りが行われると、ESE はページ上のフラグを調べて、ページに最新のチェックサム形式があるかどうかを確認します。次に、適切なチェックサム (最新の形式、または以前の形式) の計算が行われます。最新の形式のチェックサムと一致しないチェックサムが存在する場合、ESE はエラーの訂正を試みます。エラーが自動的に訂正できない場合、Exchange は -1018 エラーを報告します。

Exchange ストアが以下のいずれかの操作を行う場合、Exchange ストア自身が -1018 を生成する場合があります。

  • 間違ったチェックサムを持つページを作成した。
  • ページを正しく作成したが、オペレーティング システムに対して間違った場所にページを書き込むように通知した。

システム管理者は、-1018 エラーを検出した場合や、サーバーのハードウェア診断テストを実行してもテストでは問題が報告されない場合、ハードウェアが最初の分析に合格したために Exchange Server が問題の原因であると結論付けることがあります。

マイクロソフトやハードウェア ベンダによる詳細な調査によって、データベース ファイルが破損する実際の原因になっている捕らえにくい問題が、ハードウェア、ファームウェア、またはデバイス ドライバで発見されることがよくあります。

通常の診断テストでは、いくつかの理由から、すべての過渡的な障害が検知されるとは限りません。ファームウェアやドライバ ソフトウェアの問題は、診断プログラムの機能の範囲外である可能性があります。診断テストは、必ずしも長時間に及ぶ実行や複雑な読み込みを十分にシミュレートできるわけではありません。また、診断監視機能やデバッグ ログの出力を追加することで、これらの問題の再発を防ぐのに十分な変更をシステムに加えることができる場合があります。

チェックサムの生成やデータベース ファイルへのページの書き込みを行う Exchange Server のメカニズムは簡素で安定性が高いことから、-1018 エラーはおそらく、Exchange Server 以外の要因によって引き起こされています。チェックサムと正しくないページの検出メカニズムは単純で信頼性が高く、最初の Exchange がリリースされて以来、データベースのバージョン間でのデータベース ページ形式の変更に適合することを目的としたマイナ チェンジを除き、基本的な変化はありません。

チェックサムは、ページ番号自身を含むすべてのデータがページに書き込まれた後、ディスクに書き込もうとしているそのページに対して生成されます。Exchange Server はページにチェックサムを追加した後、公開されている標準の Windows Server API を使用してディスクにページを書き込むように、Microsoft Windows Server オペレーティング システムに指示します。

チェックサムがページに対して正しく生成されても、そのページがハード ディスク上の間違った場所に書き込まれてしまう可能性があります。これは、"ビット フリップ" などの一時的なメモリ エラーによって発生します。たとえば、Exchange が新しいバージョンの 70 ページを作成するとします。そのページ自体にエラーはありませんが、ディスク コントローラ、またはオペレーティング システムが使用するページ番号のコピーがランダムに変更されました。こうした問題は、不安定なメモリ セルによって 70 (バイナリ 1000110) が 6 (バイナリ 000110) に変化した場合に発生する可能性があります。ページのチェックサムは正しいのですが、データベース内のページの場所が正しくありません。Exchange Server は、論理ページ番号がページの物理的な場所と一致しないことを検出したとき、そのページに対して -1018 エラーを報告します。

(Exchange Server が原因となる) その他のページ番号エラーは、Exchange Server 自体が正しくないページ番号をページ自体に書き込んだ場合に発生する可能性があります。ただし、この場合は -1018 エラー以外のエラーになります。Exchange Server が 70 ページにページ番号 71 を書き込み、70 ページのチェックサムを正しく実行した場合、そのページが 71 の場所に書き込まれ、ページ番号とチェックサム テストのどちらにも合格します。

一般的に、Exchange Server データベースで 1 回の -1018 エラーが報告されても、データベースが停止する原因になったり、-1018 エラー自体が存在する以外の他の症状になることはありません。ページは、あまりアクセスされないフォルダ ([送信済みアイテム] または [削除済みアイテム] フォルダなど) に存在するか、めったに開かれないか、空の添付ファイルに存在します。

1 回の -1018 エラーが大規模なデータ損失を引き起こす可能性はほとんどありませんが、-1018 エラーは記憶域システムが少なくとも一度はデータを保存または取得できなかったことを証明するものであるため、懸念されるエラーです。-1018 エラーは、まったく再発しない一過性の問題である可能性もありますが、徐々に進行する問題を初期の段階で警告するエラーである可能性の方が高い問題です。最初の -1018 エラーがデータベース内の空のページに存在する場合でも、次にどのページが破損する可能性があるのかを知ることはできません。重要なグローバル テーブルが破損すると、データベースは起動せず、その修理も部分的に、または完全に失敗する可能性があります。

-1018 エラーがログに出力されたら、根本的な原因を見つけて取り除くまでは、データベースに障害が発生する危険が迫っているかランダムな破損が発生する可能性を考慮し、そうした事態に備えておく必要があります。

データベース ツリーのバランス維持

ESE の主な機能の 1 つに、データベース ツリーのバランスを常に維持することが挙げられます。ツリーのバランス維持の処理は、すべてのページが分割または結合されたときに終了します。次の図でわかるように、ツリーのルート レベルには常に、ツリーのリーフ レベルと同じ数のノードがあります。したがって、ツリーはバランスが取れています。

3b77fce3-607c-48db-bcf9-e190d7c4e197

note注 :
一般的に、ESE データベース内のツリーは B ツリーと呼ばれていますが、実際には B+ ツリーです。B+ ツリーは、B ツリーの特性をすべて備えていますが、B+ ツリーの各データ ページにはさらにリーフの直前/直後のページを指すページ ポインタがあります。挿入や分割/結合の操作中は、これらのポインタを最新の状態に維持するためのオーバーヘッドがありますが、ポインタによって B+ ツリー構造のデータに対する高速シーケンシャル シークが可能になります。

ESE の観点から見ると、データベース テーブルは B ツリーの集まりです。各テーブルはデータを含む 1 つの B ツリーで構成されますが、多数のセカンダリ インデックスの B ツリーを使用して、さまざまなビューでデータを表示することができます。テーブルの列またはフィールドが広がりすぎて B ツリーに格納できない場合は、"Long-Value ツリー" という名前の個別の B ツリーに分割されます。

これらのテーブルとその関連の B ツリーの定義は、システム カタログという名前の別の B ツリーに格納されます。システム カタログが失われるのは深刻な問題です。したがって、ESE はこの B ツリーの 2 つの同一コピー (4 ページから始まるコピーと 24 ページから始まるコピー) を各データベースに保持します。

分割

ページがほぼいっぱいになると、約半分のデータがセカンダリ ページに配置され、セカンダリ ページの親ページには追加のキーが配置されます。このプロセスは、親ページもいっぱいにならない限り、実行されます。この場合、このページの親ページは分割され、親ページにポインタが追加されます。最終的に、最上位のルート ブロック以下の各ポインタ ページを分割することが必要になることがあります。ルート ブロックの分割が必要な場合は、追加レベルのページがツリーに挿入されます。たとえて言えば、ツリーの高さが伸びます。

結合

ページはほぼ空になると隣接するページと結合され、親ページのポインタが更新されます。さらに、必要に応じてその親ページも結合されます。最終的に、ルート ブロック以下の各ポインタ ページが結合されると、ツリーの高さが低くなります。リーフ (データ) を取得する場合、ESE はルート ノードから開始し、目的のリーフ ノードにたどり着くまでページ ポインタをたどります。

ファンアウト

ESE B ツリーのツリー構造には、非常に高度なファンアウト機能があります。高度なファンアウトとは、4 回以下のディスク読み取り (3 つのポインタ ページとデータ ページ自体) で 50 GB テーブル内のどの部分のデータにもアクセスできることを意味します。ESE は 4 KB ごとに 200 個を超えるページ ポインタを格納します。これによって、最小数の親/子レベルが設定されたツリー ("shallow tree" とも呼ばれる) が使用できるようになります。ESE はさらに、現在のツリーのキーを格納します。このキーは現在のツリーの高速検索を可能にします。上記の図は、3 つの親/子レベルが設定されたツリーです。親/子のレベルが 4 つある 1 つのツリーには数 GB のデータを格納できます。

インデックス

従来の B ツリーでは、インデックスを付ける方法は特定の 1 つの方法しかありませんでした。この方法では、1 つのキーを使用し、データの取得はそのキーを使って行う必要があります。たとえば、メッセージ テーブルのレコードには、メッセージ転送サービス (MTS) ID と呼ばれるメッセージの一意の識別子に対してインデックスが付けられます。ただし、ユーザーは、よりユーザー フレンドリな形式で整理されたメッセージ テーブルにデータを表示することを望むでしょう。

インデックス、中でも特にセカンダリ インデックスは、データの取得に使用されます。各セカンダリ インデックスは、選択されたセカンダリ キーをプライマリ キーにマッピングする別の B ツリーです。これによって、インデックスを付けたデータに比べて B ツリーを小さくします。

セカンダリ インデックスの使用方法を理解するために、メッセージング フォルダにメッセージを表示する方法を変更すると何が起こるのかについて考えます。Outlook のフォルダのビューを受信時刻順ではなく、件名順に変更すると、ストアと ESE はメッセージ フォルダ テーブル上に新しいセカンダリ インデックスを作成します。

大きなフォルダのビューを最初に変更する場合は、遅延が発生します。サーバーを詳しく調べてみると、ディスク処理に小さなスパイクがあるのがわかります。再びそのビューに切り替えると、インデックスは既に作成されており、応答がずっと速くなります。

Microsoft Exchange Information Store サービスのセカンダリ インデックス B ツリーは、8 日間存続します。それらのインデックスが使用されない場合は、Microsoft Exchange Information Store サービスにより、バックグラウンド操作として削除されます。

Long-Value

ESE の列またはレコードは、データ B ツリーのページにまたがることはできません。そのため、4 KB 境界のページを分割する値 (メッセージの本文である PR_BODY など) があります。これらの値を Long-Value (LV) と呼びます。テーブルの Long-Value B ツリーは、これらの大きな値を格納するために使用されます。

ESE テーブルに大きなデータが入力されて、このデータがデータ B ツリーに収まらない場合は、データが 4 KB サイズのページに分割され、別個の Long-Value B ツリーに格納されます。データ B ツリーのレコードには、Long-Value へのポインタが含まれています。このポインタは、Long-Value ID (LID) と呼ばれます。これは、レコードに LID 256 を指すポインタがあることを意味します。

レコード形式

B ツリーの集合は、テーブルを表します。テーブルは行の集合です。行はレコードとも呼ばれます。レコードは多数の列で構成されます。レコードの最大サイズ、つまり 1 つのレコードに表示される列の数は、データベースのページ サイズからヘッダーのサイズを引いた残りのサイズによって決まります。ESE のページ サイズは 4 KB です。したがって、最大レコード サイズは約 4,050 バイト (4,096 バイトからページ ヘッダーの分を引いたバイト数) です。

列データ型

各列の定義では、列に格納されるデータの種類を指定する必要があります。ESE がサポートするデータの種類を次の表に示します。

Extensible Storage Engine の列データ型

列データ型 説明

Bit

NULL、0、または 0 以外

Unsigned Byte

1 バイトの符号なし整数

Short

2 バイトの符号付き整数

Unsigned Short

2 バイトの符号なし整数

Long

4 バイトの符号付き整数

Unsigned Long

4 バイトの符号なし整数

LongLong

8 バイトの符号付き整数

Currency

8 バイトの符号付き整数

IEEE Single

4 バイトの浮動小数点数

IEEE Double

8 バイトの浮動小数点数

Date Time

8 バイトの日付値 (整数部は日付、小数部は時刻)

GUID

16 バイトの一意の識別子

Binary

255 バイト以下のバイナリ文字列

Text

255 バイト以下の ANSI または Unicode 文字列

Long Binary

2 GB 未満の long 値のバイナリ文字列

Long Text

2 GB 未満の long 値の ANSI または Unicode 文字列

列データ型は、以下の 2 つのカテゴリに分類されます。最初のカテゴリは、固定列と可変列です。2 番目のカテゴリはタグ付けされた列です。各列は、16 バイトの FIELD 構造体と列名のサイズを加えて定義されます。

ESE データベースにテーブルが作成されると、テーブルが FIELD 構造体の配列を使用して定義されます。この配列によって、テーブル内の個々の列が識別されます。この配列内では、各列が列 ID と呼ばれるインデックス値で表されます。これは通常の配列と同じで、配列 [0]、配列 [1] のように ID によって配列のメンバを参照できます。列は ID によって高速にアクセスできますが、列名による検索には FIELD 構造体の配列を線形にスキャンする必要があります。

固定列と可変列

固定列には、固定長のデータが含まれます。各レコードは、何も値が定義されていない場合も、定義済みのレコード領域を占めます。データ型 ID 1 ~ 10 は、固定列として定義できます。各テーブルには、最大 126 個の固定列 (列 ID 1 ~ 127) が定義できます。

可変列は最大 256 バイトのデータを含むことができます。オフセット配列は、最上位の変数列のセットを持つレコードに格納されます。各列には 2 バイトが必要です。データ型 ID 10 ~ 11 は、可変列として定義できます。各テーブルには、最大 127 個の可変列 (列 ID 128 ~ 256) が定義できます。

タグ付けされた列

ESE では、ほとんど発生しないか、単一のレコード内で複数発生する列を、タグ付けされた列として定義しています。未定義のタグ付けされた列によって、領域のオーバーヘッドは生じません。タグ付けされた列は、同一レコード内で繰り返し発生する可能性があります。タグ付けされた列は、セカンダリ インデックスで表現され、発生するごとにインデックスで参照されます。

タグ付けされた列には可変長のデータを無制限に含むことができます。列 ID と長さはデータと共に格納されます。すべてのデータ型が、タグ付けされた列として定義できます。各テーブルには、最大 64,993 個のタグ付けされた列を定義できます。