缓存和查询预热

Solr 的缓存提供了一种改善查询性能的基本方法。缓存可以存储文档、查询中使用的过滤器以及先前查询的结果。

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

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

solrconfig.xml 中的 <query>

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

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

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

缓存

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

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

缓存实现

Solr 带有用于不同类型缓存的默认 SolrCache 实现。

CaffeineCache 是由 Caffeine 缓存库 支持的实现。默认情况下,它使用窗口 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 禁用。还可以使用 cache 参数逐个查询禁用缓存,如 cache 本地参数 一节中所述。

每个缓存的详细信息如下所述。

过滤器缓存

此缓存保存已解析的查询以及与之匹配的所有文档的无序集合。除非此类集合微不足道,否则集合实现是一个位集。

Solr 使用 filterCache 最典型的方式是缓存每个 fq 搜索参数的结果,但也有一些其他情况。使用相同参数过滤器查询的后续查询会导致缓存命中并快速返回结果。有关 fq 的详细讨论,请参阅 fq(过滤器查询)参数。可以使用 cache 本地参数fq 禁用此缓存的使用。

使用此缓存的另一个 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,则仅尊重堆使用限制。

可以使用 cache 本地参数q 中逐个查询地禁用此缓存的使用。

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

文档缓存

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

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

<documentCache class="solr.CaffeineCache"
               size="512"
               initialSize="512"
               autowarmCount="0"/>

用户定义的缓存

您还可以为自己的应用程序代码定义命名缓存以供使用。您可以通过调用 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 为单位设置最大缓存大小。

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

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

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

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

查询大小和预热

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

<maxBooleanClauses> 元素

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

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

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

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

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

<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 configsetsolrconfig.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”事件的示例查询未进行注释。

如果查询字符串“solrconfig.xml 中的静态 firstSearcher 预热”与您的搜索应用程序无关,则没有必要用它自动预热您的搜索器。

使用 Config API 管理预热查询

可以使用 Config 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 属性,则可以使用 Config API 覆盖它们。

删除查询集

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

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

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