AEM中慢速查询有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//*" 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
解释所有查询,并确保它们解析到调整为匹配查询属性限制的索引。
indexRules
表示所有属性限制,而查询中最紧的属性限制则至少表示。orderable=true.
的属性进行排序的属性的索引规则cqPageLucene
没有jcr:content/cq:tags
的索引规则添加cq:tags索引规则之前
cq:tags索引规则
查询生成器查询
type=cq:Page
property=jcr:content/cq:tags
property.value=my:tag
查询计划
[cq:Page] as [a] /* lucene:cqPageLucene(/oak:index/cqPageLucene?lang=zh-Hans) *:* 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
查询生成器查询
type=cq:Page
property=jcr:content/cq:tags
property.value=myTagNamespace:myTag
查询计划
[cq:Page] as [a] /* lucene:cqPageLucene(/oak:index/cqPageLucene?lang=zh-Hans) 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的限制将在其后进行过滤。
索引后过滤的另一个原因是访问控制列表,在开发过程中经常会漏掉该列表。 尝试确保查询不返回用户可能无法访问的路径。 这通常可以通过更好的内容结构以及对查询提供相关路径限制来完成。
识别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
为oak.queryLimitInMemory设置低阈值(例如 10000)和oak.queryLimitReads(例如 5000),并在点击UnsupportedOperationException时优化昂贵的查询,该查询会读取超过x个节点……。
这有助于避免资源密集型查询(即, 不受任何索引的支持,或受覆盖范围较小的索引的支持)。 例如,读取1M个节点的查询将导致大量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支持以下查询语言:
以下示例使用查询生成器,因为查询生成器是AEM开发人员使用的最常用的查询语言,但JCR-SQL2和XPath也适用相同的原则。
添加nodetype限制,以便查询解析为现有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
节点)。
调整查询的nodetype限制,以便查询解析为现有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节点)。
或者,调整属性限制,以便查询解析为现有的属性索引。
未优化查询
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
,可使查询解析为属性索引slingResourceType
,该属性索引通过sling:resourceType
对所有内容进行索引。
当查询不能通过nodetype识别,并且结果集由单个属性限制主导时,最好使用属性索引(与Lucene属性索引相对)。
向查询添加最紧的路径限制。 例如,与/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
会增加索引大小。
尽可能避免查询函数/操作超出以下范围:LIKE
和fn: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(..)
之类的查询函数可能比较难以优化,因为没有更快的等效函数(在更复杂和更突出的索引分析器配置之外)。 最好确定其他范围限制,以改进整体查询性能,要求函数对尽可能小的潜在结果集进行操作。
此调整特定于查询生成器,不适用于JCR-SQL2或XPath。
如果不立即需要完整的结果集,请使用查询生成器的guessTotal 。
未优化查询
type=cq:Page
path=/content
优化查询
type=cq:Page
path=/content
p.guessTotal=100
如果查询执行速度快但结果数量大,则为p。guessTotal
是对查询生成器查询进行的关键优化。
p.guessTotal=100
告知查询生成器仅收集前100个结果,并设置一个布尔标记,以指示是否至少还存在一个结果(但不存在多少个结果),因为计数此数字会导致运行缓慢。此优化优于分页或无限加载用例,在这些用例中,仅递增显示结果的子集。
如果将最佳查询解析为属性索引,则无需执行任何操作,因为属性索引是最小可调的。
否则,查询应解析为Lucene属性索引。 如果无法解析索引,请跳转到创建新索引。
根据需要,将查询转换为XPath或JCR-SQL2。
查询生成器查询
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
从查询生成器查询生成的XPath
/jcr:root/content/my-site/us/en//element(*, cq:Page)[jcr:content/@contentType = 'article-page'] order by jcr:content/@publishDate descending
将XPath(或JCR-SQL2)提供给Oak索引定义生成器以生成优化的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"
以添加方式将生成的定义手动合并到现有Lucene属性索引中。 请注意,不要删除现有配置,因为它们可能用于满足其他查询。
/oak:index/cqPageLucene
。验证查询未解析为现有Lucene属性索引。 如果有,请参阅上面有关调整和现有索引的部分。
根据需要,将查询转换为XPath或JCR-SQL2。
查询生成器查询
type=myApp:Author
property=firstName
property.value=ira
从查询生成器查询生成的XPath
//element(*, myApp:Page)[@firstName = 'ira']
将XPath(或JCR-SQL2)提供给Oak索引定义生成器以生成优化的Lucene属性索引定义。
生成的Lucene属性索引定义
- compatVersion = 2
- type = "lucene"
- async = "async"
- jcr:primaryType = oak:QueryIndexDefinition
+ indexRules
+ myApp:AuthorModel
+ properties
+ firstName
- name = "firstName"
- propertyIndex = true
部署生成的Lucene属性索引定义。
将Oak索引定义生成器为新索引提供的XML定义添加到管理Oak索引定义的AEM项目中(记住,将Oak索引定义视为代码,因为代码取决于它们)。
按照常规的AEM软件开发生命周期部署和测试新索引,并验证查询已解析为索引且查询性能良好。
初始部署此索引后,AEM将使用必需的数据填充该索引。
由于AEM的内容架构灵活,因此很难预测并确保内容结构的遍历不会随着时间的推移而演变为无法接受的大。
因此,确保索引满足查询,除非路径限制和节点类型限制的组合保证小于20个节点被遍历。
查询生成器调试器
CRXDE Lite — 查询工具
查询生成器日志记录
DEBUG @ com.day.cq.search.impl.builder.QueryImpl
Oak查询执行日志记录
DEBUG @ org.apache.jackrabbit.oak.query
Apache Jackrabbit查询引擎设置OSGi配置
NodeCounter JMX Mbean