tuning-oak-query-indexes について
Oak クエリのインデックス適用範囲を調べる、AI エージェント向けの手順です。クエリとインデックス定義の両方を基に、スコープやプロパティの不足を特定し、固定項目と環境依存項目を分けて提案します。
主な機能
- 01クエリ項目とインデックスプロパティの照合
- 02ノード型とパスのスコープ確認
- 03静的項目と動的項目の分類
- 04評価に必須なインデックス定義
- 05可能な場合に explain でプラン検証
活用シーン
- 01Oak クエリがノード走査する原因の調査
- 02変更前のインデックス定義レビュー
- 03設定依存の項目名を持つクエリの監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ adobe/ skills --skill tuning-oak-query-indexesインストール後、Claude Code で/tuning-oak-query-indexesを入力して読み込まれたか確認します。
同梱ファイルreferences/ai-agent-indexing-guide.mdreferences/extracting-jcr-queries.md
詳細
Oak クエリーインデックスの調整
概要
Oak クエリーがインデックスを使用するのは、インデックスが実際にカバーしている部分だけです。一致するプロパティ定義がなく、適切なフラグも設定されていない WHERE/ORDER BY/全文検索のフィールドは、すべてノードごとにメモリー上で評価されます。ノードタイプやパスのスコープが間違っている場合も同様です。すべてのフィールドにインデックスが設定されていても変わりません。 このスキルでは、カバーされていないフィールドや条件を正確に特定し、それぞれの問題を解消するインデックス定義の変更を見つける手順を説明します。
以下のすべての記述の根拠:AI エージェントのインデックス作成ガイド。このスキルと一緒にバンドルされているため、どのコードベースにコピーしても同じように使用できます。プロパティの正確な一覧、デフォルト値、コストモデルの詳細、エッジケースについては、必要に応じて参照してください。このスキルはチェックリストであり、リファレンスではありません。
このスキルの前提は、「クエリーとインデックス定義がある」ことだけで、使用するコードベースについては何も仮定しません。 AEM インスタンス、稼働中の Oak リポジトリ、Oak 自身の Java ソースのチェックアウトは必要ありません。これらがあると、記載のある箇所(手順 6 と、以下の「explain で到達できない場合」の代替手順)で確信度が高まりますが、必須ではありません。クエリーを読み、インデックス定義を読み、ギャップを見つけ、修正案を提示するという基本手順は、この 2 つの成果物だけで実行できます。実際のプロジェクトのインデックス定義は、生の JSON ではなく、FileVault XML(/apps/.../install 配下のコンテンツパッケージにある .content.xml、jcr:primaryType="oak:QueryIndexDefinition" など)であることがよくあります。同じ方法で読み取ってください。異なるのはシリアライズ形式だけで、プロパティ名と意味は同じです。
手順
必須入力 — インデックス定義。これがなければ、このスキルは実行できません。 評価全体は、クエリーと、それが使用できるインデックス定義との比較であるため、インデックス定義は補助的な情報ではなく必須入力です。提供されていない場合は、提示を求めてください。次のいずれでも受け付けます。/apps/.../install パッケージにある FileVault の .content.xml、JSON エクスポート、または GET /system/console/status-oak-index-defn.json で取得したライブダンプ(管理者認証が必要)。どれも入手できない場合は、このスキルを実行せず、その旨を明示してください。クエリーだけを基にカバレッジを評価してはいけません。何がインデックスされているかについての推測は調査結果として受け取られ、実際の対応につながります。誤った回答をするより、回答しないほうがよいでしょう。(クエリーももう 1 つの必須入力です。直接提示するか、バンドルされているJCR クエリーの抽出ガイドを使って一覧を作成してください。)
前提条件:以下の手順 1 と 6 では、ライブインスタンスのツール(explain、Felix の InventoryPrinter、Oak の trunk テストスイート)に触れていますが、いずれも必須ではありません。クエリーのテキストとインデックス定義があれば、それらのツールに関するサブ項目を省略し、手順 2~5 と 7 を静的解析のみで実施して、最終セクションに従って検証状況を「推論に基づくが未検証」と明記してください。
- クエリと、それが使用する可能性のあるインデックス定義をすべて取得します — 想定したものだけでは不十分です。
クエリをまだ入手していない場合 — たとえば、渡された1つのクエリの調整ではなく、アプリ全体やバンドル全体の監査を依頼された場合 — まず、付属の
JCR クエリの抽出ガイドに従って、コードベースが実際に発行するすべての
JCR/QueryBuilder クエリを特定します(ラッパー/ヘルパー/DAO コードを通じてプログラムで組み立てられるものも含みます。テキスト検索だけでは見つかりません)。その出力(構築箇所と、すべてのトリガー/呼び出し元の経路)を、このスキルの評価対象として、クエリごとに扱います。稼働中のインスタンスに接続できない場合は、実際に提供されたインデックス定義をすべて基に作業し、レポート(§ レポート形式)にその旨を記載します — 候補の選定は確認済みではなく、推論に基づくものになります。稼働中のインスタンスに接続できる場合は、
explainにより実際に選択されるインデックスがわかります。これは定義だけに基づく推論より確実です。リポジトリ内のすべてのインデックス定義(パスごとの検索では見落とすルート以外の定義も含む)を列挙するには、Oak 自身が登録する FelixInventoryPrinterステータスページを使用します。AEM に限らず、Sling/Felix ベースのあらゆる Oak デプロイメントで利用できます。GET /system/console/status-oak-index-defn.json(管理者認証)。すべてのインデックス定義をダンプします(設定のみで、非表示の:data/:indexストレージノードは含みません)。oak-run の--index-definitions-fileで使用されるものと同じ、nam:/str:/dat:/... の型プレフィックス付き JSON エンコーディングです。候補となるインデックス名を1つずつ推測するより、1回のリクエストで済みます。 - 分析を始める前に、すべてのクエリを2つのセクションに分けます — セクション A(静的フィールド)とセクション B(動的フィールド)です。
- セクション A — 静的フィールド:クエリがフィルタリング、並べ替え、または検索するプロパティのパス/名前がソースコード内で固定されています — リテラル文字列または名前付き定数です。比較対象の値が実行時に変わる場合(呼び出し元から渡される ID、ループ変数、値としてのみ使われるリクエストパラメーターなど)も同様です。正確な
WHERE/ORDER BYフィールド名を推測せずに示せる限り、クエリはセクション A に分類します。 - セクション B — 動的フィールド:プロパティのパス/名前そのものがソースコード内で固定されていません — OSGi 設定(
detectMetadataField形式のプロパティ)、リクエスト/述語マップのキー、コンテンツ側で作成されたクエリ(dam:queryノード、スマートコレクション)、またはスクリプト/ネイティブクエリ文字列に由来します。外部入力なしでは、インデックスがカバーすべきプロパティを特定できません。 **セクション A を完全に評価します — これが主な成果物です。**セクション B については、ありそうなフィールド名やデフォルトのフィールド名を推測し、その推測をクエリの実際の対象であるかのように評価してはいけません。代わりに、各セクション B のクエリについて、不足している具体的な入力(例:「detectMetadataFieldの OSGi 設定値」、「このサーブレットに渡されるtypeリクエストパラメーター」)を列挙し、ユーザーに確認します。ユーザーから、そのデプロイメントで実際に使用される具体的なフィールド名が提示された場合に限り、セクション B のクエリを実際の評価に含めます — 以降は、その回答をセクション A の事実と同様に扱います。 | それでも評価してしまう言い訳 | 実態 | |---|---| | 「デフォルト値はおそらく X なので、但し書きを付けて記載します」 | 但し書き付きの推測でも評価として読まれ、その前提で対応されます。デプロイ環境で設定が上書きされていれば、カバレッジ表全体が誤りになります。デフォルト値を評価するのではなく、不足している入力を明示してそこで止めてください。 | | 「部分的な評価でも役に立ちます」 | 不完全でも誤った評価は、評価がないより悪いものです。レポートでは検証済みの所見と区別できません。Section B に分けて、質問してください。 | | 「Technique 1 ですでにデフォルトのケースを扱いました」 | デフォルトを知っていることと、設定済みの値を知っていることは同じではありません。推測せず、評価の前に確認してください。 | | 「質問するとレポートの流れが途切れます」 | レポートは引き続き完結しています。Section A はそれだけで成立します。ユーザーが回答すれば、Section B もすぐに評価可能になります。 |
- セクション A — 静的フィールド:クエリがフィルタリング、並べ替え、または検索するプロパティのパス/名前がソースコード内で固定されています — リテラル文字列または名前付き定数です。比較対象の値が実行時に変わる場合(呼び出し元から渡される ID、ループ変数、値としてのみ使われるリクエストパラメーターなど)も同様です。正確な
- クエリが参照するフィールド/構造をすべて抽出します:
WHEREの等価/範囲/IN/IS NULL/IS NOT NULL、ORDER BYの列、contains(...)/全文検索、rep:facet/rep:similar/rep:suggest/rep:spellcheck、パス制限(ISDESCENDANTNODE///)、およびクエリ自体のノードタイプ制限 (FROM [type]/XPath のelement(*, type))。- AEM QueryBuilder の述語マップ — フィールド名は通常、キーではなく値です。
property、relativedaterange、daterange、boolproperty、similarの述語タイプでは、マップの キー(1_property、5_relativedaterange.propertyなど)は述語タイプのトークンとグループ インデックスにすぎません。これはどの述語かを示すものであり、どの JCR プロパティかを 示すものではありません。インデックスのカバレッジを確認する対象となる実際のフィールドは、 そのキーに割り当てられた文字列値です。たとえば、map.put("1_property", "jcr:content/hasValidMetadata")の場合、インデックスカバレッジを確認するフィールドはjcr:content/hasValidMetadataであり、リテラル文字列1_propertyではなく、値から 読み取ります。同様に、map.put("1_relativedaterange.property", "jcr:created")の場合、 フィールドはjcr:createdです。これを理由にステップ 2 の Section A/B の判断を誤らないで ください。この場合のフィールド名も、SQL2/XPath の条件にある一般的なWHERE句のフィールド には見えなくても、ソース内のハードコードされた文字列リテラルです(Section A)。その値自体が リテラルではなく変数/リクエストパラメーターである場合に限り、Section B になります。
- AEM QueryBuilder の述語マップ — フィールド名は通常、キーではなく値です。
- 各フィールドについて、必要なフラグを確認します(下表)。一致する
nameを持ち、そのフラグが 設定されたプロパティ定義がインデックスにあることを確認します。 - 何よりも先に、スコープに関する2つの落とし穴を確認します。フィールドがどれほど適切に
インデックス化されていても、これらに該当するとインデックス全体が対象から外れます。見た目には
正しいインデックスが選択されない、最もよくある理由です。
- ノードタイプのスコープ:クエリ自体の
FROM/element()制限が、インデックスで宣言された 型(またはサブタイプ)を指定していますか?クエリに制限がなく(単独の*/暗黙のnt:base)、インデックスが1つの型に対するルールのみを宣言している場合(プロパティ インデックスのdeclaringNodeTypes、または Lucene でnt:baseを含まないindexRules)、 インデックスはプランナーから見えません。コストはInfinityになり、候補リスト自体から 省かれることもあります。インデックスを変更するのではなく、クエリにノードタイプ制限を追加して 修正してください。 - パスのスコープ(Lucene のみ):
includedPathsとqueryPathsは互いに一致していますか? クエリの実際のパス制限は、それらの範囲内ですか?includedPathsだけを狭めてqueryPathsを デフォルト(/)のままにすると、選択されないよりも悪い結果になります。実際のカバレッジ範囲外で インデックスが選択され、実在する結果より少ない結果をひそかに返すためです。
- ノードタイプのスコープ:クエリ自体の
- 仕様から推測するだけでなく、実際に検証します。
explain <query>(または AEM Query Performance ツール)を実行します。次の点を確認してください:(a)期待するインデックスが候補/ コストのログに実際に表示されること、(b)そのコストが、トラバーサルを含むすべての代替候補より 低いこと、(c)プランにtraverseがなく、カバレッジ対象だと考えているフィールドに未 インデックス化の残余条件がないこと。コストが有限であることだけでは、特定の条件がネイティブに 処理されている証明にはなりません。実環境で確認された例では、通常の有限コスト(ノードタイプ/ パスのスコープだけでトラバーサルを上回る)で選択されたインデックスのプランに、実際のluceneQueryとして*:*だけが記載されていました。つまり、インデックス化されていないプロパティ の等価条件はインデックスに渡されず、残余のインメモリフィルターとして評価されていました。 何らかのインデックスが有限コストになったかどうかだけでなく、対象のフィールドについて、ログに 出力されたluceneQuery/プランのテキスト自体を必ず確認してください。explainで分かるのは、どのインデックスとコスト/プランが選択されるかだけです。コストプランの 仕組みを介して動作しない機能が正しく動作するかは分かりません。候補には、サジェスト (rep:suggest)、スペルチェック(rep:spellcheck)、ファセットの件数、類似度ランキング (rep:similar)、rep:nativeが含まれます。これらはexplainで確認するのではなく、 個別に検証する必要があります。実際の情報を得る2つの方法(実稼働インスタンスの設定/MBean との照合、または実行する正確な Oak trunk のユニットテスト)については、インデックス作成 ガイドの「explainでは確認できない機能の検証」 (付録 B)を参照してください。どちらも利用できない場合は、推測せず、その旨をレポートに明記して ください。
- 変更を書く前に、修正を適用すべき既存インデックスを1つ特定します。 新しいインデックスを
勝手に作成したり、候補になりそうな複数のインデックスから黙って1つを選んだりしてはいけません。
対象のインデックスによって推奨内容全体(既存のプロパティ、現在のタグ/ウェイト、再
インデックス化の履歴)が変わるため、選択を誤るとステップ 8 の「修正済みインデックス定義」も
誤ったものになります。
- 稼働中のインスタンスに接続できる場合:クエリをそのまま使って
explainを実行します。 不足があると想定していても、ここで必要なのは合否だけでなく、レスポンスが返す候補リストと インデックスごとのコストです。explainのレスポンスには、勝者だけでなく検討されたすべての 候補インデックスとそのコストが列挙されます。先頭行だけでなく、そのリストを確認してください。 ステップ 5 のノードタイプ/パスのスコープにも一致する、最もコストの低いインデックスが対象です。 インデックス名だけでなく、読み取ったコストの数値も明記し、選択を検証できるようにしてください。 - 稼働中のインスタンスがない場合:与えられたすべてのインデックス定義を列挙し、クエリに
ノードタイプとパスのスコープ(ステップ 5)が一致するものを確認します。リポジトリに
インデックスファイルがいくつあっても、実際の候補はこれだけです。次に:
- 一致する候補がちょうど1つ → それを対象として、作業を進めます。
- 一致する候補が0個 → 拡張できる既存インデックスはありません。その旨を明示し、最後の 手段としてのみ新しいインデックスを提案してください(新しいインデックスの作成が正しいのは この場合だけですが、黙って「修正」として提示せず、その旨を明記する必要があります)。
- 一致する候補が複数(競合するインデックス) → 黙って1つを選んではいけません。これは、
上のリストにある「プロパティはインデックス化されているが、別のより低コストなインデックスが
依然として選択される」という誤りとまったく同じです。ただし今回は、
explainで得られるはずの コスト情報なしに、あなた自身が選ぼうとしています。候補を並べて比較してください。それぞれに ついて、クエリのフィールドのうちすでにカバーしているもの、tags、weight(設定されている 場合)、includedPaths/queryPaths、および現在のスコープの絞り込み具合を示し、どの インデックスに変更を適用するかユーザーに尋ねてください。ユーザーが選ぶまでは、どの候補についても 修正済み定義を提案しないでください。
- 稼働中のインスタンスに接続できる場合:クエリをそのまま使って
- すべてのフィールドに必要なフラグを設定する最小限の修正を提案します。用途ごとに重複する
複数のインデックスを作るより、ノードタイプごとに一貫性のあるインデックスを1つ使用してください。
変更には
refresh: trueではなくreindex: trueを設定してください。ただし、新しい/変更された プロパティの値を持つ既存コンテンツが一切ないと確実に確認できる場合は例外です。refresh: trueで可能になるのは、新しいプロパティ定義をプランナーが候補にできること(explainで候補として表示 されること)だけです。定義が存在する前から値を持っていた既存ドキュメントへの反映は行われません。 実環境で確認された例では、refresh: trueのみで追加したプロパティはすぐexplainに表示され ましたが、既存ドキュメントの値を実際に検索しても、reindex: trueを実行するまでは結果が0件 でした。すべてのドキュメントでそのプロパティが本当に新しいと確信できない場合は、再 インデックス化してください。インデックスを変更する前に、クエリを絞り込むほうが少ないコストで 効果を得られないか確認してください。クエリを制約するほうが、インデックスを広げるより常に低コストで 安全です。優先順位は次のとおりです。- ノードタイプ制限(
element(*, T)/FROM [T])を追加/絞り込みます。最も効果の大きい変更です。 上記のノードタイプのスコープに関する落とし穴を参照してください。実際の対象タイプが分かっている 場合、単独の*/nt:base制限は使わないでください。 - パス制限(ユースケースで許容される範囲まで絞り込んだ
ISDESCENDANTNODE///)を追加/ 絞り込みます。これにより、トラバーサルへのフォールバックコストが下がり、インデックスがパスで スコープされている場合は、引き続き選択対象になります。 - プロパティ条件を追加/厳密化します。選択性の高い等価条件/範囲条件を増やすほど、インデックス内で
処理される作業が増え、読み出すデータが減ります。
ユースケースで真に必要な範囲までクエリを絞り込んだ後に限り、インデックス定義そのものを広げて
ください。インデックスの拡張は、クエリテキストを絞り込むよりコストが高く、変更の影響も長期に
わたります(ストレージ容量が増え、インデックス作成に時間がかかり、それを使用するほかのすべての
クエリに影響します)。一方、クエリテキストの絞り込みは無料で、そのクエリリクエストにのみ適用されます。
修正済みインデックス定義(JSON/コンテンツ差分)の作成と、それを適用することは別です。
レビュー用に未依頼で作成するのは問題ありませんが、稼働中のインスタンスへの実際の適用(Sling
POST/
curl -F、.content.xmlのコミット、その他の書き込み)は、ほかのリポジトリ変更と同様に、 ユーザーからの明示的な許可を得てから行う必要があります。インデックス作成ガイド の付録 B で作成/変更/削除コマンドについて求めているのと同じです。
- ノードタイプ制限(
- 変更を適用した後(該当する場合は非同期サイクルの完了後)に
explainで再検証します。残余/ インメモリのフィルタリングが残っていないこと、およびORDER BYがインメモリの全件ソートではなく、 インデックスによって処理されることを確認してください。
Field の構成 → 必須のインデックスフラグ
| クエリの構成 | Property インデックス(type=property) | Lucene のプロパティ定義 |
|---|---|---|
等価条件 / IN | propertyNames に指定 | propertyIndex: true |
IS NOT NULL | propertyNames に指定(プロパティインデックスには存在確認専用のフラグがありません) | propertyIndex: true、または値に対する等価条件/範囲/順序付けが不要な場合は、より低コストな notNullCheckEnabled: true のみ(下記のコストに関する注記を参照) |
IS NULL | 該当なし — type=property には仕組みがありません | nullCheckEnabled: true(単独で指定でき、propertyIndex は不要) |
ORDER BY | サポートされていません | ordered: true + 明示的な type |
contains(prop, ...) | サポートされていません | analyzed: true |
contains(., ...)(ノードスコープ) | サポートされていません | analyzed: true + 対象となるすべてのプロパティで nodeScopeIndex: true |
rep:facet | サポートされていません | facets: true |
範囲(<,>,<=,>=) | propertyNames に指定されていれば動作します | propertyIndex: true + 明示的な type |
相対プロパティ(a/b/@c) | サポートされていません | name: "a/b/c" |
関数(lower(...)、name()、path()) | サポートされていません | 必要に応じて function: "..." + propertyIndex/ordered |
ORDER BY または範囲条件で使用するプロパティには、必ず明示的な type(Date/Long/Double)を設定してください。設定しないと、値が辞書順で比較される場合があります(数値に見える文字列は "1","10","2" の順に並び、解析されていない日付はエラーになったり除外されたりせず、有効な日付より前に並びます)。
インデックスサイズ/ストレージコストに関する注意点
このスキルの修正はどれも、インデックスの対象範囲を広げることでクエリを高速化します。これは無料ではありません。提案する変更によって以下のいずれかが発生する場合は、フラグを黙って追加するのではなく、必ずトレードオフを明示してください。
| これを有効にすると… | …次のコストが発生します |
|---|---|
ルール内の任意のプロパティでnodeScopeIndexを有効にする | ブースト対象のプロパティだけでなく、そのルールに一致するすべてのノードのノード名がインデックス化されます。nt:baseのような広範なルールでは大きなコストになります。有効にする前に、ルールの対象を実際の最も狭いノードタイプに絞り込んでください。 |
evaluatePathRestrictions | ドキュメントごとのストレージがわずかですが確実に増加します(祖先パスの用語を保存します)。デフォルトで有効にするのではなく、ネイティブなパス制限評価が実際に必要なインデックス(§4.2のqueryPathsの正確性に関するケース)でのみ有効にしてください。 |
プロパティでorderedを有効にする | インデックスサイズが増加します(orderedを指定したプロパティごとにdoc-valuesフィールドが追加されます)。単一値プロパティのみを対象としています。ただし、複数値プロパティで有効にしてもハードエラーにはなりません。代わりにWARNをログに記録し、そのドキュメントのdoc-valuesフィールドをスキップします。そのため、複数値のドキュメントではエラーになるのではなく、ORDER BYが予測不能な位置にひそかに劣化します。実際にORDER BYで使うプロパティのみorderedに指定してください。また、複数値に関する誤りを検出するためにハードエラーに頼らず、データを確認してください。 |
| 1つの広範なインデックスに「念のため」多数のプロパティを含める | インデックスが大きくなり、インデックス作成が遅くなります。また、前述のノードタイプ/パスのスコープに関する落とし穴のとおり、クエリがそれらのプロパティを要求しない場合、選択上のメリットはありません。大規模な何でも入りのインデックスより、対象を絞った一貫性のあるインデックスを優先してください。 |
| 単一のLuceneインデックスが約2^31ドキュメントに近づく | Oakに同梱されるLuceneのバージョンにおけるハード上限です。これを超えると、インデックスを開けなくなる可能性があります。より狭い対象範囲(ノードタイプまたはパスでスコープを限定)に分割してください(以前はSolrがこの問題の回避策でしたが、Oak 1.82で削除されました)。 |
maxFieldLengthの上書きがない長いテキスト値 | デフォルトでは、フィールドごとに最初の約10000個の用語のみがインデックス化されます。大きなドキュメントフィールドは、全体がインデックス化されるのではなく、ひそかに途中で切り捨てられるため、その境界を超えたコンテンツで全文検索の漏れが発生する可能性があります。 |
等価性/範囲/並べ替えがまったく不要なのに、IS NOT NULL/IS NULLだけを目的としてpropertyIndex: trueを使う | ストレージの無駄になります。propertyIndexは、専用フィールドに個別の値ごとに用語をインデックス化します。notNullCheckEnabled/nullCheckEnabledだけを使えば(propertyIndexは不要)、インデックス全体で共有される共有フィールド(:notNullProps/:nullProps)に、保存されない軽量な用語を1つ使って、同じ存在確認を行えます。これは実際に低コストであり、単独で機能することも実動作で確認済みです。一方で、プロパティがほとんど設定されない広範/汎用的なノードタイプでnullCheckEnabledを有効にすると、ほぼすべてのドキュメントにエントリーが作成されることがあります(値を持たないノードごとに1エントリー)。対象を狭いノードタイプに絞ってください。 |
一般原則:クエリの対象が絞られているほど、低コストかつ安全に処理できます。インデックスの動作にかかわらず、特定の狭いノードタイプとパスを指定するクエリは、広範なnt:base/スコープ未指定のクエリより常に望ましいです。不必要に広範なクエリを補うインデックスを推奨する前に、まずクエリを絞り込むこと(ステップ8)を推奨してください。
よくある間違い(このスキルの開発中に、それぞれ実環境で検証済み)
手順 5 にあるスコープ関連の落とし穴が 2 つあります(クエリに一致する制限がないと、ノードタイプにスコープを限定したインデックスが完全にスキップされること、includedPaths と queryPaths の不一致により、結果が少なくても通知されないこと)。それらに加えて、以下の点にも注意してください。
- Lucene プロパティ定義に
sync/uniqueを指定しても、インデックス自体のasyncが単一の文字列のまま(["async","sync"]/["async","nrt"]ではない)だと、同期処理が行われず、一意性も適用されません。 - プロパティがインデックス対象でも、別のより低コストなインデックスが選択されることがあります。
weight(数値が小さいほど低コスト、デフォルトは5)を確認し、選択されると想定したインデックスについて、上記のスコープ関連の落とし穴も再確認してください。 - 関連性のない多数のプロパティを「念のため」1 つのインデックスにまとめるのは避けてください。ノードタイプごとに、関連性の高いプロパティをまとめたインデックスを 1 つ用意するのが適切です。同じノードタイプに対して
includedPaths/excludedPathsが異なるインデックスを重ねると、選択が曖昧になります。 rep:facet/rep:similarにもノードタイプのスコープに関する落とし穴があります。rep:facetは、通常の全文検索のように暗黙にフォールバックせず、明確な例外("... can't be evaluated by traversal")を発生させます。**これはクエリが実際に実行されたときにのみ発生します。**同じクエリに対するexplainは例外を発生させず、traverse/estimatedEntries: Infinityを含む通常の成功結果を返すため、「問題ない、遅いだけ」と誤解しやすくなります。explainが成功したからといって、rep:facetクエリが機能する証拠だと考えないでください。実際にクエリを実行するか、ファセットクエリでコスト無限大のトラバースプランが返された場合は、実行時に失敗する兆候として扱ってください。- 生の
rep:native('idx', 'field:value')クエリ文字列は、fieldがanalyzedプロパティだと一致結果を返しません。実際の Lucene フィールド名はfull:fieldです(コロンをエスケープしてfull\:field:valueと指定します)。JCR プロパティ名をそのまま使うのは、分析対象外(propertyIndexのみ)のフィールドに限られます。原因はFieldNames.ANALYZED_FIELD_PREFIXから特定し、同じインデックス/コンテンツを使った実環境での比較検証も行いました(エスケープなしでは結果 0 件、full\:とエスケープすると正しく一致)。これは、explainプランが正しく見えるのにネイティブクエリが「機能しない」場合の、最もよくある原因です。
レポートの形式
手順 2 の 2 つのセクションに分けてレポートを構成してください。統合してはいけません。
セクション A — 静的フィールド(すべてを完全に評価)
セクション A の各クエリについて、以下を提示してください。
- 表:フィールド/構成 → 対象か → 不足しているもの。
- 対象になっていない各フィールドの根本原因(表/上記の「よくある間違い」にある失敗モード。該当する場合は、問題のない場合も含め、2 つのスコープ確認を実施してください)。
- クエリ自体を先にさらに絞り込めるかどうか(ノードタイプ、次にパス、次にプロパティ条件の順です。手順 8 を参照)。最終的な回答が「ユースケースで許容できる範囲まで、すでに絞り込まれている」であっても、明記してください。
- 修正対象となるインデックスとその理由(手順 7)。
explainで確認した候補とそのコスト、静的に一致した単一の候補、または複数のインデックスが競合し、優先順位を決めるための実環境がない場合は、候補を並べて比較し、どれを変更するかユーザーに質問してください。回答があるまでは、どのインデックスについても修正後の定義を提示してはいけません。 - 4 項目目で特定したインデックスの修正後の定義(JSON 全体、または内容の差分)。変更ごとに 1 行で理由を説明してください。また、上記の落とし穴のいずれかを伴う変更については、ストレージ/サイズへのトレードオフを必ず明記してください(クエリを修正できるからといって、サイズのコストを黙って見過ごさないでください)。
- 検証に使う
explainコマンドと、検証に成功した場合の結果の例。ただし、実行可能な Oak インスタンスがない場合は、その旨を明記し、検証について黙って省略せず、静的分析は推論に基づくもので未検証であると説明してください。
セクション B — 動的フィールド(評価せず、質問する)
セクション B のクエリごとに、構築場所と正確に不足している入力(名前付きの OSGi プロパティ、リクエストパラメーター、設定キー、または「任意のコンテンツ作成クエリ — 固定フィールドは存在しません」)を示し、それについてユーザーに尋ねてください。ユーザーが具体的なフィールド名を提示するまでは、セクション B のクエリについてカバレッジテーブル、根本原因、修正後のインデックス定義を含めないでください。ユーザーが提示した時点で、そのフィールドに対して手順 4〜9 を再実行し、そのクエリをセクション A のレポート形式に移してください。