惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
L
LangChain Blog
Jina AI
Jina AI
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
T
Threat Research - Cisco Blogs
量子位
G
GRAHAM CLULEY
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
C
Cisco Blogs
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
V
V2EX
博客园 - 三生石上(FineUI控件)
T
Tor Project blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
Vulnerabilities – Threatpost
PCI Perspectives
PCI Perspectives
博客园_首页
L
LINUX DO - 最新话题
IT之家
IT之家
有赞技术团队
有赞技术团队
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Last Week in AI
Last Week in AI
The Cloudflare Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题
AWS News Blog
AWS News Blog
S
Security Affairs
T
Tailwind CSS Blog

数据库

SQL 查不到数据,数据却明明存在——MySQL 降序主键与 index_merge intersect 的一次诡异排查 - V2EX 各位现在还手写 sql 吗? [翻译] 为什么我要用 C# 构建数据库引擎 向量数据库的正确用法是什么? 明天就要软考了,我发现了数据库三范式之第一范式好像过时了 WM 到 IOS 用了二十年的数据表格软件 Listpro 准备退休了 异机备份方案 Oracle 裁员裁到大动脉了?官方软件 Oacle SQL Developer 居然出现恶性 BUG 了。 ubuntu 中 DataGrip 从数据表中复制的中文成了乱码 你们在用什么数据库管理软件? 大佬们,生产环境的 Mysql 和 Redis 都是部署在哪里的呢 不知道全国有多少数据系统被 Oracle 数据库的 VARCHAR2(X) 的默认单位给坑了 海量数据访问 刚问 AI 解了一个去年看书的一个疑惑:数据存储选择 lklv, llkv 有没有好用 GUI client 可以方便管理多个 Postgre 数据库 你们现在设计系统数据库的时候还在数据库层面搞外键约束吗? - V2EX 有没有类似阿里云 mysql 数据库这样的数据恢复工具? 刚接触后端不久,帮忙推荐一个免费的数据库可视化工具 2026 年了,公司搞了一大堆没用的 Tracing 数据,存 ES 都快卡炸了 请教 oracle, mysql 不停机同步到达梦数据库实际工作中有什么方案吗 datagrip 切换查询界面的时候,结果集不随着跳转 我发现 TiDB Cloud 比较牛逼啊 请问有做过时序数据库的大佬么? - V2EX 程序员玩具多系列:有什么 navicat/datagrip 替代品推荐 - V2EX grafana 中 dashboard 里的数据显示为空,实际通过 Queries 查询是有数据的? 数据库性能测试的要点有哪些? 标签系统内容最大标签数放开到很大(比如三万)对性能影响有多大? 2025 年了, select *是否仍然禁止使用? - V2EX AI 给我写了一个 SQL DB 模糊测试工具,求 star [讨论] 大家的项目中会使用外键约束吗? - V2EX 帮忙看看是不是太垃圾,断断续续搞了几个月数据库监控工具,一个用户都没有 - V2EX 有能把 navicat 连接转为 datagrip 的工具吗 内存数据库 h2 与 mysql 兼容性太差了,能把 PostgreSQL 整成单测环境启动吗 [讨论] 免费的数据库工具中大家喜欢哪款 怎么评价 order by rand() limit 1 这条 sql 大佬们,请教一个数据库设计的问题 你们用过数据库联合索引优化排序功能吗 靓仔们, DataGrip 非商用免费了 - V2EX PgSQL 和 Sqlserver 哪个好?哪个资源占用小?哪个性能好速度快? 有没有公开的、靠谱的美妆品牌&产品库数据源 - V2EX 有个关于 mongodb 的问题,求大神来解答一下 sql server where 查询时 字段是 nvarchar 类型的保存的数字 但是可以用 int 类型的数字去匹配,这是什么原理? 你们平时手撸 SQL 多吗?还是 ORM 优先 postgres vs mysql - Why Uber Engineering Switched from Postgres to MySQL 信创, Mongo、redis 可以用到信创项目里吗 团队日志要存几年,怎么找一个便宜又能查的地方? - V2EX 数据表和字段取名太难了,有没有工具可以根据描述生成表名? 不限语言,你觉得最好用的框架和 ORM 是什么? 2025 年了,贵司的可观测技术栈用的是什么?监控怎么存,日志怎么存,追踪怎么存?想知道新的趋势都有什么? 小白求教:图数据库和关系型数据最大的区别是什么 [新手求建议] 结构化数据,波形图为主,用于识别模型训练有没有推荐的数据库? SQL 数据库咨询 DMS 客户端已经很久没有更新了,后面也不会维护 新系统做大数据解析是否需要上 hadoop sql 占位符替换 TUI 工具 pebble kv 数据库,在查询过程中是怎么处理的。(针对存放在不同地方的的数据库) 阿里云的各种云数据库同步功能就是个大坑。。 数据库操作,如何在多个不同的库里筛选数据 关于数据库索引的一个问题:有复合索引建立于列 a,b,c。那么 where a=1 and b like b% and c=3 命中此索引吗? where a=1 and b > 2 and c=3 命中此索引吗? 项目 MYSQL 数据库,要改造成信创国产数据库,有没有改造最小的方案; 有没有异构数据库对比的软件? TIDB 在 LEFT JOIN 时,为什么不使用被关联表的索引,而是全表扫描?
VictoriaMetrics 开发者笔记: Traces 的查询性能与优化倾向
RedisMasterNode · 2025-08-04 · via 数据库

数月之前我们公布了关于用 VictoriaLogs 充当 Traces 数据的存储的调研,从 PoC 的角度看,这是一次很不错的尝试,但是我们意识到,这离真正可用的产品形态还差很远。

因此在这段时间内,这个 PoC 项目发生了如下的变化:

  1. VictoriaLogs 从 VictoriaMetrics 项目分离。而我们的 Traces 解决方案,作为 VictoriaLogs 的一个下游分支,也拥有了属于它的新名字和仓库:VictoriaTraces
  2. 完善查询场景的性能测试和优化,发布了第一个版本 v0.1.0

同时,我们还收到了很多用户关于查询性能的疑问,因为在上一篇博客中,查询性能只被简单地提及过 —— 是的,那是一轮不够严谨的测试。我们通过简单观察不同 Traces 后端的响应速度,得出 VictoriaLogs 的查询性能不逊色于竞争对手,这当然没有说服力

所以,这篇博客中,让我们一起来探索一下 Traces 在不同的后端中是如何查询的。

回想一下,开发者们是如何使用 Traces 的:

  1. 有人向你报告了一个 Bug 以及对应的 TraceID ,然后你打开 Traces 平台,通过 TraceID 查询 Trace
  2. 有人向你报告系统变慢了,但不知道原因。你打开 Traces 平台,搜索一段时间内所有耗时超过 3000ms ,或者包含错误的 Traces

这是 Traces 中两个最常见的查询场景,那么数据 Schema 的设计就应该围绕着它们进行。

想要加速 TraceID 的查询,那数据就应该按照 TraceID 进行排列,而其余数据可以分多列存储,也可以编码成 Binary 或者 JSON 存入一列。另一方面,想要在时间范围内按照 Span 的 Attributes (如耗时、状态)进行查询,那这些数据应该按时间排序,并且相关 Attributes 就应该作为单独的列,提供检索的能力。

不同 Traces 后端的优化倾向

如果我们观察 VictoriaTraces 和其他 Traces 后端的 Schema ,可以看出来它们的优化倾向各不相同。

Jaeger

ClickHouse 是多个 Traces 后端都选用的存储方案。在 Jaeger 的设计中,ClickHouse 有两个关键的表:

<details>

CREATE TABLE spans_table
(
    `timestamp` DateTime CODEC(Delta, ZSTD(1)),
    `traceID` String CODEC(ZSTD(1)),
    `model` String CODEC(ZSTD(3))
)
ENGINE = MergeTree
PARTITION BY toDate(timestamp)
ORDER BY traceID
SETTINGS index_granularity = 1024;
CREATE TABLE spans_index_table
(
    `timestamp` DateTime CODEC(Delta, ZSTD(1)),
    `traceID` String CODEC(ZSTD(1)),
    `service` LowCardinality(String) CODEC(ZSTD(1)),
    `operation` LowCardinality(String) CODEC(ZSTD(1)),
    `durationUs` UInt64 CODEC(ZSTD(1)),
    `tags` Nested(key LowCardinality(String), value String) CODEC(ZSTD(1)),
    INDEX idx_tag_keys tags.key TYPE bloom_filter(0.01) GRANULARITY 64,
    INDEX idx_duration durationUs TYPE minmax GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toDate(timestamp)
ORDER BY (service, -toUnixTimestamp(timestamp))
SETTINGS index_granularity = 1024;

</details>

很显然,这是针对 TraceID 查询优化的,在 spans_table 中,数据按日分区,按 TraceID 排序,因此通过 TraceID 可以快速取出数个连续的 Spans 。而通过 TraceID 外的条件查询数据时,先在 spans_index_table 中找到 TraceID ,再回到 spans_table 中取出完整数据。

ClickStack ( ClickHouse )

作为 ClickHouse 的亲儿子,ClickStack 的 Schema 完全按照 OpenTelemetry 定义,让每个属性都拥有对应的字段:

<details>

CREATE TABLE otel_traces
(
    `Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
    `TraceId` String CODEC(ZSTD(1)),
    `SpanId` String CODEC(ZSTD(1)),
    `ParentSpanId` String CODEC(ZSTD(1)),
    `TraceState` String CODEC(ZSTD(1)),
    `SpanName` LowCardinality(String) CODEC(ZSTD(1)),
    `SpanKind` LowCardinality(String) CODEC(ZSTD(1)),
    `ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
    `ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
    `ScopeName` String CODEC(ZSTD(1)),
    `ScopeVersion` String CODEC(ZSTD(1)),
    `SpanAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
    `Duration` Int64 CODEC(ZSTD(1)),
    `StatusCode` LowCardinality(String) CODEC(ZSTD(1)),
    `StatusMessage` String CODEC(ZSTD(1)),
    `Events.Timestamp` Array(DateTime64(9)) CODEC(ZSTD(1)),
    `Events.Name` Array(LowCardinality(String)) CODEC(ZSTD(1)),
    `Events.Attributes` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
    `Links.TraceId` Array(String) CODEC(ZSTD(1)),
    `Links.SpanId` Array(String) CODEC(ZSTD(1)),
    `Links.TraceState` Array(String) CODEC(ZSTD(1)),
    `Links.Attributes` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
    INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
    INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_span_attr_key mapKeys(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_span_attr_value mapValues(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_duration Duration TYPE minmax GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId);

</details>

如果需要按照 TraceID 查询怎么办呢?是不是要在所有 Partition 中都找一遍?显然太低效了。因此,ClickStack 还会将每个 Span 的时间记录到 otel_traces_trace_id_ts 表,并且创建物化视图,这样,每个 TraceID 的起始和结束时间就很容易确定了,有效加速了以 TraceID 在 otel_traces 表的查询速度。

<details>

CREATE TABLE otel_traces_trace_id_ts
(
    `TraceId` String CODEC(ZSTD(1)),
    `Start` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
    `End` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
    INDEX idx_trace_id TraceId TYPE bloom_filter(0.01) GRANULARITY 1
)
ENGINE = MergeTree
ORDER BY (TraceId, toUnixTimestamp(Start));

CREATE MATERIALIZED VIEW otel_traces_trace_id_ts_mv TO otel_traces_trace_id_ts
(
    `TraceId` String,
    `Start` DateTime64(9),
    `End` DateTime64(9)
)
AS SELECT
    TraceId,
    min(Timestamp) AS Start,
    max(Timestamp) AS End
FROM otel_traces
WHERE TraceId != ''
GROUP BY TraceId;

</details>

VictoriaTraces

VictoriaTraces 目前的设计近似于 ClickStack ,在上一篇博客中提到过,尽可能将所有 Attributes 平铺为 Fields ,而 Fields 正接近于 Column-oriented 数据库中“列”的概念。

同样,单纯这样的设计并不能应对 TraceID 查询的场景,因此,我们又增加了一个单独的 Index Stream ,VictoriaTraces 在遇见每个新的 TraceID 的时,都会在这个 Stream 中增加一条记录 (Timestamp, TraceID)。这个 Stream 非常小,行数为 Trace 的总量,因此在这个 Stream 中按照 TraceID 查询会很快。在找到 TraceID 对应的 Timestamp 后,以此为中心,在各个 Stream 中查询 ±45 秒内的数据,获取 TraceID 对应的所有 Spans 。

这个设计只是作为加速 TraceID 查询的概念验证,它有很多显而易见的缺点:

  1. Index Stream 数据按照时间排序,因此按 TraceID 的查询是遍历而二分查找,效率不高。
  2. TraceID 的起始结束时间是不确定的,查询 90 秒的数据既可能浪费(如 Trace 耗时仅为 1 秒),也可能不足(如 Trace 跨越数分钟)。

不过,聪明的读者一定也知道所有的设计都有其长处和短板,问题在于是否值得:

  • 用更多的空间换更快的时间。
  • 用更昂贵的写入换更快的查询。

我们一定会在后续版本保持探索,调整这些设计,将它变得更加适合不同的使用场景。不过在那之前,不如先回到今天的主题——它们到底查询性能如何?

查询性能对比

改良 Traces 数据生成

为了生成大量更贴合生产环境的测试数据,我们一开始打算部署多个 OpenTelemetry Demo。该 Demo 是基于 14 个微服务的分布式系统,覆盖了不同的编程语言、不同的插桩方式,产生的数据比过往使用的 Jaeger tracegen 更具有代表性。

但是在运行一段时间后,我们发现 OpenTelemetry Demo 需要消耗较多的资源,并且产生的压力有限。因此,我们又基于流量录制回放的思路编写了 vtgen,它可以:

  1. 反复发送预先录制好的 OpenTelemetry Demo 的 Trace 请求到多个 OTLP HTTP Endpoints ,这些 Trace 请求中 TraceID 和部分字段会被按需修改。
  2. 记录 HTTP 请求耗时指标。

vtgen 既可以用于 Traces 后端的写入性能 Benchmark ,也可以为不同 Traces 后端写入完全一致的数据,并随机记录一定量的 TraceID ,用于查询性能 Benchmark 。

Benchmark 设计

我们在对 VictoriaTraces 的 Benchmark 中仍然选用了 Grafana Tempo 及 Jaeger & ClickHouse 作为对比,写入相同的的数据,其中数据写入过程的监控监控记录如下图,读者也可以在 Grafana Dashboard 快照中查阅。

我们在第一节中介绍过,最常见的 Traces 查询场景包括:

  1. 根据 TraceID 查询。
  2. 根据属性在特定时间段搜索出多个 Traces 。

因此对应地:

  1. 通过 vtgen 在 Ingestion 过程中记录下 63000 个 TraceID ,逐一向 3 个 Traces 后端进行请求。
  2. 手动构造 15 组 Traces 属性查询的参数模板,以及 50 个时长为 10-60 分钟的时间段,逐一向 VictoriaTraces 和 Jaeger 进行共计 750 次请求。

{{<admonition type=note title="为什么属性搜索对比中没有 Tempo ?">}}

VictoriaTraces 、ClickHouse 均可以支持 Jaeger 的 Search API ,而 Tempo 同样提供 Search API ,但是两个 Search APIs 的返回数据格式并不一致:

  • Jaeger 的 Search API 需要提供完整的 Traces 数据,包含所有 Spans 。换句话说,Jaeger 的 Search API 就是 List 版本的 Trace API 。
  • Tempo 的 Search API 只需返回 Traces 的部分数据,因而无需额外查找每个 Trace 的其余 Spans 。

因此,它们无法直接对比查询性能。

不过,Tempo 的 Search API 设计实际上更简洁高效,所以,我们会在 VictoriaTraces 实现 Tempo API 后将其进行补充对比。

{{< /admonition >}}

结果

通过上面的图表,我们可以看到 VictoriaTraces 相比一些主流 Traces 后端的性能如何。

与 Jaeger & ClickHouse 的组合相比,如第二节中所分析,因为它们的查询优化方向不同,所以在两种场景中的表现互有胜负。ClickHouse 使用了 2 倍于 VictoriaTraces 的存储空间( 162 GiB vs. 79 GiB )来换取根据 TraceID 查询的速度,舍弃了在 Traces Search 场景的性能。

VictoriaTraces 的未来

通过这些测试,我们已经知道 VictoriaTraces 与不同竞品的差异 —— 既有设计上的原因,也有优化上的不足。我们当然需要继续迭代 VictoriaTraces ,希望它能够更早抵达稳定版本。

所以,在性能上:

  • VictoriaTraces 的底层数据结构将与 VictoriaLogs 进一步分叉,让 Traces 场景得到更多的关爱。引入更合适的 IndexDB 和 Cache 来优化数据写入和查询。
  • Profiling 结果显示,VictoriaTraces 还有很多糟糕的代码,它还有很大的进步空间。

同时,在功能上,我们希望:

  • 提供 Tempo HTTP APIs ,这允许用户更灵活地查询 Traces 数据。
  • 完善 Kubernetes 支持,提供 Operator 和 Helm Chart 。
  • 完善 Cluster 版本的设计。

期待能在博客评论区或 VictoriaTraces 的 Issues 中收到你的反馈,也期待与你在下一期开发者笔记中再会!