缓存和查询预热

Solr 的缓存提供了一种提高查询性能的重要方法。缓存可以存储文档、查询中使用的过滤器以及来自先前查询的结果。

缓存会在提交后被清除,并且通常需要在重新看到其好处之前重新填充。为了解决这个问题,可以在新的搜索器被认为打开之前“预热”缓存,方法是自动使用旧缓存中的值填充新缓存。

缓存管理对于成功的 Solr 实施至关重要,因此应该注意,随着应用程序的增长,需要对缓存进行微调。

solrconfig.xml 中的 <query>

本节中的设置会影响 Solr 处理和响应查询的方式。

这些设置都在 solrconfig.xml<query> 元素的子元素中配置。

<config>
  <query>
    ...
  </query>
</config>

缓存

Solr 缓存与索引搜索器的特定实例相关联,索引搜索器是索引的特定视图,在该搜索器的生命周期内不会更改。只要正在使用该索引搜索器,其缓存中的任何项都将有效并可重复使用。默认情况下,缓存的 Solr 对象在一段时间后不会过期;相反,它们在索引搜索器的生命周期内保持有效。可以通过使用 maxIdleTime 选项启用基于空闲时间的过期。

当打开新的搜索器时,当前的搜索器会继续为请求提供服务,而新的搜索器会自动预热其缓存。新搜索器使用当前搜索器的缓存来预填充自己的缓存。当新的搜索器准备好时,它会被注册为当前搜索器并开始处理所有新的搜索请求。旧的搜索器将在完成为其所有请求提供服务后关闭。

缓存实现

Solr 附带了一个默认的 SolrCache 实现,用于不同类型的缓存。

CaffeineCache 是一个由 Caffeine 缓存库 支持的实现。默认情况下,它使用 Window TinyLFU (W-TinyLFU) 驱逐策略,该策略允许基于使用频率和最近度在 O(1) 时间内驱逐,且占用空间小。通常,与传统缓存相比,此缓存通常具有更低的内存占用、更高的命中率和更好的多线程性能。

CaffeineCache 使用自动预热计数,该计数支持整数和百分比,在预热发生时,这些值会相对于当前缓存大小进行评估。

Solr 管理 UI 中的插件和统计信息屏幕将显示有关所有活动缓存的性能信息。此信息可以帮助您根据您的特定应用程序适当地微调各种缓存的大小。当搜索器终止时,其缓存使用情况的摘要也会写入日志。

缓存参数

每个缓存都有设置来定义其初始大小 (initialSize)、最大大小 (size) 以及在预热期间使用的项数 (autowarmCount)。对于 autowarmCount,也可以表示为百分比而不是绝对值。

maxIdleTime 属性控制自动驱逐一段时间未使用的条目。此属性以秒为单位表示,默认值 0 表示不会由于超出空闲时间而自动驱逐任何条目。此属性的较小值会导致较旧的条目被快速驱逐,这将减少缓存内存使用量,但可能会由于相同条目的重复驱逐-查找-未命中-插入周期而导致颠簸。较大的值会导致条目保留更长时间,等待重用,但代价是内存使用量增加。合理的值,取决于查询量和模式,可能介于 60-3600 之间。

maxRamMB 属性限制缓存可以消耗的最大内存量。当同时指定 sizemaxRamMB 限制时,maxRamMB 限制将优先,并且 size 限制将被忽略。

async 属性决定缓存是存储直接结果(async=false,禁用)还是存储对计算的间接引用(async=true,默认启用)。如果您的查询包含子文档或连接查询,则必须启用异步缓存才能正常工作。禁用异步选项可能会减少每个缓存条目的内存使用量,但会增加 CPU 消耗。对于许多并发查询请求尚未缓存的相同结果集的情况,异步缓存提供了最显著的改进,可以替代更大的缓存大小或增加的自动预热计数。但是,异步缓存不会阻止有时限查询的数据竞争,因为这些查询预期会提供部分结果。

可以使用参数 enabled 并将值设置为 false 来禁用所有缓存。

以下描述了每个缓存的详细信息。

过滤器缓存

此缓存保存已解析的查询,并与匹配它的所有文档的无序集合配对。除非这样的集合非常小,否则集合实现是一个位集。

Solr 使用 filterCache 最典型的方式是缓存每个 fq 搜索参数的结果,尽管还有其他一些用例。后续使用相同参数过滤器查询会导致缓存命中并快速返回结果。有关 fq 的详细讨论,请参阅fq(过滤器查询)参数

对于特定的查询 fq,可以使用cache 本地参数禁用此缓存的使用。

Solr 使用此缓存的另一个功能是默认 Lucene 查询解析器中的 filter(…​) 语法。

当配置参数 facet.method 设置为 fc 时,Solr 还将此缓存用于分面。有关分面参数的讨论,请参阅字段值分面参数

<filterCache class="solr.CaffeineCache"
             size="512"
             initialSize="512"
             autowarmCount="128"/>

该缓存支持 maxRamMB 参数,该参数限制此缓存使用的最大堆内存量。CaffeineCache 仅支持按堆使用量或大小进行驱逐,但不能同时按两者驱逐。因此,如果指定了 maxRamMB,则 size 参数将被忽略。

<filterCache class="solr.CaffeineCache"
             maxRamMB="1000"
             autowarmCount="128"/>

过滤器缓存是启用 async 计算的理想选择。

<filterCache class="solr.CaffeineCache"
             size="1024"
             autowarmCount="0"
             async="true"/>

查询结果缓存

queryResultCache 保存先前搜索的结果:基于查询、排序和请求的文档范围的文档 ID(DocList)的有序列表。

queryResultCache 具有一个可选设置,可以限制使用的最大 RAM 量 (maxRamMB)。这允许您指定此缓存内容使用的最大堆大小(以兆字节为单位)。当缓存超出此大小时,最早访问的查询将被驱逐,直到缓存的堆使用量降低到指定的限制以下。如果在 maxRamMB 之外还指定了 size,则仅会遵守堆使用量限制。

<queryResultCache class="solr.CaffeineCache"
                  size="512"
                  initialSize="512"
                  autowarmCount="128"/>

文档缓存

documentCache 保存 Lucene Document 对象(每个文档的存储字段)。由于 Lucene 内部文档 ID 是临时的,因此此缓存不会自动预热。

documentCache 的大小应始终大于 max_results 乘以 max_concurrent_queries,以确保 Solr 在请求期间不需要重新获取文档。您在文档中存储的字段越多,此缓存的内存使用量就越高。

<documentCache class="solr.CaffeineCache"
               size="512"
               initialSize="512"
               autowarmCount="0"/>
请勿将 maxRamMB 设置用于 documentCache。缓存文档所需的内存量将无法正确计算,这可能导致缓存使用的内存远远超出预期。

用户定义的缓存

您还可以定义命名缓存供您自己的应用程序代码使用。您可以通过调用 SolrIndexSearcher 方法 getCache()cacheLookup()cacheInsert(),按名称查找和使用您的缓存对象。

<cache name="myUserCache" class="solr.CaffeineCache"
                          size="4096"
                          initialSize="1024"
                          autowarmCount="1024"
                          regenerator="org.mycompany.mypackage.MyRegenerator" />

如果您希望自动预热您的缓存,请包含一个 regenerator 属性,该属性具有实现solr.search.CacheRegenerator的类的完全限定名称。您还可以使用 NoOpRegenerator,它只是用旧项重新填充缓存。使用 regenerator 参数将其定义为 regenerator="solr.NoOpRegenerator"

监控缓存大小和使用情况

缓存统计部分介绍了每个缓存可用的指标。可以在插件和统计屏幕或使用指标 API访问这些指标。

评估缓存时,最重要的指标是大小和命中率。

大小表示缓存中有多少项。一些缓存支持设置最大缓存大小(以 MB 为单位的 RAM)。

命中率是缓存服务的查询的百分比,显示为 0 到 1 之间的数字。较高的值表示经常使用缓存,而较低的值表示缓存对查询的帮助不大。理想情况下,此数字应尽可能接近 1。

如果您发现命中率较低,但您已将缓存大小设置得很高,则可以通过减小缓存大小来进行优化 - 当这些对象未使用时,无需将其保留在内存中。

另一个有用的指标是缓存驱逐,它衡量从缓存中删除的对象。高驱逐率可能表明您的缓存太小,增加它可能会显示更高的命中率。或者,如果您的命中率很高但驱逐率很低,则您的缓存可能太大,您可能会受益于减小大小。

低命中率并不总是特定缓存问题的征兆。如果您的查询不经常重复,则预计命中率会很低,因为缓存的对象不太可能需要重用。在这些情况下,较小的缓存大小可能最适合您的系统。

查询大小调整和预热

有几个元素可用于控制查询的大小以及缓存的预热方式。

<maxBooleanClauses> 元素

设置解析布尔查询字符串时允许的最大子句数。

此限制仅影响用户作为查询字符串的一部分指定的布尔查询,并提供对用户指定的布尔查询的复杂程度的每个集合的控制。指定的子句数超过此限制的查询将导致错误。

如果此每个集合的限制大于在 solr.xml 中指定的全局 maxBooleanClauses 限制,则它将不起作用,因为该设置也会限制用户指定的布尔查询的大小。

在默认配置中,如果指定了此属性,则它使用 solr.max.booleanClauses 系统属性的值。这与默认 solr.xml 中全局 maxBooleanClauses 设置中使用的系统属性相同,这使得 Solr 管理员可以轻松地增加两个值(在所有集合中),而无需在每个集合中搜索和更新 solrconfig.xml 文件。

<maxBooleanClauses>${solr.max.booleanClauses:1024}</maxBooleanClauses>

<minPrefixQueryTermLength> 元素

基于前缀的查询消耗的资源与索引中以指定前缀开头的术语数量成正比。特别是短前缀,例如只有一两个字符的前缀,往往与索引中的大部分匹配,因此它们是资源争用和不稳定的常见原因。此设置建立查询的最小前缀长度,为管理员提供了一种阻止可能导致稳定性问题的查询的方法。不匹配此最小前缀长度的查询会触发错误。可以通过提供具有不同值的 minPrefixQueryTermLength “本地参数”在每个查询的基础上覆盖此设置。

该设置旨在控制所有基于前缀的查询(例如,val_s:a*{!prefix f=val_s}a{!complexphrase}val_s:"a*")。

在默认配置集中,最小前缀设置为 '-1'(具有“无限制”语义的标志值)或 solr.query.minPrefixLength 系统属性的值(如果指定)。

<enableLazyFieldLoading> 元素

当此参数设置为 true 时,未直接请求的字段将仅在需要时加载。

如果最常见的查询只需要一小部分字段,尤其是如果很少访问的字段很大,这可以提高性能。

<enableLazyFieldLoading>true</enableLazyFieldLoading>

<useFilterForSortedQuery> 元素

此设置仅影响请求的排序不包含“分数”的查询(或者分数不相关的情况,例如,未请求任何文档,查询输出一个恒定分数)。在这种情况下,将此元素配置为 true 会导致查询 filterCache 以查找与主查询匹配的过滤器。如果使用不同的排序选项或通过 offsetcursorMark 使用不同的分页重复发出相同的搜索,则此功能非常有用(同样,前提是“分数”不包括在内或与请求的排序无关)。如果启用此选项,请确保 filterCache 具有足够的容量来支持预期的使用模式。

<useFilterForSortedQuery>true</useFilterForSortedQuery>

<queryResultWindowSize> 元素

queryResultCache 一起使用时,这将缓存请求的文档 ID 数的超集。

例如,如果查询请求文档 10 到 19,并且 queryWindowSize 为 50,则将缓存文档 0 到 49。

<queryResultWindowSize>20</queryResultWindowSize>

<queryResultMaxDocsCached> 元素

此参数设置 queryResultCache 中任何条目要缓存的最大文档数。

<queryResultMaxDocsCached>200</queryResultMaxDocsCached>

<useColdSearcher> 元素

此设置控制没有当前注册的搜索器的搜索请求是应等待新的搜索器预热 (false) 还是应立即继续 (true)。当设置为 false 时,请求将阻塞,直到搜索器预热其缓存。

<useColdSearcher>false</useColdSearcher>

<maxWarmingSearchers> 元素

此参数设置在任何给定时间可能在后台预热的最大搜索器数量。超过此限制将引发错误。

对于只读关注者,2 的值是合理的。领导者可能应该设置得高一点。

<maxWarmingSearchers>2</maxWarmingSearchers>

缓存部分所述,新的搜索器会被缓存。可以使用侦听器的触发器来执行与查询相关的任务。最常见的用途是定义查询以在搜索器启动时进一步“预热”它们。这种方法的一个好处是,预先填充了字段缓存以加快排序速度。

良好的查询选择是此类侦听器的关键。最好选择您最常见和/或最繁重的查询,并且不仅包括使用的关键字,还包括任何其他参数,例如排序或筛选请求。

有两种类型的事件可以触发侦听器。

  1. 当正在准备新的搜索器但没有当前注册的搜索器来处理请求或从(例如,在 Solr 启动时)获取自动预热数据时,会发生 firstSearcher 事件。

  2. 每当正在准备新的搜索器时(例如,在提交之后),并且有一个当前搜索器正在处理请求时,都会触发 newSearcher 事件。

下面的(注释掉的)示例可以在与 Solr 一起包含的 sample_techproducts_configs 配置集solrconfig.xml 文件中找到,并演示了使用 solr.QuerySenderListener 类来预热一组显式查询

<listener event="newSearcher" class="solr.QuerySenderListener">
  <arr name="queries">
  <!--
    <lst><str name="q">solr</str><str name="sort">price asc</str></lst>
    <lst><str name="q">rocks</str><str name="sort">weight asc</str></lst>
   -->
  </arr>
</listener>

<listener event="firstSearcher" class="solr.QuerySenderListener">
  <arr name="queries">
    <lst><str name="q">static firstSearcher warming in solrconfig.xml</str></lst>
  </arr>
</listener>

以上代码来自一个示例 solrconfig.xml

一个关键的最佳实践是在将应用程序投入生产环境之前修改这些默认值,但请注意:虽然示例查询在“newSearcher”部分中被注释掉了,但“firstSearcher”事件的示例查询没有被注释掉。

如果查询字符串“static firstSearcher warming in solrconfig.xml”与您的搜索应用程序无关,那么使用它来自动预热您的搜索器就没有意义。

使用配置 API 管理预热查询

可以使用 配置 API 通过如下命令来管理预热查询。

可以提供多个具有不同 name 属性的集合,任何更改都将立即生效,无需重启。

添加预热查询集

要添加一组预热查询,请使用 add-listener 命令。(您可能还希望使用单独的命令和 name 提供 firstSearcher 查询。)

{
  "add-listener": {
    "name": "my-warming-queries",
    "event": "newSearcher",
    "class": "solr.QuerySenderListener",
    "queries": [
      { "q": "solr", "sort": "price asc" }
    ]
  }
}

更新查询集

要更新一个集合,请使用 update-listener 命令。请注意,整个集合将被替换。

{
  "update-listener": {
    "name": "my-warming-queries",
    "event": "newSearcher",
    "class": "solr.QuerySenderListener",
    "queries": [
      { "q": "solr", "sort": "price asc" },
      { "q": "rocks", "sort": "weight asc" }
    ]
  }
}

如果在 solrconfig.xml 中定义了预热查询,则如果为每个 <listener> 元素添加了 name 属性,则可以使用配置 API 覆盖它们。

删除查询集

要删除一组查询,请使用 delete-listener 命令。

{
  "delete-listener": "my-warming-queries"
}

请注意,对于最初在 solrconfig.xml 中定义的预热查询集,使用 delete-listener 命令将恢复到 solrconfig.xml 中的设置,而不是删除预热查询。