処理に時間のかかるクエリのトラブルシューティング

処理に時間のかかるクエリの分類

AEM で処理に時間のかかるクエリは、主に 3 つに分類されます。以下に重大な順に示します。

  1. インデックスのないクエリ

    • インデックスに解決​されず、結果を収集するために JCR のコンテンツをトラバースするクエリ
  2. 制限(範囲指定)が不十分なクエリ

    • インデックスには解決されるが、結果を収集するためにすべてのインデックスエントリをトラバースする必要があるクエリ
  3. 結果セットが大きいクエリ

    • 非常に多くの結果を返すクエリ

最初の2種類のクエリ(インデックスレス、低い制限)は、Oakクエリエンジンに対して、​の潜在的な結果(コンテンツノードまたはインデックスエントリ)を​実際の​結果セットに属するものを特定させるので、低速です。

各結果候補を調査する動作をトラバースと呼びます。

結果候補をそれぞれ調査する必要があるので、実際の結果セットを特定するためのコストは、結果候補の数に比例して増大します。

クエリの制限を追加し、インデックスを調整すると、結果を迅速に取得できるように最適化された形式でインデックスデータを格納できます。また、結果候補セットを順次調査する必要性が低減するかなくなります。

AEM 6.3 では、デフォルトでトラバースの回数が 100,000 回に達すると、クエリが失敗し、例外がスローされます。この制限は、AEM 6.3より前のAEMバージョンではデフォルトで存在しませんが、Apache Jackrabbitクエリエンジン設定OSGi設定およびQueryEngineSettings JMX bean (プロパティLimitReads)を介して設定できます。

インデックスのないクエリの検出

開発時

すべての​クエリを説明し、クエリ計画に​/*が含まれていないことを確認するtraverse​説明が含まれます。 トラバースクエリ計画の例:

  • プラン: [nt:unstructured] as [a] /* traverse "/content//*?lang=ja" where ([a].[unindexedProperty] = 'some value') and (isdescendantnode([a], [/content])) */

デプロイメント後

  • インデックスのないトラバーサルクエリについて、error.log を監視します。

    • *INFO* org.apache.jackrabbit.oak.query.QueryImpl Traversal query (query without index) ... ; consider creating and index
    • このメッセージがログに記録されるのは、使用できるインデックスがない場合とクエリが多数のノードをトラバースする可能性がある場合のみです。インデックスが使用可能な場合、メッセージはログに記録されませんが、トラバースの量が少ないので処理にかかる時間は短くなります。
  • AEM クエリパフォーマンスオペレーションコンソールにアクセスし、スロークエリの説明でトラバーサルを探すか、インデックスクエリの説明を見つけないかを調べます。

制限が不十分なクエリの検出

開発時

すべてのクエリの説明を実行し、クエリのプロパティ制限に一致するよう調整されたインデックスに解決されることを確認します。

  • 理想的なクエリプランの範囲では、すべてのプロパティ制限、および少なくともクエリで最も厳密なプロパティ制限に indexRules を持ちます。
  • 結果を並べ替えるクエリは、Lucene プロパティインデックスに解決される必要があります。このインデックスには、orderable=true. を設定するプロパティによる並べ替えに関するインデックスルールがあります。

例えば、デフォルトのcqPageLuceneにはjcr:content/cq:tagsのインデックスルールがありません

cq:tags インデックスルールを追加する前

  • cq:tags インデックスルール

    • デフォルトでは存在しません。
  • Query Builder クエリ

    type=cq:Page
     property=jcr:content/cq:tags
     property.value=my:tag
    
  • クエリプラン

    • [cq:Page] as [a] /* lucene:cqPageLucene(/oak:index/cqPageLucene?lang=ja) *:* where [a].[jcr:content/cq:tags] = 'my:tag' */

このクエリは cqPageLucene インデックスに解決されます。ただし、jcr:content または cq:tags のプロパティインデックスルールは存在しないので、この制限を評価する際に、cqPageLucene インデックス内のすべてのレコードが一致するかどうかを判断するためにチェックされます。つまり、インデックスに 100 万個の cq:Page ノードが含まれている場合は、結果セットを特定するために 100 万件のレコードがチェックされます。

cq:tags インデックスルールを追加した後

  • cq:tags インデックスルール

    /oak:index/cqPageLucene/indexRules/cq:Page/properties/cqTags
     @name=jcr:content/cq:tags
     @propertyIndex=true
    
  • Query Builder クエリ

    type=cq:Page
     property=jcr:content/cq:tags
     property.value=myTagNamespace:myTag
    
  • クエリプラン

    • [cq:Page] as [a] /* lucene:cqPageLucene(/oak:index/cqPageLucene?lang=ja) jcr:content/cq:tags:my:tag where [a].[jcr:content/cq:tags] = 'my:tag' */

cqPageLuceneインデックスにjcr:content/cq:tagsのindexRuleを追加すると、cq:tagsデータを最適化された方法で保存できます。

jcr:content/cq:tags制限付きのクエリが行われると、インデックスは値別に結果を検索できます。 つまり、100 個の cq:Page ノードに値として myTagNamespace:myTag が設定されている場合は、この 100 件の結果だけが返され、他の 999,000 件は制限チェックから除外されるので、パフォーマンスは 10,000 倍向上します。

当然ながら、さらにクエリを制限すると、対象となる結果セットが少なくなり、クエリはさらに最適化されます。

同様に、cq:tagsプロパティにインデックスルールを追加しないと、cq:tagsを制限したフルテキストクエリでも、インデックスの結果がすべてのフルテキスト一致を返すので、パフォーマンスが低下します。 cq:tagsに対する制限は、その後フィルタリングされます。

インデックス後にフィルタリングされるもう 1 つの原因は、開発中に見落とされることがよくあるアクセス制御リストです。ユーザーがアクセスできない可能性のあるパスがクエリによって返されていないかどうかを確認してみます。これをおこなうには、通常、コンテキスト構造を改良すると共に、クエリに適切なパス制限を指定します。

Luceneインデックスが非常に小さなサブセットを返す結果を多く返しているかどうかを識別する有用な方法は、org.apache.jackrabbit.oak.plugins.index.lucene.LucenePropertyIndexのDEBUGログを有効にし、インデックスから読み込まれているドキュメントの数を調べることです。 最終結果の数と読み込まれたドキュメントの数を比較すると釣り合うはずです。詳しくは、ログを参照してください。

デプロイメント後

  • error.logを監視してトラバーサルクエリを調べます。

    • *WARN* org.apache.jackrabbit.oak.spi.query.Cursors$TraversingCursor Traversed ### nodes ... consider creating an index or changing the query
  • AEM クエリパフォーマンスオペレーションコンソールにアクセスし、クエリの動作が遅い場合、クエリプロパティの制約を解決しないクエリ計画を探してプロパティルールをインデックス化します。

結果セットが大きいクエリの検出

開発時

oak.queryLimitInMemory とoak.queryLimitReads のしきい値を低く設定し(例えば、それぞれ 10000 と 5000)、UnsupportedOperationException にヒットして「The query read more than x nodes…」と表示されたときに高負荷のクエリを最適化します。

これにより、リソースを集中的に使用するクエリ(つまり、インデックスのないクエリまたは対応するインデックスが少ないクエリ)を回避することができます。例えば、100 万個のノードを読み取るクエリでは、大量の IO が発生し、アプリケーション全体のパフォーマンスに悪影響を及ぼします。このため、上述の制限が原因で失敗するクエリは、分析して最適化する必要があります。

デプロイメント後

  • 大量のノードのトラバーサルまたは大量のヒープメモリの消費を引き起こすクエリについてログを監視します。

    • *WARN* ... java.lang.UnsupportedOperationException: The query read or traversed more than 100000 nodes. To avoid affecting other tasks, processing was stopped.
    • クエリを最適化して、走査するノードの数を減らします。
  • ログを監視して、ヒープメモリを大量に消費しているクエリがないかどうかを調べます。

    • *WARN* ... java.lang.UnsupportedOperationException: The query read more than 500000 nodes in memory. To avoid running out of memory, processing was stopped
    • クエリを最適化して、ヒープメモリの使用量を減らします。

AEM 6.0 - 6.2バージョンでは、AEM開始スクリプトのJVMパラメーターを使用してノードトラバーサルのしきい値を調整し、大きなクエリが環境をオーバーロードするのを防ぐことができます。 推奨される値は次のとおりです。

  • -Doak.queryLimitInMemory=500000
  • -Doak.queryLimitReads=100000

AEM 6.3 では、デフォルトで上述の 2 つのパラメーターが事前設定されており、OSGi QueryEngineSettings を使用して変更できます。

詳細は、次を参照してください。https://jackrabbit.apache.org/oak/docs/query/query-engine.html#Slow_Queries_and_Read_Limits

クエリパフォーマンスのチューニング

AEM におけるクエリパフォーマンス最適化のモットーは次のとおりです。

「制限は多いほどよい」

以下に、クエリのパフォーマンスを確保するために推奨される調整の概要を示します。まずクエリ、次にあまり目立たないアクティビティ、その後必要に応じてインデックス定義をチューニングします。

クエリステートメントの調整

AEM では、以下のクエリ言語がサポートされています。

  • Query Builder
  • JCR-SQL2
  • XPath

以下の例では、AEM 開発者が最もよく使用する Query Builder を使用していますが、JCR-SQL2 および XPath にも同じ原則が当てはまります。

  1. クエリが既存の Lucene プロパティインデックスに解決されるようにノードタイプの制限を追加します。

    • 最適化されていないクエリ

       property=jcr:content/contentType
       property.value=article-page
      
    • 最適化されたクエリ

       type=cq:Page 
       property=jcr:content/contentType 
       property.value=article-page
      

    ノードタイプの制限がないクエリにより、AEM では nt:base ノードタイプが想定されます。これは、AEM のすべてのノードのサブタイプなので、実質上ノードタイプの制限は存在しません。

    type=cq:Pageを設定すると、このクエリはcq:Pageノードのみに制限され、クエリはAEM cqPageLuceneに解決され、結果はAEMのノードのサブセット(cq:Pageノードのみ)に制限されます。

  2. クエリが既存の Lucene プロパティインデックスに解決されるようにクエリのノードタイプの制限を調整します。

    • 最適化されていないクエリ

      type=nt:hierarchyNode
      property=jcr:content/contentType
      property.value=article-page
      
    • 最適化されたクエリ

      type=cq:Page
      property=jcr:content/contentType
      property.value=article-page
      

    nt:hierarchyNode はの親ノードタイプ cq:Pageで、カスタムアプリケーション jcr:content/contentType=article-page を介して cq:Page ノードにのみ適用される場合、このクエリは cq:Page ノードを返すのにここでのみ jcr:content/contentType=article-pageです。ただしこれは、以下の理由から、次善策としての制限となります。

    • 他のノードはnt:hierarchyNodeから継承します(例: dam:Asset)を追加する際に、不必要に結果のセットに追加する必要があります。
    • nt:hierarchyNodeに対してAEMで提供されたインデックスは存在しませんが、cq:Pageに対して指定されたインデックスが存在します。

    type=cq:Page を設定すると、このクエリは cq:Page ノードのみに限定され、AEM の cqPageLucene に解決されます。これにより、結果は AEM のノードのサブセット(cq:Page ノードのみ)に限定されます。

  3. または、クエリが既存のプロパティインデックスに解決されるように、プロパティの制限を調整します。

    • 最適化されていないクエリ

        property=jcr:content/contentType
        property.value=article-page
      
    • 最適化されたクエリ

      property=jcr:content/sling:resourceType
      property.value=my-site/components/structure/article-page
      

    プロパティ制限をjcr:content/contentType(カスタム値)から既知のプロパティsling:resourceTypeに変更すると、クエリはsling:resourceTypeによってすべてのコンテンツのインデックスを作成するプロパティインデックスslingResourceTypeに解決できます。

    (Lucene プロパティインデックスではなく)プロパティインデックスの使用が最も適しているのは、クエリがノードタイプを認識せず、単一のプロパティ制限によって結果セットが決まる場合です。

  4. クエリに可能な限り厳密なパス制限を追加します。例えば、/content/my-siteより/content/my-site/us/enを好み、/より/content/damを好みます。

    • 最適化されていないクエリ

      type=cq:Page
      path=/content
      property=jcr:content/contentType
      property.value=article-page
      
    • 最適化されたクエリ

      type=cq:Page
      path=/content/my-site/us/en
      property=jcr:content/contentType
      property.value=article-page
      

    path=/contentからpath=/content/my-site/us/enへのパス制限をスコープすると、インデックスを使用して、検査する必要のあるインデックスエントリの数を減らすことができます。 クエリが、/content/content/damの他に、パスを非常にうまく制限できる場合は、インデックスにevaluatePathRestrictions=trueが含まれていることを確認します。

    evaluatePathRestrictionsを使用すると、インデックスサイズが大きくなります。

  5. 可能な場合は、LIKEfn:XXXX などのクエリの関数や操作を避けます。これらのコストは、制限に基づいた結果の数に伴って増減するからです。

    • 最適化されていないクエリ

      type=cq:Page
      property=jcr:content/contentType
      property.operation=like
      property.value=%article%
      
    • 最適化されたクエリ

      type=cq:Page
      fulltext=article
      fulltext.relPath=jcr:content/contentType
      

    ワイルドカード(「%…」)を含むテキスト開始の場合は、インデックスを使用できないので、LIKE条件の評価は低速です。 jcr:contains 条件は、フルテキストのインデックスの使用を可能にするので、推奨されています。このためには、解決されたLuceneプロパティインデックスにanalayzed=trueと共にjcr:content/contentTypeのindexRuleが必要です。

    fn:lowercase(..)のようなクエリ関数を使う方が、より複雑で目立つインデックス・アナライザ構成の外では、より高速な等価性がないので、最適化が難しくなる場合があります。 他の範囲制限を指定し、クエリ全体のパフォーマンスを向上させることをお勧めします。これには、関数の操作対象となる結果候補のセットをできるだけ小さくする必要があります。

  6. この調整は、Query Builder 固有であり、JCR-SQL2 または XPath には当てはまりません。​**

    すべての結果が​not すぐに必要な場合は、クエリビルダー' guessTotalを使用します。

    • 最適化されていないクエリ

      type=cq:Page
      path=/content
      
    • 最適化されたクエリ

      type=cq:Page
      path=/content
      p.guessTotal=100
      

    クエリの実行は高速で結果数が多い場合には、p.guessTotalは、クエリビルダークエリにとって重要な最適化です。

    p.guessTotal=100 を指定すると、Query Builder は最初の 100 件の結果だけを収集し、さらに 1 つ以上の結果が存在するかどうかを示すブール値フラグを設定します(ただしカウントすると処理に時間がかかるので、残りの数は示されません)。この最適化は、ページネーションまたは無限ロードの使用例よりも優れており、結果のサブセットだけが増分的に表示されます。

既存のインデックスのチューニング

  1. 最適なクエリがプロパティインデックスに解決される場合、プロパティインデックスで可能なチューニングは最小限なので、できることはありません。

  2. それ以外の場合は、クエリはLuceneプロパティインデックスに解決する必要があります。 解決できるインデックスがない場合は、「新しいインデックスの作成」に進んでください。

  3. 必要に応じて、クエリを XPath または JCR-SQL2 に変換します。

    • Query Builder クエリ

      query type=cq:Page
      path=/content/my-site/us/en
      property=jcr:content/contentType
      property.value=article-page
      orderby=@jcr:content/publishDate
      orderby.sort=desc
      
    • Query Builder クエリから生成された XPath

      /jcr:root/content/my-site/us/en//element(*, cq:Page)[jcr:content/@contentType = 'article-page'] order by jcr:content/@publishDate descending
      
  4. この XPath(または JCR-SQL2)を Oak Index Definition Generator に提供して、最適化された Lucene プロパティインデックス定義を生成します。

    生成された Lucene プロパティインデックス定義

    - evaluatePathRestrictions = true
    - compatVersion = 2
    - type = "lucene"
    - async = "async"
    - jcr:primaryType = oak:QueryIndexDefinition
        + indexRules 
        + cq:Page 
            + properties 
            + contentType 
                - name = "jcr:content/contentType"
                - propertyIndex = true
            + publishDate 
                - ordered = true
                - name = "jcr:content/publishDate"
    
  5. 生成された定義を既存のLuceneプロパティインデックスに追加方式で手動で結合します。 その他のクエリを満たすために使用される可能性があるので、既存の設定を削除しないよう注意してください。

    1. cq:Page を対象とする既存の Lucene プロパティインデックスを探します(インデックスマネージャーを使用)。この場合は/oak:index/cqPageLuceneです。
    2. 最適化したインデックス定義(手順 4)と既存のインデックス(/oak:index/cqPageLucene)の設定の差分を特定し、欠けている設定を最適化したインデックスから既存のインデックス定義に追加します。
    3. AEM のインデックス再作成のベストプラクティスにより、このインデックス設定の変更が既存コンテンツに影響するかどうかに基づいて、更新または再インデックス付けのいずれかが必要になります。

新しいインデックスの作成

  1. クエリが既存の Lucene プロパティインデックスに解決されないことを確認します。解決される場合は、前述の既存インデックスのチューニングに関する節を参照してください。

  2. 必要に応じて、クエリを XPath または JCR-SQL2 に変換します。

    • Query Builder クエリ

      type=myApp:Author
      property=firstName
      property.value=ira
      
    • Query Builder クエリから生成された XPath

      //element(*, myApp:Page)[@firstName = 'ira']
      
  3. この XPath(または JCR-SQL2)を Oak Index Definition Generator に提供して、最適化された Lucene プロパティインデックス定義を生成します。

    生成された Lucene プロパティインデックス定義

    - compatVersion = 2
    - type = "lucene"
    - async = "async"
    - jcr:primaryType = oak:QueryIndexDefinition
        + indexRules 
        + myApp:AuthorModel 
            + properties 
            + firstName 
                - name = "firstName"
                - propertyIndex = true
    
  4. 生成された Lucene プロパティインデックス定義をデプロイします。

    Oak Index Definition Generator によって新しいインデックスに提供された XML 定義を、Oak インデックス定義を管理する AEM プロジェクトに追加します(コードは Oak インデックス定義に依存するので、これらの定義はコードとして扱うことを忘れないでください)。

    通常の AEM ソフトウェア開発ライフサイクルに従って新しいインデックスをデプロイしてテストし、クエリがインデックスに解決され、効率よく実行されることを確認します。

    このインデックスを初めてデプロイしたときに、AEM によってインデックスに必要なデータが追加されます。

インデックスを使用しないクエリとトラバーサル・システムが正常に動作するのはいつですか。

AEM のコンテンツアーキテクチャは柔軟です。そのため、コンテンツ構造のトラバーサルが時間の経過と共に受け入れられないほど大きくならないことを予測したり保証したりすることは困難です。

したがって、パス制限とnodetype制限を組み合わせると、が20個未満のクエリがトラバースされることが保証される場合を除き、インデックスがノードを満たしていることを確認します。

クエリ開発ツール

アドビでのサポート

コミュニティによるサポート

  • Oak Index Definition Generator

    • XPath または JCR-SQL2 クエリステートメントから最適な Lucence プロパティインデックスを生成します。
  • AEM Chrome Plug-in

    • Google Chrome Web ブラウザーの拡張機能で、実行されたクエリとそのクエリプランなど、リクエストごとのログデータをブラウザーの開発ツールコンソールに公開します。
    • Sling Log Tracer 1.0.2 以上がインストールされ、AEM で有効になっている必要があります。

このページ