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

CAUTION
AEM 6.4 の拡張サポートは終了し、このドキュメントは更新されなくなりました。 詳細は、 技術サポート期間. サポートされているバージョンを見つける ここ.

処理に時間のかかるクエリの分類 slow-query-classifications

AEMでは、処理に時間のかかるクエリの主な分類が 3 つあり、重大度別に表示されます。

  1. インデックスなしクエリ

    • 実行するクエリ not インデックスに解決し、JCR のコンテンツを走査して結果を収集する
  2. 制限(範囲指定)が不十分なクエリ

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

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

最初の 2 つの分類のクエリ(インデックスのないクエリと制限が不十分なクエリ)の処理に時間がかかるのは、Oak クエリエンジンが強制的に結果​ 候補(コンテンツノードやインデックスエントリ)それぞれを調査し、どれが​ 実際の ​結果セットに属しているかを特定するからです。

各潜在的な結果を検査する行為は、トラバーシングと呼ばれるものです。

各潜在的な結果を検査する必要があるので、実際の結果セットを決定するコストは、電位結果の数に比例して増加します。

クエリ制限とチューニングインデックスを追加すると、インデックスデータを最適化された形式で保存し、結果を迅速に取得でき、潜在的な結果セットの線形検査の必要性を軽減または排除できます。

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

インデックスレスクエリの検出 detecting-index-less-queries

開発中 during-development

すべての ​クエリの説明を実行し、それらのクエリプランに  /* traverse  が含まれていないことを確認します。トラバースするクエリプランの例は次のとおりです。

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

デプロイメント後 post-deployment

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

    • *INFO* org.apache.jackrabbit.oak.query.QueryImpl Traversal query (query without index) ... ; consider creating and index
    • このメッセージは、使用可能なインデックスがなく、クエリが多数のノードを横断する可能性がある場合にのみ記録されます。 インデックスが使用可能な場合、メッセージはログに記録されませんが、トラバースする量が少ないので、処理が高速です。
  • AEM のクエリパフォーマンス操作コンソールに移動し、処理に時間がかかるクエリの説明を実行して、トラバーサルまたはインデックスのないクエリの説明を探します。

制限が不十分なクエリの検出 detecting-poorly-restricted-queries

開発中 during-development-1

すべてのクエリについて説明し、クエリのプロパティ制限に合わせて調整されたインデックスに解決されることを確認します。

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

例えば、デフォルトの cqPageLucene には jcr:content/cq:tags に対するインデックスルールがありません。 for-example-the-default-cqpagelucene-does-not-have-an-index-rule-for-jcr-content-cq-tags

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

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

    • 標準では存在しません
  • Query Builder クエリ

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

    • [cq:Page] as [a] /* lucene:cqPageLucene(https://experienceleague.adobe.com/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 インデックスルール

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

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

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

cqPageLucene インデックスに jcr:content/cq:tags のインデックスルールを追加したので、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 のデバッグログを有効にし、インデックスから読み込まれるドキュメントの数を確認する方法が役立ちます。結果の数と読み込まれたドキュメントの数は不釣り合いになりません。 詳しくは、ログを参照してください。

デプロイメント後 post-deployment-1

  • トラバーサルクエリについて、error.log を監視します。

    • *WARN* org.apache.jackrabbit.oak.spi.query.Cursors$TraversingCursor Traversed ### nodes ... consider creating an index or changing the query
  • AEM のクエリパフォーマンス操作コンソールに移動し、処理に時間がかかるクエリの説明を実行して、クエリプロパティ制限がインデックスプロパティルールに解決されないクエリプランを探します。

大きな結果セットクエリの検出 detecting-large-result-set-queries

開発中 during-development-2

oak.queryLimitInMemory の低いしきい値を設定します ( 例: 10000) と oak.queryLimitReads ( 例: 5000) で、UnsupportedOperationException を押したときに、「The query read more than x nodes…」というメッセージが表示され、高価なクエリを最適化します。

これにより、リソースを大量に消費するクエリ ( インデックスをベースとしない、または、インデックスをカバーしないことによってバックアップされない )。 例えば、1M ノードを読み取るクエリは、大量の IO を引き起こし、アプリケーション全体のパフォーマンスに悪影響を与えます。 したがって、上記の制限によって失敗したクエリは、分析し、最適化する必要があります。

デプロイメント後 post-deployment-2

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

    • *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 を参照してください。

クエリパフォーマンスの調整 query-performance-tuning

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

「制限が多いほど、より良いものになる」

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

クエリ文の調整 adjusting-the-query-statement

AEMは、次のクエリ言語をサポートしています。

  • Query Builder
  • JCR-SQL2
  • XPath

次の例では、AEM開発者が使用する最も一般的なクエリ言語として Query Builder を使用していますが、同じ原則が JCR-SQL2 および XPath にも当てはまります。

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

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

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

      code language-none
       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 プロパティインデックスに解決されるように、クエリのノードタイプ制限を調整します。

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

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

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

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

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

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

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

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

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

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

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

    (Lucene プロパティインデックスとは異なる)プロパティインデックスは、クエリがノードタイプで認識されず、単一のプロパティ制限が結果セットに優先する場合に最も適しています。

  4. クエリに可能な最も厳密なパス制限を追加します。 例:/content/my-siteより/content/my-site/us/enが推奨され、また/より/content/damが推奨されます。

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

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

      code language-none
      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 などのクエリの関数や操作を避けます。これらのコストは、制限に基づいた結果の数に伴って増減するからです。

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

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

      code language-none
      type=cq:Page
      fulltext=article
      fulltext.relPath=jcr:content/contentType
      

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

    fn:lowercase(..) などのクエリ関数の使用を最適化するのは、(より複雑で目立つインデックス分析設定の外部に)より高速な同等の手段がないので困難です。他のスコーピング制限を特定して、クエリ全体のパフォーマンスを向上させ、可能な限り小さな結果セットに対して関数を操作する必要があるようにすることをお勧めします。

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

    用途 Query Builder の guessTotal 結果の完全なセットがすぐに必要​ ​場合。

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

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

      code language-none
      type=cq:Page
      path=/content
      p.guessTotal=100
      

    クエリの実行時間が短くても結果の数が多い場合、p.guessTotal は、Query Builder のクエリにとって必要不可欠な最適化です。

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

既存のインデックスの調整 existing-index-tuning

  1. 最適なクエリがプロパティインデックスに解決される場合、プロパティインデックスは最小限の調整可能なので、実行する必要はありません。

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

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

    • Query Builder クエリ

      code language-none
      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

      code language-none
      /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 プロパティインデックス定義

    code language-xml
    - 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) と既存のインデックス (https://experienceleague.adobe.com/oak:index/cqPageLucene?lang=ja) の間の設定差分を特定し、最適化されたインデックスの欠落している設定を既存のインデックス定義に追加します。
    3. AEMの再インデックスのベストプラクティスに従って、このインデックス設定の変更によって既存のコンテンツが影響を受けるかどうかに基づいて、更新または再インデックスが順番におこなわれます。

新しいインデックスの作成 create-a-new-index

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

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

    • Query Builder クエリ

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

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

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

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

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

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

    このインデックスの初期デプロイメント時に、AEMは必要なデータをインデックスに入力します。

インデックスがないクエリおよびトラバーサルクエリに問題がない場合 when-index-less-and-traversal-queries-are-ok

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

こうした理由から、パス制限とノードタイプ制限の組み合わせによって​ トラバースされるノードが 20 個未満 ​であることが保証される場合を除いて、インデックスがクエリを確実に満たすようにします。

クエリ開発ツール query-development-tools

Adobe対応 adobe-supported

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

  • Oak Index Definition Generator

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

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