導入
Odooのフィールド定義を見ていると、index=Trueのような小さなフラグに出くわすことがあります。画面上では目立たない属性ですが、検索やフィルタリング、一覧表示の反応速度に大きな差を生むことがある重要な設定です。
本稿では、Odooのデータモデルにおけるインデックス付きフィールドの意味、データベースに与える影響、そしていつそれを使うべきかを分かりやすく説明します。カスタムモジュールの作成時や既存実装のレビュー時に、適切な判断を下せるようになります。
Odooでのインデックス付きフィールドとは何か
OdooのORMでは、フィールド定義にindex=Trueを付けると、そのフィールドに対してインデックスを作成するよう指示されます。モジュールをインストール・更新するタイミングで、PostgreSQL側に対応するインデックスを作らせる仕組みです。
以下は、Pythonでのフィールド定義の簡単な例を示す雛形です。
class SaleOrder(models.Model):
_name = 'sale.order'
reference = fields.Char(string='Reference', index=True)
state = fields.Selection([...], index=True)
partner_id = fields.Many2one('res.partner', index=True)
ユーザーインターフェース上、インデックスがあるかどうかは見えません。フォーム入力や一覧表示を行うユーザーにとっては無関係で、インデックスはデータベース内部の最適化に過ぎません。
インデックスの本質は処理速度です。インデックスがあるとPostgreSQLは大量の行があるテーブルでも条件に合う行を素早く見つけられます。インデックスがなければ全行を順に調べる必要があり、大規模データでは遅くなります。
どのOdooフィールドがindex=Trueに対応しているか
Odoo ORMでは多くのスカラ型フィールドでindex属性が使えます。
- CharやText型のフィールド
- IntegerやFloat型のフィールド
- DateやDatetime型のフィールド
- Selection型のフィールド
- Many2one型(特に頻繁にインデックス化される)
- Boolean型のフィールド
一方、One2manyやMany2manyのような関係フィールドは直接のDB列を持たないためindex属性は適用できません。また、ストアされていない計算フィールドもDB列がないためインデックス化できません。
インデックスはどのように機能するか
モジュールの初期化やアップグレード時、Odooはフィールド定義を読み取りデータベーススキーマを同期します。index=Trueのフィールドには対応するSQLを実行してPostgreSQL上にインデックスを作成します。
標準ではPostgreSQLはBツリー(B-tree)インデックスを作成します。Bツリーは等価比較(=)、範囲検索(>、<、BETWEEN)やソートに適しており、Odooの一覧フィルタやドメイン検索の多くに適用されます。
Odoo ORMとの相互作用
OdooのORMはPythonのドメイン式をSQLクエリに変換します。例えば[('state','=', 'sale')]はWHERE state = 'sale'になります。stateにインデックスがあれば、PostgreSQLは全件スキャンせずにインデックスを使って迅速に該当行を見つけます。
Many2one型のフィールドは典型的な利用例です。たとえば売上注文のpartner_idは関連先のIDを保持します。顧客で絞り込んだ一覧を開く際にWHERE partner_id = Xの条件が走りますが、partner_idにインデックスがあれば何十万件の注文があっても検索が速くなります。
インデックスと書き込み性能
インデックスはコストを伴います。レコードを作成・更新・削除するたびに、PostgreSQLはそのテーブル上の全インデックスを更新しなければなりません。インデックスが多いテーブルでは書き込み処理が若干遅くなります。多くのケースでは検索性能向上のほうが価値がありますが、無差別に全てをインデックス化するのは避けるべきです。
index='trigram'オプションについて
Odoo 16以降では、index属性に'trigram'を指定できます。これはPostgreSQLのpg_trgm拡張と組み合わせたGINトライグラムインデックスを作り、ILIKEのようなあいまい検索を高速化します。商品名や取引先名など部分一致検索が多いフィールドに有効です。
name = fields.Char(string='Product Name', index='trigram')
Odoo標準モジュールでも、部分文字列検索が頻繁に行われるフィールドに対してこの高度なオプションが使われています。
業務上の利用シーン
実務でインデックスが効く代表的なケースをいくつか挙げます。設定によって体感できる差は大きいです。
1. CRM:担当者でのリード絞り込み
営業管理者はパイプラインを担当者で絞ることが多く、crm.leadのuser_idはデフォルトでインデックスされています。数千件のリードでも担当者でのフィルタが高速に動きます。カスタムでユーザーやチームを参照するMany2oneを追加した場合も、同様にインデックス化を考えます。
2. 販売:受注の状態検索
sale.orderのstateはインデックスされており、確定済みや配送待ちといった状態で絞るときに高速です。フィルタとしてよく使う選択項目はインデックスの候補になります。
3. 在庫:商品別の移動履歴の追跡
出荷や製造が多い環境ではストック移動が膨大になります。stock.moveのproduct_idがインデックスされていることで、特定商品に関する動きを素早く抽出できます。インデックスがないとトレースレポートが非常に遅くなることがあります。
4. 会計:仕訳の取引先絞り込み
会計担当者は特定の得意先や仕入先の仕訳を頻繁に見るため、account.move.lineのpartner_idがインデックス化されています。長年の会計データを扱う企業では、このインデックスがないとレポートがタイムアウトしがちです。
5. カスタムモジュール:参照用フィールドの索引化
カスタム開発では外部システムの参照番号やプロジェクトコードといった検索対象のフィールドを持つことが多いです。ユーザーがそのフィールドで頻繁に検索するなら、index=Trueを付けておくとデータ増加後も検索が遅くなりません。
インデックス付きフィールドの作成・カスタマイズ方法
Pythonでの実装(カスタムモジュール開発)
フィールド定義に単純にindex=Trueを追加するだけでインデックス指定は完了します。宣言時のキーワード引数として渡します。
from odoo import models, fields
class ProjectTask(models.Model):
_inherit = 'project.task'
x_external_ref = fields.Char(
string='External Reference',
index=True,
help='Reference number from the external system'
)
フィールドをモジュールに追加したら、odoo-bin -u your_module_nameでアップグレードするか、Appsメニューからモジュールを更新してください。Odooが新フィールドを検知して対応するインデックスをDBに作成します。
既存フィールドにインデックスを追加する場合、継承して定義を上書きする方法もありますが、元のフィールドの振る舞いに影響を与えないよう慎重な取り扱いが必要です。
Odoo Studioの場合
Odoo Studioでは非技術者でもフィールドを作れますが、現状ではStudio画面からインデックス設定を切り替えるオプションは提供されていません。Studioで作られたフィールドはデフォルトでインデックス化されません。
Studioで作成したフィールドを性能面でインデックス化したい場合は、Studioのカスタマイズを正式なPythonモジュールに落とし込み、そこでindex=Trueを付与するのが確実な方法です。通常は開発者の手が必要になります。
PostgreSQL側で直接インデックスを追加する方法
既存の本番DBをモジュールアップグレードせずに最適化したい場合、DBAが直接SQLでインデックスを作ることもあります。
CREATE INDEX CONCURRENTLY idx_sale_order_partner_id
ON sale_order (partner_id);
CONCURRENTLYを使うとインデックス作成中にテーブル全体をロックしないため本番環境で有用です。ただし、Python側のモジュール定義と整合性を保つ必要があり、将来のモジュールアップグレード時にOdooがそのインデックスをどう扱うかは注意が必要です。
実務上のベストプラクティス
検索ドメインに頻出するフィールドをインデックス化する
一覧フィルタや自動アクション、スケジュールジョブ、計算フィールドの依存関係などで頻繁にドメインに現れるフィールドは、インデックス化の優先候補です。多いのはMany2one、状態フィールド、参照コード系のフィールドです。
Odooの慣例に従う
どのフィールドにインデックスを付けるかの良い指標は、Odoo標準モジュールのソースです。sale.orderやaccount.move、stock.moveなど標準定義を確認すると、実運用を前提に開発者がどのフィールドをインデックス化しているか分かります。
高ボリュームモデルのMany2oneは必ずインデックス化する
仕訳や在庫移動、受注行のように長期間で大量に蓄積されるモデルでは、フィルタに使うMany2oneは必ずインデックス化すべきです。書き込み側のコストは増えますが、読み取り性能の改善効果が大きいのが通常です。
テキスト検索にはトライグラムを検討する
Odoo 16以降で、商品名や取引先名など部分一致検索が多いCharフィールドにはindex='trigram'を検討してください。トライグラムはILIKEベースの部分一致検索に最適化されています。
インデックスが実際に使われているか確認する
インデックス追加後はEXPLAIN ANALYZEでクエリ実行計画を確認しましょう。プランナーがシーケンシャルスキャンを選ぶ場合、テーブルが小さすぎるか、クエリ条件がインデックスに合っていない可能性があります。
インデックス化の判断をドキュメント化する
カスタムモジュール開発時には、なぜそのフィールドをインデックス化したか簡単にコメントを残しておきましょう。将来の開発者が意図を理解でき、リファクタ時に誤って除去されるのを防げます。
よくある落とし穴
すべてのフィールドをデフォルトでインデックス化するべきか?
学習段階で犯しやすいミスは、念のためと全フィールドにindex=Trueを付けてしまうことです。これは逆効果になります。各インデックスはストレージを消費し、書き込み時の負荷を増やします。高頻度で更新されるテーブルでは不要なインデックスがボトルネックになり得ます。
小さなテーブルへのインデックス
数百行程度の小さなテーブルでは、PostgreSQLのクエリプランナーがシーケンシャルスキャンの方が速いと判断することがよくあります。テーブルが数千行以上になって初めてインデックスの恩恵が顕著になります。小規模なルックアップテーブルやほとんど使われないカスタムモデルにインデックスを付けるのは無意味なことが多いです。
index=Trueを追加してモジュールをアップグレードし忘れる問題
単にPython定義にindex=Trueを書いただけではデータベースに自動的にインデックスは作成されません。-u module_nameでモジュールをアップグレードするか、バックエンドから更新する必要があります。開発段階でこの手順を忘れると、本番やステージングで性能問題が発生する原因になります。
BツリーがILIKE '%keyword%'を速くすると思い込む誤解
通常のBツリーインデックスは先頭にワイルドカードが付いたILIKE '%keyword%'の検索では役に立ちません。先頭ワイルドカード付きの部分一致はBツリーで評価できないため、部分一致検索を速くしたい場合はindex='trigram'や全文検索の導入を検討してください。
計算フィールドのうちストアされているものを見落とさない
store=Trueのついた計算フィールドはDB列を持つためインデックス化可能です。集計や派生値をフィルタで多用する場合、ストア済み計算フィールドにインデックスを付けることでパフォーマンス改善につながることがあります。見落としがちな改善ポイントです。
まとめ
index=Trueは小さな設定ですが、データ量が増えたときに検索速度に大きく影響します。適切に使えば一覧やレポートの応答性を維持できますが、安易に使うと書き込み負荷や運用コストを増やすリスクがあります。
まとめると、ドメインフィルタで頻繁に使うフィールド、特に高ボリュームモデルのMany2oneはインデックス化すべきです。Odoo標準の設計を参考にし、小規模テーブルやほとんど検索されないフィールドの過剰なインデックス化は避けましょう。Odoo 16以降ではテキスト検索用にindex='trigram'を検討するのがお勧めです。
カスタム開発の早い段階で正しいインデックス戦略を決めておくことは、本番での遅いクエリを後から解析するよりもはるかに楽です。
Odoo導入作業を進めていますか?
Dasoloでは、Odooの導入・カスタマイズ・パフォーマンス最適化を支援しています。カスタムモジュールの開発や既存環境の改善、新規プロジェクトの設計など、実務に即した技術支援を提供します。
クエリが遅い、カスタマイズが複雑、Odoo開発のベストプラクティスについて相談したい、という課題があればご相談ください。 Dasoloチームへお問い合わせください。 どのような案件に取り組んでいるか教えてください。