【実務・中級編】 二次索引(セカンダリインデックス)の概念 – 階層型DBMS

階層型DBMSの真髄:二次索引(セカンダリインデックス)の極意

おい、設計レビューの手を止めてこっちを向いてくれ。

今、君が担当しているそのデータモデリング、本当に「階層型DBMSの特性」を理解した上で組まれたものか? リレーショナルデータベース(RDBMS)の頭のままで、`WHERE`句の条件だけでホイホイとセカンダリインデックスを貼ろうとしていないか?

IBMのIMSなどをルーツに持つ階層型DBMSにおいて、データは厳格なツリー構造(親子関係)として物理配置される。ルートセグメントから子、孫へと至るパス(物理ポインタ)を辿るアクセスこそがこのアーキテクチャの最大の強みであり、数百万件のレコードであってもO(1)に近い爆速のルート走査を実現する。

しかし、現実はどうだ?
「親IDでの検索しかできないのは困る」「顧客名やメールアドレスといった、階層パスとは無関係のキーでランダムアクセスしたい」というビジネス要件が必ず降ってくる。

ここで登場するのが、「二次索引(セカンダリインデックス)」だ。

今回は、階層型DBMSの冷徹な物理構造を理解した上で、セカンダリインデックスをどう定義し、どう運用すべきか。レビューの場で私がいつも叩き込んでいる「極限の知見」を授けよう。

—

1. 階層型における「二次索引」の残酷な真実

まず前提を叩き込む。階層型DBMSにおけるデータは、ポインタチェーンによってガチガチに結合されている。リレーショナルなB+Treeインデックスとは異なり、セカンダリインデックスは「本来の階層構造をねじ曲げる異物」だ。

セカンダリインデックスの概念はこうだ:

  • ターゲット(非依存セグメント): 検索したいデータが存在する本来のセグメント。
  • インデックスデータベース(独立DB): 検索キーと、ターゲットセグメントを指し示すポインタ(あるいはルートからの物理パス)を保持する専用の索引構造。

つまり、セカンダリインデックスを使った検索は、「一度インデックス用の独立したデータベースを引いてターゲットの物理アドレス(またはポインタ)を手に入れ、それから本来のデータセグメントにジャンプする(双方向のオーバーヘッド)」という裏の処理を行っている。

これを忘れると、パフォーマンスチューニングで痛い目を見る。

—

2. スキーマ定義(DDL)とインデックスデータベースの構築

百聞は一見にしかずだ。代表的な階層型DBMSの定義言語(ここでは概念的なDBD/PSB定義をモダンに解釈した擬似DDLとする)を見てみよう。

以下の例では、親セグメント `COMPANY` の下に子 `DEPARTMENT`、さらにその下に `EMPLOYEE` がぶら下がるツリー構造に対し、`EMPLOYEE` のメールアドレス (`EMAIL`) で高速検索するためのセカンダリインデックスを定義する。

— ==========================================
— 1. プライマリ(メイン)データベースの定義
— ==========================================
DATABASE DEFINITION corp_db
— ルートセグメント:会社
SEGMENT COMPANY
FIELDS (
company_id CHAR(5) PRIMARY KEY,
company_name CHAR(50)
);

— 子セグメント:部門(COMPANYに従属)
SEGMENT DEPARTMENT
PARENT COMPANY
FIELDS (
dept_id CHAR(4) PRIMARY KEY,
dept_name CHAR(30)
);

— 孫セグメント:従業員(DEPARTMENTに従属)
SEGMENT EMPLOYEE
PARENT DEPARTMENT
FIELDS (
emp_id CHAR(6) PRIMARY KEY,
emp_name CHAR(40),
email CHAR(50), — ←このカラムでセカンダリインデックスを張りたい
salary DECIMAL(9,2)
);

— ==========================================
— 2. セカンダリインデックス(索引)データベースの定義
— ==========================================
— 階層パスとは無関係に、emailからEMPLOYEEを直接引くための独立DB
INDEX DEFINITION emp_email_idx
— 索引のターゲットとなる実データ側のセグメントを指定
TARGET corp_db.EMPLOYEE

— インデックスのソートキー(検索条件になる項目)
INDEX_KEY (email)

— ポインタ保持方式の指定(ダイレクトポインタ方式:実セグメントの物理アドレスを保持)
POINTER DIRECT;

設計上の急所:

ここで `POINTER DIRECT` を指定している点に注目してほしい。
インデックスDB側がターゲットセグメントの物理アドレスを直接保持するため、ヒットした瞬間に一発でデータに到達できる。しかし、もし基底のプライマリDB側でレコードの再配置(ストレージの再編成:Reorganization)が発生し、物理アドレスが変わると、このインデックス全体の再構築(あるいはポインタの動的パッチ当て)が必要になる。
データの更新頻度が高い環境でダイレクトポインタを乱用すると、夜間バッチのReorg地獄が待っている。更新が多いカラムには `SYMBOLIC(シンボリックポインタ:検索キー文字列を保持し、再検索で解決する方式)` を選ぶ勇気を持て。

—

3. 実務で使える堅牢な設計パターン

コードレビューで「インデックスを全部貼れ」と言い出すジュニアがいたら、即座にペンを置かせること。セカンダリインデックスの設計には、プロのアーキテクトとしての冷徹なトレードオフ判断が必要だ。

パターンA:スパース・インデックス(部分索引)の活用

すべてのレコードにインデックスを作る必要はない。例えば「退職した従業員」のデータまでインデックスに乗せて容量を圧迫し、バッチの更新コストを上げるのは悪手だ。
有効なステータス(例: `status = ‘ACTIVE’`)のセグメントのみをターゲットにするフィルター条件を定義できるDBMSであれば、確実にそれを適用しろ。

パターンB:コンポジット(複合)インデックスの順序設計

検索クエリが `WHERE dept_id = ? AND email = ?` の場合、インデックスキーの順序が命取りになる。
階層型であっても、セカンダリインデックスのキーに複数項目を含む場合、カーディナリティ(値の分散度)が高いものを先頭に置くのが鉄則だ。
ただし、階層型のセカンダリインデックスでは「どのセグメントのどのフィールドをルートにするか」が物理的なディスクI/Oに直結するため、RDBMS的な感覚で安易にカラムを追加するな。

—

4. パフォーマンス上の注意点とアンチパターン

最後に、現場でよく見る「やらかし事例」を挙げておく。これらに該当していたら、今すぐ設計書を書き直せ。

1. 「とりあえず全カラムにインデックス」の呪い

  • 現象: 検索性能は上がるが、子セグメント(`EMPLOYEE`)の挿入(`INSERT`)や更新(`UPDATE`)が発生するたびに、インデックスDB側のツリーも芋づる式にロックされ、更新スループットが劇的に低下する。
  • 対策: 参照頻度と更新頻度(Read/Write Ratio)を必ず計測しろ。R:Wが 9:1 なら許容できるが、1:1 に近いならセカンダリインデックスは悪だ。

2. シーケンシャルなキーによるインデックスホットスポット

  • 現象: 自動採番のタイムスタンプや連番をセカンダリインデックスのキーに指定すると、インデックスの右端(末尾)のノードにすべての書き込みが集中し、ラッチ競合(Latch Contention)を引き起こす。
  • 対策: ハッシュ化プレフィックスを付与するか、そもそも階層パスでのアクセスに落とし込む設計変更を検討しろ。

3. カバリング・インデックスの不在による二度手間

  • インデックス側だけでクエリが完結せず、必ず実データセグメントまでフェッチしにいかなければならない構造になっている場合、大量件数のレンジスキャン(範囲検索)を行うと、ランダムI/Oの嵐でシステムが沈黙する。

—

結びにかえて

階層型DBMSにおけるセカンダリインデックスは、「王道である親子ポインタによる高速ツリー走査という美しさを、現代の非構造的な検索要件のためにあえて汚す、諸刃の剣」だ。

リレーショナルデータベースのように「困ったらINDEX」という思考停止は即座に捨てろ。
「このセカンダリインデックスは、システム全体のバッチ処理時間とストレージ容量を犠牲にしてまで、本当にそのオンライン検索要件を満たす価値があるのか?」

その問いを自分自身に投げかけられないうちは、私のレビューは絶対にパスさせない。
さて、設計書の修正にかかるとしようか。

コメント

タイトルとURLをコピーしました