菜单
文档breadcrumb arrow Grafana Tempobreadcrumb arrow 管理breadcrumb arrow 优化搜索性能
开源

优化搜索性能

无论您是使用 TraceQL 还是原始搜索 API,Tempo 都会搜索指定时间范围内的所有块。根据您的数据量,这可能会导致查询缓慢。

本文档解释了 Tempo 中的读取路径工作原理、可用于调整读取路径的控制选项、核心配置选项的详细信息以及如何调整这些配置选项以充分利用您的 Tempo 集群。

一般建议是扩展您的 Compactor 和 Querier。额外的 Querier 可以更有效地并行运行作业,而额外的 Compactor 可以更积极地缩短您的块列表长度和数据副本(如果使用 RF=3)。

注意

所有搜索形式(基于 TraceQL 和标签的搜索)仅在 vParquet 及更高版本的块上受支持。v2 块只能用于按 ID 查找追踪。

Tempo 2.3 及更高版本支持专用属性列,这是另一个提升搜索性能的好方法。

开始之前

您应该了解 Tempo 的基本架构。

有关更多信息,请参阅 Tempo 架构,了解 Tempo 的工作原理。

词汇表

查询
由最终用户发出的搜索查询。例如,{ traceDuration > 1s } 是一个 TraceQL 查询。
作业
搜索查询的一个分片,最低的工作单元。一个查询被分解成多个作业并进行处理。
批处理
一组作业称为一个批处理。
前端
query-frontend 的另一个名称
Querier
负责执行(处理)作业,并将结果发送回 query-frontend。

Tempo 查询路径

Tempo 的查询路径由 query-frontend、Querier、Ingester、metrics-generator 和后端组成。

您可以将查询路径中的每个组件视为一个通用的生产者和工作者模型

  • query-frontend 是一个生产者,连接着多个工作者。query-frontend 接收单个查询并将其分片为多个作业(工作单元)。
  • Querier 是工作者。它们从队列中获取工作,处理后将结果发送回生产者(query-frontend)。
  • Querier 要么从后端读取数据并处理查询,要么根据作业类型、时间范围和查询类型将查询委托给 Ingester 和 metrics generator。

Tempo query path architecture

查询的生命周期

搜索请求来自用户,并在 query-frontend 中被分解成多个作业。

这些作业随后被添加到队列中,Querier 按批次获取作业进行处理。

一个搜索查询可以根据时间范围和其他变量创建数百个作业。这些作业被打包成批发送给 Querier。

Querier 接收到批次并处理批次中的作业,构建结果,并将整个批次的结果发送回 query-frontend。

query-frontend 合并并去重结果,然后将其发送回客户端。

当 Querier 启动时,它会与每个 query-frontend 建立多个连接。

作业批次通过这些连接发送并返回结果。此过程是同步的,因此单个作业批次可能会阻塞一个连接。

连接数控制 Querier 同时处理的批次数量。连接数由 querier.max_concurrent_queriesfrontend_worker.parallelism 控制。

通用指南

调整搜索管道可能很困难,因为它需要在多个不同的配置参数之间取得平衡。以下技巧可以帮助您理解一般问题,但具体情况需要实验验证。

  • 查看 query-frontend 日志中类似以下行,以了解您的查询生成了多少作业

    level=info ts=2023-07-19T19:38:01.354220385Z caller=searchsharding.go:236 msg="sharded search query request stats and SearchMetrics" ...
  • 对于单个 TraceQL 查询,最大并行作业数受以下因素限制

    • query_frontend.search.concurrent_jobs: 这是前端为一个 TraceQL 查询调度的最大作业数。
    • # querier 数量 * querier.max_concurrent_queries * query_frontend.max_batch_size: 这是您的 Tempo 集群的最大作业容量。如果给定的 TraceQL 查询生成的作业数小于这两个值,它应该在 Querier 上完全并行执行。
  • 增加 querier.max_concurrent_queries 是充分利用 Querier 的好方法。但是,如果 Querier 出现 OOM 或其他资源饱和,则应降低此值。降低 query_frontend.max_batch_size 也会减少单个 Querier 尝试的总工作量。

Querier 和 query-frontend 配置

Querier 和 query-frontend 还有与后端数据存储搜索相关的额外配置。

Querier

yaml
querier:
  # Control the amount of work each querier will attempt. The total number of
  # jobs a querier will attempt this is this value * query_frontend.max_batch_size
  max_concurrent_queries: 20

使用无服务器技术

注意

Tempo 无服务器功能现已弃用,并将在未来版本中移除。

注意

无服务器可以作为备用查询容量,是降低成本的好方法。然而,与直接让 Querier 执行搜索相比,无服务器的变动性往往更大。

yaml
querier:

  search:
    # A list of endpoints to query. Load will be spread evenly across
    # these multiple serverless functions.
    external_endpoints:
    - https://<serverless endpoint>

    # If set to a non-zero value a second request will be issued at the provided duration. Recommended to
    # be set to p99 of search requests to reduce long tail latency.
    external_hedge_requests_at: 8s

    # The maximum number of requests to execute when hedging. Requires hedge_requests_at to be set.
    external_hedge_requests_up_to: 2

Query-frontend

Query frontend 列出了所有配置选项。

这些建议有助于处理扩展问题。

yaml
server:
  # At larger scales, searching starts to feel more like a batch job.
  # Increase the server timeout intervals.
  http_server_read_timeout: 2m
  http_server_write_timeout: 2m

query_frontend:
  # When increasing concurrent_jobs, also increase the queue size per tenant,
  # or search requests will be cause 429 errors. This is the total number of jobs
  # per tenant allowed in the queue.
  max_outstanding_per_tenant: 2000

  # The number of jobs the query-frontend will batch together when passing jobs to the queriers. This value
  # This value * querier.max_concurrent_queries is your the max number of jobs a given querier will try at once.
  max_batch_size: 3

  search:
    # At larger scales, increase the number of jobs attempted simultaneously,
    # per search query.
    concurrent_jobs: 2000

    # The query-frontend will attempt to divide jobs up by an estimate of job size. The smallest possible
    # job size is a single parquet row group. Increasing this value will create fewer, larger jobs. Decreasing
    # it will create more, smaller jobs.
    target_bytes_per_job: 50_000_000

无服务器环境

注意

Tempo 无服务器功能现已弃用,并将在未来版本中移除。

无服务器不是必需的,但在负载较大时,可以使用无服务器来降低成本。Tempo 支持 Google Cloud Run 和 AWS Lambda。在这两种情况下,您都可以使用以下设置来配置 Tempo 使用无服务器环境

yaml
querier:
  search:
    # A list of external endpoints that the querier will use to offload backend search requests. They must
    # take and return the same value as /api/search endpoint on the querier. This is intended to be
    # used with serverless technologies for massive parallelization of the search path.
    # The default value of "" disables this feature.
    [external_endpoints: <list of strings> | default = <empty list>]

    # If external_endpoints is set then the querier will primarily act as a proxy for whatever serverless backend
    # you have configured. This setting allows the operator to have the querier prefer itself for a configurable
    # number of subqueries. In the default case of 2 the querier will process up to 2 search requests subqueries before starting
    # to reach out to external_endpoints.
    # Setting this to 0 will disable this feature and the querier will proxy all search subqueries to external_endpoints.
    [prefer_self: <int> | default = 2 ]

    # If set to a non-zero value a second request will be issued at the provided duration. Recommended to
    # be set to p99 of external search requests to reduce long tail latency.
    # (default: 4s)
    [external_hedge_requests_at: <duration>]

    # The maximum number of requests to execute when hedging. Requires hedge_requests_at to be set.
    # (default: 3)
    [external_hedge_requests_up_to: <int>]

云特定详细信息

可以安全增加且影响不大的设置。

扩展 Querier 是增加查询容量的安全方法。在 Grafana Labs,我们倾向于通过增加副本数来水平扩展查询。如果您看到内存不足 (OOM) 错误,可能值得垂直扩展 Querier。

我们建议至少运行两个 query-frontend 副本。这些副本应该垂直扩展而不是水平扩展。如果您需要扩展,请通过增加 CPU 和 RAM 来垂直扩展 query-frontend。目前,query-frontend 不进行水平扩展,但这将来可能会改变。

我们只保留两个副本的原因是每个 query-frontend 都有自己的请求队列,并且它也会影响发送给每个 Querier 的作业数量。如果您增加更多的 query-frontend,则需要调整其他配置参数以适应这种变化。我们目前决定将 query-frontend 保持为两个副本。

在专用集群中,您可以增加 query_frontend.max_outstanding_per_tenant,因为该集群专用于单个客户。在共享集群中,增加 querier.max_outstanding_per_tenant 需要更加谨慎。

参数交互

query_frontend 配置选项控制 Tempo 如何将一个查询分片成多个作业。这些选项还控制单个作业的大小、作业数量以及一次添加到工作队列中供 Querier 获取的作业数量(并发)。max_batch_size 选项控制在一个批次中一次发送给 Querier 的作业数量。

Querier 配置选项控制这些作业的处理,查询在作业级别操作。它们是工作者:它们从队列中获取一个作业并执行该作业,然后将结果返回给 query-frontend。Querier 选项通过向 query-frontend 打开并发连接来控制其一次处理多少作业,每个连接处理一个作业。

关键配置参数指南

以下部分提供了关键配置参数的建议。

query_frontend.max_outstanding_per_tenant 参数

query_frontend.max_outstanding_per_tenant 参数决定了每个租户每个前端的最大未完成请求数;超出此限制的请求将返回 HTTP 429 错误。

此配置控制单个租户可以对集群施加多少负载。返回 429 表示用户应该放慢速度。

此配置可用于通过向过载系统的租户发送 429 来要求租户放慢速度,从而维护租户的服务质量 (QoS)。

指南

  • 在拥有一个大租户的专用集群中,增加 query_frontend.max_outstanding_per_tenant 的数值是可以的。
  • 在拥有许多小租户的共享集群中,应保持此数值较小。
  • 如果单个租户正在压垮整个集群,您应该降低此参数。这会减少该租户一次可以入队的作业量,并开始向该租户返回 429 错误。
  • 如果您的租户抱怨收到 429 错误,您可能需要增加此参数并横向扩展查询以处理查询负载。
  • 如果您增加了其他配置以提高查询吞吐量或扩展 Querier,您可能需要降低此参数来控制可以入队的作业量。

query_frontend.max_batch_size 参数

在一次 HTTP 请求中一起发送给 Querier 的作业数量。长时间范围的大型搜索查询会产生数千个作业。与其逐个发送作业,不如将作业批量打包后一次发送。Querier 获取并处理它们。

批处理可以更快地将作业推送到 Querier,并减少等待 Querier 获取作业的时间。

指南

  • max_batch_size 的默认值设置为 5
  • 我们不建议更改 batch size 的默认值 5。根据 Grafana Labs 的测试,5 是一个不错的默认值。
  • 如果批次大小较大,您会一次性推送更多作业,但 Querier 处理该批次并返回结果所需的时间会更长。
  • 较大的 batch size 会增加 Querier 请求的延迟,它们可能会开始遇到 5xx 超时,这将增加重试率。
  • 更大的 max_batch_size 会导致向 Querier 推送过多的作业。如果查询提前退出,则必须取消这些作业。

query_frontend.search.concurrent_jobs 参数

搜索后端时并发执行的作业数量。这控制了并发产生的工作量。

换句话说,如果一个搜索作业被分片成 5000 个作业,而 concurrent_jobs 设置为 1000,那么 Tempo 只会并发执行 1000 个作业。当第一个 1000 个作业返回响应后,其余 4000 个作业将逐个处理。

如果 Tempo 在执行所有 5000 个作业之前设法回答了搜索查询,Tempo 将提前退出并取消尚未开始的作业。

指南

  • 此参数控制并发作业的数量以及一次放入队列中处理一个查询的作业数量。
  • 如果此数值较低,生成大量作业的大型查询处理速度会很慢。
  • 如果此数值较高,前端会更快地将工作推送到查询,并且如果搜索因为查询已满足而提前退出,前端需要取消这些作业。
  • 如果您看到 Querier 负载高、峰值出现或 OOM,您可以降低此配置以获得公平的 QoS 和稳定性。
  • 将此数值设置得很高将允许单个租户一次推送大量工作并压垮 Querier。因此,在共享集群中将其保持在较低数值可以实现公平调度。
  • 此参数控制作业被推入队列以便查询获取的速度
  • 调整此参数可能会影响您的批次大小,因此在更改此配置时应观察该指标,并确保实际批次大小接近 max_batch_size
  • 我们建议在具有单个大型租户的专用集群中将其保持在较高数值。
  • 我们建议在共享集群中将其保持在较低数值,因为这样可以确保单个租户不会压垮读取路径。

query_frontend.max_retries 参数

此选项控制向 Querier 发送请求的重试次数。我们仅对来自 Querier 的 5xx 错误(从等效的 gRPC 错误转换而来)进行重试。

指南

  • 此选项控制从 query-frontend 到 Querier 的重试。
  • max_retries 会放大慢速 Querier 的影响,并导致比所需更多的负载。
  • max_retries 可能导致用户因 Querier 失败而体验不佳,因为某个作业失败且未重试。
  • 如果集群中有太多重试,这可能是 Querier 难以完成工作并需要进行扩展的迹象。
  • 大型作业(高 target_bytes_per_jobmax_batch_size)可能导致重试次数增加。通过减少 target_bytes_per_jobmax_batch_size 来减小作业,从而减少重试。如果不行,则扩展 Querier。
  • 不要将 query_frontend.max_retries 设置得很高。默认值 2 是一个不错的默认值。
  • 当您有一个生成大量作业的大型查询时,更高的重试次数会影响后端。
  • max_retries 可能在 Querier 负载过重且未能处理作业时压垮它们。重试可能滚雪球般增加,并降低查询性能。

query_frontend.search.target_bytes_per_job 参数

执行后端搜索时,每个作业要处理的目标字节数。

此参数控制单个作业的大小,而作业大小决定了单个作业读取的数据量。您可以调整此参数来创建大型或小型作业。

此选项控制作业大小的上限,可用作衡量单个搜索作业扫描多少数据或 Querier 处理作业所需工作量的代理。

指南

  • 将其设置为较小值会产生太多作业,导致更多开销;将其设置得太高会产生大型作业。Querier 可能难以完成这些作业,并可能导致高延迟。
  • 在 Grafana Labs 的测试中,100MB 到 200MB 是此配置的良好范围,适用于不同规模的集群。
  • 我们建议将其固定在建议范围内。

querier.search.prefer_self 参数

注意

此配置仅适用于 tempo-serverless

此设置控制 Querier 在溢出到 search_external_endpoints (tempo-serverless) 之前处理的作业数量。

指南

  • 在 Grafana Labs 的测试中,无服务器存在冷启动问题。如果您的查询负载是可预测的,不建议使用无服务器。
  • 如果您想在 Querier 中处理更多作业并在极端情况下溢出,请增加 prefer_self 的值。
  • 将其设置为非常大的数值相当于关闭此功能,因为 Querier 会尝试处理所有作业,并且永远不会溢出到无服务器。
  • 如果将其设置为较低数值,即使 Querier 有能力处理作业,我们也会将更多作业溢出到无服务器,并且由于冷启动,查询延迟会增加。

querier.frontend_worker.parallelism 参数

每个 query-frontend 或 query-scheduler 同时处理的查询数量。此配置控制 Querier 进程处理的每个 query-frontend 的并发请求数量。

如果 parallelism 设置为 5 并且正在运行两个 query-frontend,那么一个 Querier 进程会与每个 query-frontend 打开 5 个连接,总共 10 个连接。

每个连接处理一个批次(如果禁用批处理,则处理一个作业),因此这控制了最大并发作业数量。

单个批次是同步处理的,批次大小由 max_batch_size 控制,连接会被阻塞直到处理完成并返回批次结果。

您也可以禁用此设置并使用 max_concurrent_connections,但我们使用此设置来确保 Querier 从两个 query-frontend 获取工作,并且作业得到公平调度。

如果您想确保始终拥有此配置中定义的连接数,您应该在配置中设置 match_max_concurrent: false,以确保您不受 max_concurrent_connections 的限制。

指南

  • 使用 parallelism 时 Querier 处理的最大作业数等于 parallelism * query_frontend 副本数 * max_batch_size
  • 随着您添加更多 Querier,与单个前端的连接会增加。Query-frontend 对这些连接有共享锁,因此如果您在横向扩展 Querier 时遇到问题,请降低 parallelism 以减少争用。
  • 建议在使用 parallelism 时设置 match_max_concurrent: false 并且不设置 max_concurrent_queries

querier.max_concurrent_queries 参数

这控制了 Querier 同时处理的最大作业数量。它不区分查询类型。这些作业可以来自两个不同的查询,也可以来自不同的租户。

Querier 同步处理这些作业,阻塞连接,一旦返回结果,它会从 query-frontend 队列中获取新的作业。

指南

  • max_concurrent_queries 参数控制 Querier 一次(并发)处理的查询数量。
  • 如果 Querier 利用率不足,增加此值以并发处理更多作业并加快查询速度。
  • 如果 Querier 过载、出现峰值或 OOM,您可以降低此配置以减少每个 Querier 的工作量,并通过横向扩展 Querier 来增加集群容量。
  • 此设置取决于资源,在小型 Querier 中,应将其设置为较低数值。
  • 如果您的 Querier 规模较大,应将此配置设置为较高数值,以充分利用 Querier 的容量。

Querier 内存配置

如果您发现查询使用的内存超出了您的预期,请减少 Querier 同时进行的工作量。这可以降低资源使用。

如果您希望查询使用比当前更多的内存,请增加 Querier 同时进行的工作量,这会增加资源使用。

您可以调整 query_frontend.search.target_bytes_per_jobquerier.frontend_worker.parallelismquerier.max_concurrent_queries 来调整 Querier 同时进行的工作量。

Querier 的内存请求大致等于作业大小乘以并发工作量再加上一些缓冲区。

扩展缓存

Tempo 可以配置为对不同类型的数据使用多个缓存。配置后,Tempo 会使用这些缓存来提高查询性能。

以下是一些关于何时扩展缓存的一般启发式方法

  • 查看缓存延迟。如果缓存延迟达到了 cache_timeout,这意味着缓存规模不足,读写花费的时间太长,应扩展缓存。
  • 如果缓存的逐出率很高,请扩展缓存。缓存可能配置不足。
  • Bloom 缓存、parquet-page 缓存、parquet-footer 缓存等较低层级缓存的命中率通常较高(通常在 90% 以上)。如果您有稳定的查询流量,并且这些较低层级缓存的命中率较低,则说明它们被低估了,需要进行扩展。
  • frontend-search 缓存等较高层级缓存的命中率较低,仅在重复执行相同查询时才有用。根据您希望缓存的数据量来配置它们的大小。
  • 缓存大小也取决于您希望在每个层级缓存多少数据,最好在较低层级缓存更多数据,因为它们的命中率更高,并且对所有查询都有用。