欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

【译文】ES索引和查询性能调优
(last modified Jul 4, 2023, 9:40 PM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

【译文】ES索引和查询性能调优

橙序员
2023-07-03 / 0 评论 / 0 点赞 / 410 阅读 / 9,121 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

原文:https://www.elastic.co/guide/en/elasticsearch/reference/current/how-to.htmlElasticsearch 附带了默认值,旨在提供良好的开箱即用体验。全文搜索、突出显示、聚合和索引都应该可以正常工作,而无需用户进行任何更改。

原文:https://www.elastic.co/guide/en/elasticsearch/reference/current/how-to.html

Elasticsearch 附带了默认值,旨在提供良好的开箱即用体验。全文搜索、突出显示、聚合和索引都应该可以正常工作,而无需用户进行任何更改。

然而,一旦您更好地了解了如何使用 Elasticsearch,您就可以进行许多优化来提高您的用例的性能。

本节提供有关应该和不应该进行哪些更改的指导。

一般建议

不要返回大型结果集

Elasticsearch 被设计为一个搜索引擎,这使得它非常擅长检索与查询匹配的顶级文档。但是,它对于属于数据库域的工作负载(例如检索与特定查询匹配的所有文档)来说并不那么好。如果您需要执行此操作,请确保使用Scroll API。

避免大文档

鉴于默认http.max_content_length设置为 100MB,Elasticsearch 将拒绝索引任何大于该值的文档。您可能决定增加该特定设置,但 Lucene 仍然有大约 2GB 的限制。

即使不考虑硬限制,大文档通常也不实用。大型文档会给网络、内存使用和磁盘带来更大的压力,即使对于不请求的搜索请求也是如此,因为_sourceElasticsearch 在所有情况下都需要获取_id文档的 ,并且对于大型文档来说,获取该字段的成本更大,因为如何文件系统缓存有效。索引此文档可能使用的内存量是文档原始大小的倍数。邻近搜索(例如短语查询)和突出显示也变得更加昂贵,因为它们的成本直接取决于原始文档的大小。

有时重新考虑信息的单位应该是什么是有用的。例如,您想要使书籍可搜索这一事实并不一定意味着文档应该包含整本书。使用章节甚至段落作为文档,然后在这些文档中拥有一个属性来标识它们属于哪本书,这可能是一个更好的主意。这不仅避免了大文档的问题,还使搜索体验更好。例如,如果用户搜索两个单词foobar,则不同章节之间的匹配可能非常差,而同一段落内的匹配可能很好。

秘诀

使用词干精准搜索

在构建搜索应用程序时,词干提取通常是必须的,因为希望查询skiing能够匹配包含skiskis的文档。但如果用户想要专门搜索skiing怎么办?执行此操作的典型方法是使用多字段,以便以两种不同的方式对相同的内容进行索引:

PUT index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "english_exact": {
          "tokenizer": "standard",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "body": {
        "type": "text",
        "analyzer": "english",
        "fields": {
          "exact": {
            "type": "text",
            "analyzer": "english_exact"
          }
        }
      }
    }
  }
}

PUT index/_doc/1
{
  "body": "Ski resort"
}

PUT index/_doc/2
{
  "body": "A pair of skis"
}

POST index/_refresh

通过这样的设置,在body中搜索ski将返回两个文档:

GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body" ],
      "query": "ski"
    }
  }
}

结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 2,
        "relation": "eq"
    },
    "max_score": 0.18232156,
    "hits": [
      {
        "_index": "index",
        "_id": "1",
        "_score": 0.18232156,
        "_source": {
          "body": "Ski resort"
        }
      },
      {
        "_index": "index",
        "_id": "2",
        "_score": 0.18232156,
        "_source": {
          "body": "A pair of skis"
        }
      }
    ]
  }
}

另一方面,在body.exact搜索ski只会返回文档1,因为body.exact的分析链不执行词干提取。

GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body.exact" ],
      "query": "ski"
    }
  }
}

结果:

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.8025915,
    "hits": [
      {
        "_index": "index",
        "_id": "1",
        "_score": 0.8025915,
        "_source": {
          "body": "Ski resort"
        }
      }
    ]
  }
}

这并不是一件容易向最终用户公开的事情,因为我们需要有一种方法来确定他们是否正在寻找完全匹配的内容,并相应地重定向到适当的字段。另外,如果只有查询的一部分需要精确匹配,而其他部分仍应考虑词干提取,该怎么办?

幸运的是,query_stringsimple_query_string查询有一个功能可以解决这个确切的问题:quote_field_suffix。这告诉 Elasticsearch 引号之间出现的单词将被重定向到不同的字段,如下所示:

GET index/_search
{
  "query": {
    "simple_query_string": {
      "fields": [ "body" ],
      "quote_field_suffix": ".exact",
      "query": "\"ski\""
    }
  }
}

结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped" : 0,
    "failed": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 0.8025915,
    "hits": [
      {
        "_index": "index",
        "_id": "1",
        "_score": 0.8025915,
        "_source": {
          "body": "Ski resort"
        }
      }
    ]
  }
}

在上面的情况下,由于ski位于引号之间, 因此由于quote_field_suffix参数而在字段body.exact上搜索它,因此只有文档 1匹配。这允许用户根据需要将精确搜索与词干搜索混合。

如果字段quote_field_suffix在索引中不存在,搜索将回退到使用查询字符串的默认字段。

如何返回正确结果

事实上,Elasticsearch 使用分片和副本进行操作,这增加了获得良好评分的挑战。

分数不可重复(结果不一致)

假设同一用户连续两次运行相同的请求,并且文档两次都没有以相同的顺序返回,这是一种非常糟糕的体验,不是吗?不幸的是,如果您有副本(index.number_of_replicas大于 0),则可能会发生这种情况。原因是 Elasticsearch 以循环方式选择查询应转到的分片,因此如果您连续运行同一查询两次,它很可能会转到同一分片的不同副本。

现在为什么会出现问题呢?指标统计是分数的重要组成部分。由于删除的文档,这些索引统计信息在同一分片的副本之间可能会有所不同。您可能知道,当删除或更新文档时,旧文档不会立即从索引中删除,它只是被标记为已删除,并且只有在下次合并该旧文档所属的段时才会从磁盘中删除。但出于实际原因,索引统计中会考虑那些已删除的文档。因此,想象一下主分片刚刚完成了一次大型合并,删除了许多已删除的文档,那么它的索引统计信息可能与副本(仍然有大量已删除的文档)有很大不同,因此分数也不同。

解决此问题的推荐方法是**使用标识登录用户的字符串(例如用户 ID或会话 ID)**作为首选项。这确保了给定用户的所有查询始终会命中相同的分片,因此跨查询的分数保持更加一致。

这种解决方法还有另一个好处:当两个文档具有相同分数时,默认情况下它们将按其内部 Lucene 文档 id(与_id无关)排序 。然而,这些文档 ID 在同一分片的不同副本中可能会有所不同。因此,通过始终命中相同的分片,我们将获得具有相同分数的文档的更一致的排序。

相关性太差(结果乱序)

如果您发现具有相同内容的两个文档获得不同的分数,或者完全匹配的文档未排名第一,则问题可能与分片有关。默认情况下,Elasticsearch 让每个分片负责生成自己的分数。然而,由于索引统计数据是分数的重要贡献者,因此只有当分片具有相似的索引统计数据时,这才有效。假设是,由于默认情况下文档均匀路由到分片,因此索引统计数据应该非常相似,并且评分将按预期进行。但是,如果您:

  • 在检索时使用路由,
  • 查询了多个索引
  • 或者索引中的数据太少

那么很有可能搜索请求中涉及的所有分片都没有类似的索引统计信息,并且相关性可能很差。

如果您的数据集较小,解决此问题的最简单方法是将所有内容都索引到具有单个分片 ( index.number_of_shards: 1) 的索引中,这是默认设置。那么所有文档的索引统计数据将相同,分数也将一致。

否则,解决此问题的推荐方法是使用 dfs_query_then_fetch搜索类型进行检索。这将使 Elasticsearch 对所有涉及的分片执行初始往返,询问它们相对于查询的索引统计信息,然后协调节点将合并这些统计信息,并在要求分片执行该阶段时将合并的统计信息与请求一起发送,query因此分片可以使用这些全局统计数据而不是他们自己的统计数据来进行评分。

在大多数情况下,这种额外的往返旅行应该非常高效。但是,如果您的查询包含大量字段/术语或模糊查询,请注意,单独收集统计信息可能并不高效,因为必须在术语词典中查找所有术语才能查找统计信息。

将静态相关性信号纳入评分中

许多领域都有已知与相关性相关的静态信号。举个例子,PageRank和 url 长度是网络搜索的两个常用功能,以便独立于查询调整网页的分数。

script_score查询rank_feature查询两个主要查询允许将静态分数贡献与文本相关性相结合,

例如,使用 BM25 计算。假设您有一个pagerank字段希望与 BM25 分数相结合,以便最终分数等于 score = bm25_score + pagerank / (10 + pagerank)

通过script_score查询,查询将如下所示:

GET index/_search
{
  "query": {
    "script_score": {
      "query": {
        "match": { "body": "elasticsearch" }
      },
      "script": {
        "source": "_score * saturation(doc['pagerank'].value, 10)" 
      }
    }
  }
}

pagerank必须映射为数字

当使用rank_feature查询时,它看起来像下面这样:

GET _search
{
  "query": {
    "bool": {
      "must": {
        "match": { "body": "elasticsearch" }
      },
      "should": {
        "rank_feature": {
          "field": "pagerank", 
          "saturation": {
            "pivot": 10
          }
        }
      }
    }
  }
}

pagerank必须映射为rank_feature字段

虽然这两个选项都会返回相似的分数,但也有一些权衡: script_score提供了很大的灵活性,使您能够根据需要将文本相关性分数与静态信号结合起来。另一方面,rank_feature查询仅公开了将静态信号合并到分数中的几种方法。但是,它依赖于rank_featurerank_features字段,它们以特殊方式对值进行索引,允许查询rank_feature跳过非竞争性文档并更快地获取查询的顶级匹配项。

优化索引速度

使用批量请求

批量请求将比单文档索引请求产生更好的性能。为了了解批量请求的最佳大小,您应该在具有单个分片的单个节点上运行基准测试。首先尝试一次索引 100 个文档,然后是 200 个文档,然后是 400 个文档,等等,在每次基准测试运行中将批量请求中的文档数量加倍。当索引速度开始趋于稳定时,您就知道您已达到批量数据请求的最佳大小。如果出现平局,最好是文档太少而不是太多。请注意,当同时发送许多请求时,太大的批量请求可能会使集群面临内存压力,因此建议避免每个请求超过几十兆字节,即使较大的请求似乎性能更好。

使用多个工作线程/线程将数据发送到 Elasticsearch

发送批量请求的单个线程不太可能最大化 Elasticsearch 集群的索引容量。为了使用集群的所有资源,您应该从多个线程或进程发送数据。除了更好地利用集群的资源之外,这应该有助于降低每次 fsync 的成本。

请务必注意TOO_MANY_REQUESTS (429)响应代码(EsRejectedExecutionException使用 Java 客户端),这是 Elasticsearch 告诉您它无法跟上当前索引速率的方式。当发生这种情况时,您应该在重试之前暂停索引,最好使用随机指数退避。

与调整批量请求的大小类似,只有测试才能知道最佳的工作人员数量是多少。可以通过逐渐增加工作线程数量直到集群上的 I/O 或 CPU 饱和来测试这一点。

取消设置或增加刷新间隔

使更改对搜索可见的操作(称为刷新 成本高昂,并且在正在进行的索引活动时经常调用它可能会损害索引速度。

默认情况下,Elasticsearch 每秒定期刷新索引,但仅限于过去 30 秒内收到一个或多个搜索请求的索引。

如果您没有或只有很少的搜索流量(例如每 5 分钟少于一个搜索请求)并且想要优化索引速度,那么这是最佳配置。此行为的目的是在不执行搜索的默认情况下自动优化批量索引。为了选择退出此行为,请显式设置刷新间隔。

另一方面,如果您的索引遇到常规搜索请求,则此默认行为意味着 Elasticsearch 将每 1 秒刷新您的索引。如果您可以增加文档被索引到可见之间的时间量,则将 增加到 index.refresh_interval更大的值,例如 30s,可能有助于提高索引速度。

禁用初始加载的副本

如果您有大量数据想要一次性加载到 Elasticsearch 中,设置为以加快索引速度可能会有所index.number_of_replicas帮助0。没有副本意味着丢失单个节点可能会导致数据丢失,因此数据位于其他位置非常重要,以便在出现问题时可以重试初始加载。初始加载完成后,您可以设置index.number_of_replicas回其原始值。

如果index.refresh_interval在索引设置中进行了配置,则可能会进一步帮助在初始加载期间取消设置它,并在初始加载完成后将其设置回其原始值。

禁用交换

您应该确保操作系统不会通过禁用交换来交换 java 进程。

为文件系统缓存提供内存

文件系统缓存将用于缓冲 I/O 操作。您应该确保将运行 Elasticsearch 的机器的至少一半内存分配给文件系统缓存。

使用自动生成的 id

当索引具有显式 id 的文档时,Elasticsearch 需要检查同一分片中是否已存在具有相同 id 的文档,这是一项成本高昂的操作,并且随着索引的增长,成本会变得更高。通过使用自动生成的 id,Elasticsearch 可以跳过此检查,从而加快索引速度。

使用更快的硬件

如果索引受 I/O 限制,请考虑增加文件系统缓存的大小(见上文)或使用更快的存储。Elasticsearch 通常创建具有顺序写入的单个文件。然而,索引涉及同时写入多个文件,以及随机读取和顺序读取的混合,因此 SSD 驱动器的性能往往比旋转磁盘更好。

通过配置 RAID 0 阵列将索引跨多个 SSD 进行条带化。请记住,这会增加故障风险,因为任何一个 SSD 的故障都会破坏索引。然而,这通常是正确的权衡:优化单个分片以获得最大性能,然后跨不同节点添加副本,以便为任何节点故障提供冗余。您还可以使用 快照和恢复来备份索引以进一步保险。

直连(本地)存储通常比远程存储性能更好,因为它更易于配置并避免通信开销。通过仔细调整,有时使用远程存储也可以获得可接受的性能。使用实际工作负载对您的系统进行基准测试,以确定任何调整参数的效果。如果您无法达到预期的性能,请与存储系统的供应商合作找出问题。

索引缓冲区大小

如果您的节点仅执行繁重索引,请确保 indices.memory.index_buffer_size足够大,以便为执行繁重索引的每个分片提供最多 512 MB 索引缓冲区(超出该范围索引性能通常不会提高)。Elasticsearch 采用该设置(Java 堆的百分比或绝对字节大小),并将其用作所有活动分片之间的共享缓冲区。非常活跃的分片自然会比执行轻量级索引的分片更多地使用此缓冲区。

默认值10%通常是足够的:例如,如果您为 JVM 提供 10GB 内存,它将为索引缓冲区提供 1GB,这足以托管两个需要大量索引的分片。

使用跨集群复制来防止搜索从索引中窃取资源

在单个集群中,索引和搜索可能会争夺资源。通过设置两个集群,配置跨集群复制以将数据从一个集群复制到另一个集群,并将所有搜索路由到具有跟随者索引的集群,搜索活动将不再从托管该集群的集群上的索引中窃取资源。领先指数。

避免热点

当节点资源、分片或请求分布不均匀时,可能会出现热点。Elasticsearch 通过跨节点同步来维护集群状态,因此持续出现热点节点可能会导致集群整体性能下降。

额外的优化

Tune中概述的许多磁盘使用策略也提高了索引速度。

优化搜索速度

为文件系统缓存提供内存

Elasticsearch 严重依赖文件系统缓存来提高搜索速度。一般来说,您应该确保至少一半的可用内存用于文件系统缓存,以便 Elasticsearch 可以将索引的热区域保留在物理内存中。

在 Linux 上使用适度的预读值来避免页面缓存抖动

搜索可能会导致大量随机读取 I/O。当底层块设备具有较高的预读值时,可能会执行大量不必要的读 I/O,特别是当使用内存映射访问文件时(请参阅存储类型)。

大多数 Linux 发行版对单个普通设备使用合理的预读值128KiB,但是,当使用软件 raid、LVM 或 dm-crypt 时,生成的块设备(支持 Elasticsearch path.data)最终可能会具有非常大的预读值(在几个 MiB 的范围)。这通常会导致严重的页面(文件系统)缓存抖动,从而对搜索(或更新)性能产生不利影响。

您可以使用lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE来检查KiB值。有关如何更改此值的信息,请查阅发行版的文档(例如,使用udev重新启动后仍保留该规则,或通过 blockdev --setra 作为临时设置)。我们建议预读值为128KiB

blockdev 期望值以 512 字节扇区为单位,而 lsblk 报告值以 KiB 为单位。例如,要将 /dev/nvme0n1 的预读临时设置为 128KiB,请指定 blockdev --setra 256 /dev/nvme0n1。

使用更快的硬件

如果您的搜索受 I/O 限制,请考虑增加文件系统缓存的大小(见上文)或使用更快的存储。每次搜索都涉及跨多个文件的顺序和随机读取的混合,并且每个分片上可能同时运行许多搜索,因此 SSD 驱动器的性能往往比旋转磁盘更好。

直连(本地)存储通常比远程存储性能更好,因为它更易于配置并避免通信开销。通过仔细调整,有时使用远程存储也可以获得可接受的性能。使用实际工作负载对您的系统进行基准测试,以确定任何调整参数的效果。如果您无法达到预期的性能,请与存储系统的供应商合作找出问题。

如果您的搜索受 CPU 限制,请考虑使用更多更快的 CPU。

文档建模

应该对文档进行建模,以便尽可能减少搜索时间操作。

特别是应该避免nested连接。可以使查询慢几倍,而父子关系可以使查询慢数百倍。因此,如果可以通过非规范化文档来回答相同的问题,而无需连接,则可以显著提高查询速度。

搜索尽可能少的字段

query_stringmulti_match查询目标的字段越多,速度就越慢。提高多个字段搜索速度的常用技术是在索引时将它们的值复制到单个字段中,然后在搜索时使用该字段。这可以通过映射指令copy-to自动实现,而无需更改文档源。下面是一个包含电影的索引示例,该索引通过将两个值索引到字段name_and_plot中来优化搜索电影名称和情节的查询 。

PUT movies
{
  "mappings": {
    "properties": {
      "name_and_plot": {
        "type": "text"
      },
      "name": {
        "type": "text",
        "copy_to": "name_and_plot"
      },
      "plot": {
        "type": "text",
        "copy_to": "name_and_plot"
      }
    }
  }
}

索引前数据

您应该利用查询中的模式来优化数据索引方式。例如,如果所有文档都有一个price字段,并且大多数查询在固定的范围列表上运行 range聚合,则可以通过将范围预先索引到索引中并使用聚合来加快聚合速度terms

例如,如果文档如下所示:

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13
}

搜索请求如下所示:

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}

然后可以在索引时通过字段price_range来丰富文档,该字段应该映射为keyword

PUT index
{
  "mappings": {
    "properties": {
      "price_range": {
        "type": "keyword"
      }
    }
  }
}

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}

然后搜索请求可以聚合这个新字段,而不是在该price字段上运行 range聚合。

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}

将映射标识符作为keyword

并非所有数值数据都应映射为数值字段数据类型。Elasticsearch 优化查询的数字字段,例如integerlongrange查询。但是,keyword字段更适合其他 term查询。

ISBN 或产品 ID 等标识符很少在range 查询中使用。然而,它们通常是使用term查询来检索的。

如果考虑将数字标识符映射为keyword

  • 您不打算使用 range查询来搜索标识符数据。
  • 快速检索很重要。对keyword字段的term查询通常比对数字字段的term查询更快。

如果您不确定使用哪个,可以使用多字段将数据映射为 keyword numeric 两个数据类型。

避免脚本

如果可能,请避免使用基于脚本的排序、聚合中的脚本和script_score查询。请参阅 脚本、缓存和搜索速度

搜索四舍五入的日期

对使用now的日期字段的查询通常不可缓存,因为匹配的范围一直在变化。然而,就用户体验而言,切换到四舍五入日期通常是可以接受的,并且具有更好地利用查询缓存的好处。

例如下面的查询:

PUT index/_doc/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}

可以被替换为:

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}

在这种情况下,我们四舍五入到分钟。因此,如果当前时间是16:31:29,范围查询将匹配 my_date 字段值在 15:31:0016:31:59 之间的所有内容。如果多个用户在同一分钟内运行包含此范围的查询,则查询存储可以帮助加快速度。对于舍入的间隔越长,查询存储的帮助就越大,但请注意,过度激进的舍入也可能会损害用户体验。

为了能够利用查询缓存,可能会将范围分割为较大的可缓存部分和较小的不可缓存部分,如下所示:

GET index/_search
{
"query": {
 "constant_score": {
   "filter": {
     "bool": {
       "should": [
         {
           "range": {
             "my_date": {
               "gte": "now-1h",
               "lte": "now-1h/m"
             }
           }
         },
         {
           "range": {
             "my_date": {
               "gt": "now-1h/m",
               "lt": "now/m"
             }
           }
         },
         {
           "range": {
             "my_date": {
               "gte": "now/m",
               "lte": "now"
             }
           }
         }
       ]
     }
   }
 }
}
}

然而,这种做法在某些情况下可能会使查询运行速度变慢,因为bool查询引入的开销可能会抵消更好地利用查询缓存所节省的成本。

强制合并只读索引

只读索引可能会受益于合并到单个段。基于时间的索引通常就是这种情况:只有当前时间范围的索引正在获取新文档,而旧索引是只读的。已强制合并为单个分段的分片可以使用更简单、更高效的数据结构来执行搜索。

不要强制合并您仍在写入或将来将再次写入的索引。相反,依靠自动后台合并进程根据需要执行合并,以保持索引平稳运行。如果您继续写入强制合并索引,那么它的性能可能会变得更糟。

预热全局序数

全局序数是一种用于优化聚合性能的数据结构。它们是惰性计算的,并作为字段数据缓存的一部分存储在 JVM 堆中。对于大量用于分桶聚合的字段,您可以告诉 Elasticsearch 在收到请求之前构建并缓存全局序号。应该谨慎执行此操作,因为它会增加堆使用量并使刷新时间更长。通过设置eager 全局序数映射参数,可以在现有映射上动态更新该选项:

PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}

预热文件系统缓存

如果运行 Elasticsearch 的机器重新启动,文件系统缓存将为空,因此操作系统需要一些时间才能将索引的热区域加载到内存中,以便搜索操作快速。您可以使用index.store.preload设置明确告诉操作系统哪些文件应根据文件扩展名立即加载到内存中 。

如果文件系统缓存不够大,无法容纳所有数据,则在太多索引或太多文件上急切地将数据加载到文件系统缓存中将使搜索速度*变慢。*谨慎使用。

副本可能有助于提高吞吐量,但并不总是如此

除了提高弹性之外,副本还可以帮助提高吞吐量。例如,如果您有一个单分片索引和三个节点,则需要将副本数设置为 2,以便总共拥有 3 个分片副本,以便利用所有节点。

现在假设您有一个 2 分片索引和两个节点。在一种情况下,副本数为 0,这意味着每个节点拥有一个分片。在第二种情况下,副本数为 1,这意味着每个节点有两个分片。哪种设置在搜索性能方面表现最佳?通常,每个节点总共具有较少分片的设置会表现更好。原因是它为每个分片提供了更大份额的可用文件系统缓存,并且文件系统缓存可能是 Elasticsearch 的第一大性能因素。同时,请注意,如果单个节点发生下线,没有设置副本集群会故障,因此在吞吐量和可用性之间需要进行权衡。

那么正确的副本数量是多少?如果您的集群总共num_nodes节点和num_primaries主分片,并且您希望能够最多一次处理在max_failures以内的故障节点,那么适合您的副本数量是max(max_failures, ceil(num_nodes / num_primaries) - 1)

使用搜索分析器调整您的查询

Profile API提供有关查询和聚合的每个组件如何影响处理请求所需时间的详细信息。

Kibana 中的搜索分析器可以轻松导航和分析分析结果,并让您深入了解如何调整查询以提高性能并减少负载。

由于 Profile API 本身会显着增加查询开销,因此此信息最好用于了解各种查询组件的相对成本。它不提供实际处理时间的可靠测量。

使用index_phrases进行更快的短语查询

text字段有一个index_phrases为 2-shingles 建立索引的选项,并由查询解析器自动利用来运行没有倾斜的短语查询。如果您的用例涉及运行大量短语查询,这可以显着加快查询速度。

使用index_prefixes进行更快的前缀查询

text字段有一个index_prefixes选项,可以索引所有术语的前缀,并由查询解析器自动利用来运行前缀查询。如果您的用例涉及运行大量前缀查询,这可以显着加快查询速度。

使用constant_keyword加速过滤

一般规则是过滤器的成本主要是匹配文档数量的函数。想象一下您有一个包含循环的索引,自行车数量很多,许多搜索都会对cycle_type: bicycle进行过滤。不幸的是,这种非常常见的过滤器也非常耗时,因为它匹配大多数文档。有一种简单的方法可以避免运行此过滤器:将自行车移动到自己的索引并通过搜索此索引来过滤自行车,而不是向查询添加过滤器。

不幸的是,这可能会使客户端逻辑变得棘手,但这正是 constant_keyword有帮助的地方。通过在包含自行车的索引上将含有值bicyclecycle_type映射为constant_keyword,客户端可以继续运行与在整体索引上运行完全相同的查询,并且Elasticsearch将通过忽略cycle_type上的过滤器来对自行车索引执行正确的操作,如果该值是自行车,否则不返回任何命中。

映射可能如下所示:

PUT bicycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "constant_keyword",
        "value": "bicycle"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

PUT other_cycles
{
  "mappings": {
    "properties": {
      "cycle_type": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

我们将索引分为两部分:一个仅包含自行车,另一个包含其他自行车:独轮车、三轮车等。然后在搜索时,我们需要搜索这两个索引,但不需要修改查询。

GET bicycles,other_cycles/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "description": "dutch"
        }
      },
      "filter": {
        "term": {
          "cycle_type": "bicycle"
        }
      }
    }
  }
}

bicycles索引上,Elasticsearch 将简单地忽略cycle_type 过滤器并将搜索请求重写为以下之一:

GET bicycles,other_cycles/_search
{
  "query": {
    "match": {
      "description": "dutch"
    }
  }
}

other_cycles 索引上,Elasticsearch 会很快发现 cycle_type 字段的术语字典中不存在 bicycle并返回没有命中的搜索响应。

通过将通用值放入专用索引中,这是一种降低查询成本的强大方法。这个想法也可以跨多个领域组合:例如,如果您跟踪每个自行车的颜色并且您的自行车索引最终包含大多数黑色自行车,您可以将其分为自行车黑色索引和自行车其他颜色索引。

此优化并不严格需要constant_keyword:还可以更新客户端逻辑,以便根据过滤器将查询路由到相关索引。然而,constant_keyword 使其变得透明,并允许将搜索请求与索引拓扑解耦,以换取很少的开销。

0

评论区