本文永久链接 – https://tonybai.com/2025/08/20/large-scale-logging-made-easy

当日志规模达到 PB 级别,传统的关系型数据库(如 PostgreSQL 或 MySQL)往往力不从心,不仅性能急剧下降,运维成本也变得难以承受。在 FrOSCon 2025 大会上,VictoriaMetrics 的联合创始人兼CTO、fasthttp作者、资深 Go 工程师Aliaksandr Valialkin 发表了题为“大规模日志处理变得简单”的演讲,深入剖析了专为日志设计的数据库如何通过一系列精巧的工程设计,实现单机处理 PB 级数据的惊人性能。

本文将和大家一起听演讲,并了解其分享的核心技术——包括列式存储、时间分区、日志流索引和布隆过滤器——并看看为什么这些技术能将日志查询速度从理论上的 70 小时超大幅缩短至 10 秒,以及为何传统数据库在这场竞赛中注定落败。

什么是“大规模日志”?一个与时俱进的定义

在探讨解决方案之前,演讲者 Aliaksandr Valialkin 首先抛出了一个引人深思的问题:究竟什么是“大规模日志”? 业界通常用每日的数据量来衡量,是 GB、TB 还是 PB?然而,这个定义是浮动的。Aliaksandr 提出了一个更具工程实践意义的定义,它将问题从抽象的数字拉回到了具体的物理约束上:

当你的日志无法装入单台计算机时,它就达到了“大规模”。

这个定义的巧妙之处在于,它将“规模”与具体的硬件能力和软件效率紧密地联系起来。一台搭载着普通硬盘、运行着 PostgreSQL 的服务器,可能在处理每日 GB 级日志时就会捉襟见肘。然而,一台配备了高速 NVMe 硬盘、拥有数百 CPU 核心和 TB 级内存的“巨兽”,在运行像 VictoriaLogs 这样的专用数据库时,其处理能力可能是前者的数千倍。在这种情况下,即便是每日 PB 级的日志,也可能不属于“大规模”的范畴。

这个定义为我们接下来的讨论奠定了基础:在诉诸昂贵且复杂的分布式集群(水平扩展)之前,我们是否已经通过选择正确的工具,充分压榨了单机(垂直扩展)的潜力?

单机处理 PB 级日志:一场从 70 小时到 10 秒的性能优化之旅

为了具象化地展示专用日志数据库的威力,演讲者构建了一个思想实验:在一台配备了顶级 NVMe 硬盘(理论持续读取速度 4 GB/s)的 Google Cloud 虚拟机上,查询 1 PB 的日志数据。

起点:暴力扫描 (理论耗时: 70 小时)

如果我们将 1 PB 的原始日志直接存储在硬盘上,并进行一次全盘扫描,理论上需要的时间是:

1 PB / 4 GB/s ≈ 1,048,576 GB / 4 GB/s ≈ 262,144 秒 ≈ 72.8 小时

这在任何生产环境中都是完全无法接受的查询延迟。

第一步:高压缩率带来的飞跃 (理论耗时: 4.6 小时)

专用日志数据库的第一个魔法在于其惊人的数据压缩能力。根据 VictoriaLogs 用户的真实反馈,对于典型的结构化或半结构化日志,压缩比通常在8x 到 50x 之间。

我们取一个相对保守的 16x 压缩比。这意味着 1 PB 的原始日志,可以被压缩到仅有 64 TB 的磁盘空间——这恰好是 Google Cloud 单个虚拟机可挂载的最大磁盘容量。

此时,全盘扫描的时间大幅缩短:

64 TB / 4 GB/s = 16,384 秒 ≈ 4.55 小时

这已经是一个巨大的进步,但对于即时的问题排查来说,仍然太慢。

优化的核心基石:列式存储 (Columnar Storage)

传统关系型数据库(如 PostgreSQL, MySQL)采用行式存储 (Row-oriented Storage)。这意味着一张表中,同一行记录的所有字段(列)在物理上是连续存储的。

[Row1: ColA, ColB, ColC] [Row2: ColA, ColB, ColC] ...

这种存储方式在处理事务性(OLTP)负载时非常高效,因为它能一次性读取或更新整条记录。但对于日志分析这种分析性(OLAP)负载,却是灾难性的。当一个查询只需要分析 ColA 字段时,数据库仍然被迫从磁盘上读取包含 ColB 和 ColC 的完整行数据,造成了大量的 I/O 浪费。

专用日志数据库则借鉴了数据仓库的设计,采用列式存储 (Columnar Storage)

将结构化日志按字段(列)进行拆分,将所有日志中同一个字段的值物理上连续存储在一起。

[ColA: Row1, Row2, ...] [ColB: Row1, Row2, ...] [ColC: Row1, Row2, ...]

这种设计的优势是颠覆性的:

  1. I/O 效率:当查询只涉及 ColA 和 ColB 时,数据库只需读取这两列的数据,完全跳过 ColC,I/O 量可以减少几个数量级。
  2. 压缩效率:同一列的数据具有极高的相似性。例如,log_level 列只包含 “info”, “warn”, “error” 等少数几个值;http_status 列只包含 200, 404, 500 等数字。将这些同质化的数据放在一起,其压缩效果远非混合了各种类型数据的行式存储可比。专用数据库还能根据每列的数据特征(如常量、枚举、时间戳、IP 地址等)自动选择最优的专用编码 (Specialized Codex),进一步提升压缩率,有时甚至能达到上千倍。

回到我们的实验,假设查询只涉及所有日志字段中的一小部分,需要读取的数据量从 64 TB 减少到了 4 TB。查询时间随之骤降至:

4 TB / 4 GB/s = 1024 秒 ≈ 17 分钟

仅仅列式存储还不够,为了避免全列扫描,还需要更智能的数据组织方式。

第二步:按时间分区 (理论耗时: 1 分 40 秒)

日志数据天然带有强烈的时间属性。几乎所有的日志查询都会带上时间范围。专用日志数据库利用这一点,将数据按时间(例如,每小时或每天)进行物理分区。每个分区可以是一个独立的目录或文件。

当一个查询带有 time > T1 AND time < T2 的条件时,数据库可以在查询开始前就完全跳过时间范围之外的所有数据分区,无需读取任何磁盘块。

假设我们的服务保留了 30 天的日志,而我们的查询只关心其中 3 天的数据。需要扫描的数据量等比例减少 90%:

4 TB * (3 / 30) = 400 GB

查询时间进一步缩短至:

400 GB / 4 GB/s = 100 秒 ≈ 1 分 40 秒

第三步:按日志流 (Log Stream) 索引 (理论耗时: 10 秒)

另一个重要的日志维度是其来源。演讲者将“日志流”定义为来自单个应用实例的、按时间排序的日志序列。例如,在一个 Kubernetes 集群中,每个 pod 的每个 container 都会产生一个独立的日志流。

通过为每个日志流(通常由 service, hostname, pod_name 等标签组合定义)建立索引,数据库可以在查询时,只扫描那些与查询条件(例如 service=”api-gateway”)匹配的流。

假设我们的系统中有 1000 个日志流,而查询只涉及其中的 100 个。需要扫描的数据量再次减少 90%:

400 GB * (100 / 1000) = 40 GB

查询时间最终缩短至惊人的:

40 GB / 4 GB/s = 10 秒

我们成功地将一个理论上需要 70 小时的查询,通过一系列精巧的工程设计,在单台机器上优化到了 10 秒以内!

第四步:为“大海捞针”准备的布隆过滤器 (Bloom Filters)

对于需要查找唯一或稀有子串(如 trace_id, user_id, ip_address)的“大海捞针”式查询,全量扫描即使优化后也可能很慢。为此,专用数据库引入了布隆过滤器。

布隆过滤器是一种空间效率极高的概率性数据结构,它可以快速地告诉你一个元素“绝对不存在”“可能存在”于一个集合中。它可能会有误报(说“可能存在”但实际不存在),但绝不会漏报。

通过为每个数据块(block)中的所有词元(word tokens)构建一个布隆过滤器,数据库可以在查询时:

  1. 先检查数据块的布隆过滤器。
  2. 如果过滤器显示目标 trace_id 绝对不存在于此块中,则完全跳过对该数据块的读取和解压

这可以将此类查询的性能再次提升高达 100 倍,实现亚秒级的响应。一个 64 TB 的压缩日志,其布隆过滤器索引的大小可能在 640 GB 到 6.4 TB 之间,这是一个典型的空间换时间策略。

为何传统数据库在海量日志场景中注定失败?

演讲清晰地指出了 PostgreSQL 或 MySQL 在处理大规模日志时的几个根本性缺陷,这些缺陷导致它们无法与专用数据库竞争。

  1. 行式存储的原罪:如前所述,这导致了严重的 I/O 浪费和低下的压缩率。
  2. 随机 I/O 的噩梦:由于缺乏自动的、基于日志特性的物理分区,查询一个时间范围内的特定日志流,在行式数据库中会退化成对磁盘上数百万个不同位置的随机读取。考虑到机械硬盘和 SSD 的随机 I/O 性能远低于顺序读取,这将导致灾难性的性能表现。
  3. B-Tree 索引的“水土不服”
    • 体积庞大:B-Tree 索引的大小通常与数据本身的大小在同一个数量级。对于 PB 级数据,索引本身就需要 TB 级的内存才能高效工作,这在成本上是不可接受的。
    • 不适合分析型扫描:B-Tree 擅长快速定位单条或少数几条记录,但对于需要扫描数百万行的分析型日志查询,其效率远低于专用日志数据库的稀疏索引(例如,仅索引每个数据块的起始/结束时间戳和流 ID)。
  4. 致命的写放大 (Write Amplification):传统数据库为了维护事务性和索引,会频繁地在磁盘上进行小块数据的原地更新(in-place updates)。这在现代 SSD 和 NVMe 硬盘上会触发“读取-修改-写入”的内部操作,一个 4KB 的逻辑写入可能导致 512KB 的物理写入,极其低效且会严重损耗硬盘寿命。而专用日志数据库通常采用仅追加(append-only)的写入模式,数据块一旦写入便不可变,这与现代存储硬件的工作原理完美契合。

日志系统技术选型的建议

在深入探讨了 VictoriaLogs 的设计哲学后,Aliaksandr Valialkin 还在演讲的最后分享了他对当前主流开源日志数据库的看法,并回答了现场观众的提问。这部分内容为我们提供了宝贵的技术选型参考。

主流开源日志数据库横向对比

当决定从传统数据库迁移时,开发者通常面临以下几个选择:

  1. Elasticsearch

    • 优点:功能强大,生态成熟,是全文搜索领域的王者。
    • 缺点:资源消耗巨大,尤其是内存。Aliaksandr 指出,要在 Elasticsearch 中存储 PB 级的日志,“准备好为基础设施花费数千万美元”。其横向扩展的运维复杂度也相对较高。
  2. Grafana Loki

    • 优点:设计理念新颖,只索引元数据(标签),不索引日志内容,旨在降低存储成本。与 Grafana 无缝集成。
    • 缺点:运维和配置相对复杂。更重要的是,它在处理高基数(high cardinality)日志字段(如 trace_id, user_id)时存在性能问题,这正是许多现代可观测性场景的核心需求。
  3. ClickHouse

    • 优点:一个极其快速的开源列式分析数据库,性能卓越。
    • 缺点:灵活性是一把双刃剑。要用好 ClickHouse 存储日志,你需要成为半个专家,深入理解如何正确地设计表结构、选择分区键、设置排序键等,配置门槛较高。
  4. VictoriaLogs (演讲者推荐):

    • 优点:吸收了上述方案的优点,同时致力于简化运维。它内置了所有前面提到的优化技术,并且默认开启,无需复杂配置。其架构设计使其能够轻松处理高基数数据,并实现了从树莓派到大型服务器的平滑扩展,而无需调整配置。

现场 Q&A 精华:深入 VictoriaLogs

现场观众的提问也帮助我们进一步了解了 VictoriaLogs 的一些关键特性和未来规划:

  • Q: 为什么选择Go?

    • A: 在过去十多年里,演讲者主要使用 Go 语言编写代码。Go 是他的首选编程语言。他喜欢 Go,因为Go是一门非常简洁且富有生产力的语言。用 Go 编写高性能的代码很容易,而且与其他之前使用的编程语言相比,Go 的代码通常更容易阅读和维护。演讲者喜欢编写有用的开源软件,并且喜欢让这些软件能够开箱即用,不需要查阅大量文档,也不需要进行复杂的配置。这是许多开源项目所欠缺的一个特性,但演讲者认为它对最终用户至关重要。他喜欢创建为速度和低资源消耗而优化的服务器。这也是他创建 VictoriaMetrics 的原因,它是一个用于指标(也称为时间序列数据)的开源数据库,非常高效和快速。最近,他又创建了 VictoriaLogs,这是另一个专门用于存储日志的数据库。
  • Q: VictoriaLogs 是否提供 UI?

    • A: 是的。它内置了一个用于快速日志调查的 Web UI,并且提供了功能完备的 Grafana 插件,允许用户构建任意复杂的仪表盘。其查询语言是自研的 LogSQL,被设计得比 Loki 的 LogQL 等更强大,支持在单次查询中进行复杂的数据转换和多维度统计计算。
  • Q: 是否支持日志不可篡改(immutability)?

    • A: VictoriaLogs 不支持对已存日志的修改,只支持未来的删除操作(且该功能可被禁用),这在一定程度上保证了数据的不可篡改性。但它目前没有提供基于密码学的签名验证功能。
  • Q: 多租户支持如何?

    • A: VictoriaLogs 原生支持多租户,并且可以轻松处理数万级别的租户,这与 Loki 等因架构设计而在租户数量上受限的系统形成了对比。
  • Q: 对于更大的存储需求(如单个 EC2 实例挂载 450TB 磁盘),你会如何选择?

    • A: 演讲者建议,虽然技术上可行,但他会选择水平扩展。他认为单节点存储的数据量最好有一个平衡点(例如 16TB 的压缩数据),因为过大的单节点会给备份和恢复带来巨大的运维挑战(可能需要数小时)。
  • Q: 未来的路线图是什么?

    • A: 近期最重要的主线功能是支持将历史日志分层存储到对象存储(如 S3)中。系统将能够透明地将冷数据归档到更廉价的存储,并在查询时无缝地拉取,进一步降低成本。至于是否会支持完全无本地磁盘、直接读写对象存储的模式,团队表示会在此功能实现后再做评估,因为需要解决对象存储带来的高延迟问题。

小结:为你的工作选择正确的工具

Aliaksandr Valialkin 的分享为所有处理大规模数据的 Go 开发者提供了清晰、深刻的工程指引:不要试图用一把锤子(通用关系型数据库)去拧所有的螺丝。理解问题的本质,并选择专为该问题设计的工具。

对于日志处理,这意味着:

  • 拥抱专用数据库:当你每天的日志量超过 TB 级别,或者发现现有的日志系统运维成本高昂、查询缓慢时,从 PostgreSQL/MySQL 迁移到像 VictoriaLogs、ClickHouse 或 Loki 这样的专用系统,将带来数量级的成本节约和性能提升。
  • 优先垂直扩展:在投入到复杂且昂贵的水平扩展(分布式集群)之前,先通过使用正确的单机软件,充分压榨现代硬件的潜力。这不仅能节省成本,还能极大地降低运维的复杂性。

正如演讲者所倡导的“小数据”运动理念:许多所谓的“大数据”问题,在正确的工具和架构面前,完全可以在单台计算机上被更简单、更高效地解决。 对于追求性能、效率和简洁性的 Go 开发者而言,这不仅是一次技术分享,更是一堂关于工程哲学的深刻课程。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2025, bigwhite. 版权所有.

No related posts.