■ 1. EAVとは何か
- EAV(Entity-Attribute-Value)はデータを「何が(Entity)」「どんな属性を持ち(Attribute)」「その値は何か(Value)」の3要素で表現するデータモデル
- 通常のRDB設計では属性をカラムとして定義するが、EAVでは属性を「行」として表現する
- 属性の追加はattributesテーブルへのINSERT1件で完結し、DDL変更が不要
■ 2. EAVがアンチパターンとされる理由
- SQLの複雑化:
- 通常なら単純なSELECTで済むクエリが、属性ごとにJOINを要する複雑なものになる
- データ型制約の欠如:
- value列がTEXTやVARCHARになりがちで、RDBMSの型安全性が失われる
- 外部キー制約の使用不可:
- 汎用的なvalue列ではFOREIGN KEYによる参照整合性の維持が困難
- パフォーマンス劣化:
- エンティティ1件あたり属性の数だけ行が生まれ、行数が爆発的に増加する
■ 3. EAV採用の判断基準
- 採用を検討すべき条件:
- 属性の数が数千〜数十万規模で、事前に確定できない場合
- 属性の追加・変更がビジネス要件として頻繁に発生する場合
- ほとんどのエンティティが属性の一部しか持たないスパースなデータの場合
- 避けるべきケース:
- 属性が数十個以下で固定されている場合
- 複雑な集計やレポーティングが主要なユースケースの場合
- チームにEAVの運用経験がない場合
- 代替案として検討すべき技術:
- JSONBカラム(PostgreSQL)
- ドキュメントDB(MongoDB等)
- ワイドテーブル+パーティショニング
■ 4. 事例1: 財務諸表プロジェクト(Speeda)
- 背景:
- 複数企業グループ・複数会計基準・複数国をまたぐ数十万件の勘定科目を管理
- 勘定科目をカラムとして設計するとRDBMSのカラム上限に抵触し、かつALTER TABLE運用が非現実的
- EAVの適用:
- 勘定科目そのものをAttribute、企業をEntityとして扱い、金額をValueで表現
- 新規勘定科目の追加はINSERT1件で完結し、DDL変更・アプリ改修が不要
- スパースなデータに対してNULLを持たずに必要な行だけを格納できる
- 時間軸の組み込み(period_id):
- EAVテーブルにperiod_idカラムを追加し、主キーを(entity_id, attribute_id, period_id)の3カラム構成に変更
- 財務諸表データを「誰の・何が・いつの・いくら」という4次元のデータキューブとして表現
- 期間はすべてのデータに共通する固定軸として扱い、EAVの動的な属性部分と分離
- インデックス設計:
- アクセスパターンを事前に洗い出し、逆算した複合インデックスを設計
- 数十億件規模でも実用的なレスポンスタイムを実現
- パフォーマンス問題の本質は行数ではなくアクセスパターンを無視したインデックス設計にある
- 考慮点:
- マイグレーション管理: 属性マスタの変更もシードデータとしてバージョン管理に含め環境間の一貫性を維持
- ORM非対応: EAVの読み書きを抽象化するリポジトリ層を独自実装し、アプリ側のコードからEAVの存在を隠蔽
■ 5. 事例2: ID管理プロジェクト(YESOD)
- 背景:
- 人・組織・会社・オフィス・プロジェクトという複数種類のEntityとその関係性を管理
- Entity間の関係性(所属・アサイン・勤務地等)を通常設計で表現すると中間テーブルが増殖し、要件変化のたびにDDL変更が発生
- ReferenceIdの導入:
- EAVテーブルにreference_idカラムを追加し、entitiesテーブルへの外部キーとして定義
- プリミティブな値はvalue列、Entity間の関係性はreference_id列で使い分け
- 外部キー制約によりRDBMSの仕組みの中で参照整合性を保証
- 新たな関係性の追加はattributesテーブルへのINSERT1件で完結
- Entityの拡張性:
- entitiesテーブルにentity_typeカラムを持たせるだけでEntity種類を追加可能
- 要件が流動的なプロジェクトでのテーブル構造変更なしの拡張が実現
- バイナリ格納と型安全なシリアライズ(Kotlin):
- value列をBYTEA型として定義し、すべての値をシリアライズしたバイナリで格納
- Kotlinのsealed interfaceでAttributeValueの型を定義し、data_typeに基づくシリアライザを一箇所に集約
- コンパイル時にwhen式の網羅性チェックで型の不整合を検出
- TEXT型と比べて数値精度の劣化がなく、「とりあえず文字列で入れておく」運用上の怠慢を設計で防止
■ 6. まとめ
- EAVがアンチパターンとされる主因は、不適切な条件下での採用の歴史にある
- 属性が動的で大量・データがスパース・スキーマ変更を最小化したい条件が揃う場合はEAVが合理的な設計選択になる
- EAVの弱点(型安全性・参照整合性・パフォーマンス)はそれぞれ実践的な工夫で対処可能
- 型安全性: バイナリ格納+言語側のシリアライズ機構
- 参照整合性: reference_idによる外部キー制約
- パフォーマンス: アクセスパターンを逆算した複合インデックス設計
- EAVの弱点を正しく理解した上で補完策とセットで採用することが重要