Elasticsearch LogsDB 发展历程:如何在不降低吞吐量的情况下,将索引大小减少多达 75%
Elasticsearch 最初是作为搜索引擎构建的。这种传承在日志存储方面是有代价的每个事件都会扩散到多个磁盘结构中每个结构都针对检索而非压缩进行了优化。LogsDB 改变了这一切。在我们的每晚基准测试中企业模式Enterprise mode下同样的数据在没有 LogsDB 的情况下需要 161.9 GB而使用 LogsDB 仅产生 37.5 GB 的索引——仅通过一个设置就减少了 77%。写入开销作为底层库的 Lucene 会为每个索引文档保留多个结构倒排索引 (Inverted index)将词条映射到文档。这是实现快速文本搜索的关键。_source存储原始 JSON 块在获取文档时返回。Doc values以列式存储字段值用于排序和聚合。Points / BKD 树对数值和日期字段进行索引用于范围查询。倒排索引物有所值它让你能在毫秒内通过关键词搜索数十亿条日志而且没有比这更廉价的方式来实现这种能力。_source的存在是为了让你能准确拿回索引时的内容搜索结果和GET请求会直接返回这个块。问题在于尽管相同的字段值已经可以通过 doc values 和其他结构获得它仍然存储了完整的事件。以一个包含host.name、timestamp、http.response.status_code和duration_ms等字段的日志事件为例。整个事件作为 JSON 序列化存储在_source中。同时相同的字段值还会被写入 doc values 列、编入倒排索引并存储在用于范围查询的 BKD 树中。相同的数据多个结构每个都有自己的磁盘占用。对于需要跨所有维度快速检索的搜索引擎来说这种开销是合理的权衡。但对于日志你很少需要原始 JSON也几乎从不做相关性排序搜索因此其中大部分都是纯粹的浪费。一次写入四个磁盘结构_source原始 JSON 块、倒排索引、doc values 列以及用于数值范围查询的 BKD/Points 树。相同的字段值最终出现在多个地方。为什么列式存储对压缩至关重要Doc values 是 LogsDB 所有功能的关键。与将整个文档存储为块的_source不同doc values 将 Lucene 段segment内所有文档的每个字段存储为独立的列。想象一个拥有一百万个日志事件的段。_source的表现形式是一百万个 JSON 块每个事件一个每个块都包含杂乱交织的所有字段。而 doc values 的表现形式是一组列一列包含一百万个时间戳一列包含一百万个主机名一列包含一百万个状态码依此类推。行式存储的_source将每个文档的所有字段保存在一个块中——doc0 到 doc5 每个都带着混合在一起的host.name、timestamp、status、duration_ms等。列式存储的 doc values 重新组织了相同的数据使所有host.name值位于一列所有时间戳位于另一列所有状态码位于又一列。随后压缩编解码器codecs可以独立地在每个连续的列上运行。这种列式布局使得逐列压缩成为可能。当所有的http.response.status_code值都位于一个连续的列中时Lucene 可以应用利用序列模式的编解码器。增量编码 (Delta encoding)存储相邻值之间的差异而非完整值。最大公约数编码 (GCD encoding)寻找公共因子并进行除法压缩。游程编码 (Run-length encoding, RLE)则折叠重复项。Lucene 会为每个段挑选编解码器并在段合并时重新评估。来自同一主机的四个已排序timestamps的四个阶段压缩。原始 (RAW)四个 32 位整数共 128 位。增量 (DELTA)存储差异而非完整值——保留基准值增量 100、200、300 占用 59 位。最大公约数 (GCD)除以公共因子 100剩下 1、2、3占用 39 位。位包装 (BIT-PACK)将这三个小整数打包进连续的位存储释放 9 位。但这里有一个关键这些编解码器只有在相邻文档具有相关值时才有效。以timestamp列为例。如果日志是从几十个主机随机交错到达的那么列中的时间戳会跳跃不定。相邻值之间的增量可能是 3 秒然后是 -47 秒接着是 120 秒。增量编码对此无能为力。现在考虑一下如果在写入段之前先按host.name和timestamp进行排序。来自 host-A 的所有日志都会连续存放接着是来自 host-B 的所有日志依此类推。在每个主机的连续数据中时间戳是单调递增的且增量是可预测的。来自同一主机的四个时间戳可能看起来像 1706745600, 100s, 200s, 300s。增量编码将它们缩小为一个基准值加上三个小整数。GCD 编码发现 100, 200, 300 都能被 100 整除于是存储 1, 2, 3。位包装随后将这三个值放入极少数的位中。同样的模式也适用于host.name、service.name或http.response.status_code等字段在一个排序后的序列中长段的相同值在游程编码下几乎会折叠为零。五个主机——api-01, api-02, db-01, web-01, web-02——按到达顺序随机分散左。按host.name排序后将它们分为五个连续的 8 条目块中。游程编码将每个块折叠为单个值计数对——存储 5 对而非 40 个释放了剩余空间右。Elasticsearch 以前从未默认进行排序。文档按到达顺序存放并使用 DEFLATE 压缩。我们浪费了很多优化空间。演进历程2012–2026LogsDB 中的各项技术并非全都是为日志设计的。它们是在十二年间为了解决不同问题而构建的LogsDB 正是将这些技术堆叠在一起的结果。基础阶段 (2012–2017)。Lucene 4.0 在 2012 年引入了 doc values。到 2016 年的 Elasticsearch 5.0它们已成为所有 keyword 和数值字段的默认配置。Lucene 7.0 增加了稀疏 doc values这样仅在部分文档中出现的字段就不会在段中的每个文档上浪费空间。这解决了一个严重的强制合并膨胀问题在稀疏字段上最高可达 10 倍并建立了其他所有功能都依赖的存储模型。稠密编码Dense为每个文档保留一个 8 字节槽位无论是否存在值。稀疏编码Sparse仅存储有值的文档每个 12 字节值 文档 ID。对于填充率为 12% 的error_code稀疏编码小 81%24 B vs 128 B。对于填充率为 88% 的request_path稀疏编码反而更大。Lucene 会逐字段选择在填充率低于约 67% 时稀疏编码胜出。渐进式提升 (2020–2021)。两项较小的改动针对的是可观测性工作负载。基于字典的存储字段压缩对重复的字符串元数据进行了去重带来了约 10% 的提升。match_only_text字段类型从倒排索引中去掉了词频term frequencies和位置信息positions。词频是 BM25 用来根据相关性对文档评分的——即一个词条相对于整个语料库在文档中出现的频率。对于日志搜索这个信号毫无意义你不在乎 “timeout” 在一行日志中出现了两次还是七次你只想找到它。位置信息类似存储它是为了让 Elasticsearch 进行精确短语匹配但位置数据非常昂贵而且对日志的短语查询足够少见这种权衡是值得的。当你在match_only_text字段上运行短语查询时它仍然有效——只是会回退到一条较慢的路径通过重新评分候选文档而非直接使用存储的位置。text存储每个词条及其频率和出现的每个位置。match_only_text仅保留文档 ID——足以找到文档仅此而已。在此示例中timeout出现了两次位置 1 和 4这些数据会被丢弃。去掉频率和位置信息可以将文本字段的倒排索引减少约 40%。但在 2021 年整体索引的影响仅为约 10%对于 40% 的字段级缩减来说这听起来回报不高。原因是当时的存储分布_source以原始 JSON 块的形式完整存储doc values 未压缩且未排序也没有使用 ZSTD。message字段的倒排索引只是一个更大、压缩极差的整体中的一小部分。随着接下来的五年工作解决了其他结构的问题同样的 40% 字段级节省变成了更小总量中的重要组成部分。这两项改变本身都不是决定性的但它们确立了针对日志特定的存储优化是值得追求的。TSDB 转折点 (2023 年 4 月)。故事真正开始的地方。我们在 Elasticsearch 8.7 中为时序指标metrics发布了合成_source(synthetic_source)和索引排序。合成_source改变了读写约定。在写入时我们完全跳过存储原始 JSON 块。在读取时当查询需要返回原始文档我们通过从 doc values 和存储字段中读取每个字段的值并将它们重新组装成 JSON。其结果在功能上等同于原始_source仅在字段顺序等方面有细微差别但我们从未存储过那个块。索引排序在写入磁盘前按维度字段和时间戳对文档进行分组。合成_source和索引排序结合将指标存储减少了高达 70%。这个结果告诉我们一件重要的事情同样的架构也可以用于日志。在没有 LogsDB 的情况下Elasticsearch 将每个日志事件写入两次一次作为磁盘上的原始_source块一次写入 doc values 列。LogsDB 完全跳过该块。在读取时GET index/_doc/1请求会收集 doc values 字段值并即时组装文档。TSDB 编解码器 (2024 年)。在 8.13 和 8.14 中我们构建了一个自定义 doc values 编解码器针对排序后的连续值优化了游程编码以及 PFOR-delta 编码和针对多值维度的循环序号编码。数据非常惊人在一次基准测试中kubernetes.pod.name的 doc values 从 110 MB 降到了 7.25 MB。我们将覆盖范围扩大到了所有数值和关键字类型包括ip、scaled_float和unsigned_long。LogsDB 技术预览版 (2024 年 8 月)。在 8.15 中我们将所有功能整合到了index.mode: logsdb中主机优先排序、合成_source、ZSTD 压缩以及 TSDB 数值编解码器。一个决定比预想的更重要排序顺序。先按host.name排序再按timestamp排序可以减少高达约 40% 的存储。先按时间戳排序则只能减少 ≤10%。主机优先排序使共享字段值的文档聚集在一起这正是数值编解码器所需要的。LogsDB 要求使用自动生成的_id用户提供的 ID 会禁用合成_source和路由优化。ZSTD 与正式发布 (2024 年 11 月–12 月)。在 8.16 中我们将best_compression永久从 DEFLATE 切换到了 ZSTD3 级块大小最高 2,048 个文档或 240 kB在 JDK 21 上通过 Panama FFI 使用原生绑定。ZSTD 同时带来了约 12% 的存储空间减少和约 14% 的索引吞吐量提升这在性能调优中极少发生。LogsDB 在 8.17 中正式发布 (GA)。在 GA 时我们宣称存储减少最高可达 65%。路由与恢复 (2025 年 4 月)。在 8.18 中route_on_sort_fields开始根据排序字段值而非_id将文档路由到分片。如果没有这项优化Elasticsearch 会哈希_id来挑选分片因此来自同一主机的日志会分散在所有分片上。通过排序字段路由具有相似host.name值的日志会落在同一个分片上。这在分片级别而不只是段级别聚集了相似的文档在增加 1-4% 的摄取损耗下额外带来了约 20% 的存储缩减。包含六个主机、分布在三个分片上的数据流.ds-logs-nginx-default-00001。标准模式按_id哈希所有主机的颜色随机散布。路由模式route_on_sort_fields同一主机的日志落在同一分片但在分片内仍按到达顺序排列。路由排序模式主机优先排序每个分片包含单一主机的连续块——这种组合让数值编解码器和 RLE 发挥最大潜力。我们还将副本恢复切换为合成_source重构消除了重复的_recovery_source块。在 9.0 中logs-*-*索引默认启用 LogsDB。2024 年 12 月的每晚合成_source基准测试。当副本恢复从复制原始_recovery_source块切换为从 doc values 重构文档的那天写入的索引大小下降了 39%——从约 279 GB 降至约 171 GB。合并与恢复重构9.1 (2025 年 7 月)。我们完全消除了恢复源。副本恢复使用批量合成重构将写入 I/O 减少了约 50%并将中值索引吞吐量比 8.17 基准提升了约 19%。我们将多达四次独立的 doc values 合并过程替换为单次将后台合并 CPU 占用了减少了高达 40%。此外我们将_seq_no的 BKD 树替换为 Lucene doc value skippers使_seq_no存储减半。pattern_text 与失败存储9.2–9.3 (2025 年 10 月–2026 年 2 月)。在 9.2 中我们发布了pattern_text技术预览版这是一种新的字段类型可将日志消息分解为静态模板和动态变量部分。一行日志如Session opened for user alice from 10.0.1.42 via TLS会被拆分为模板Session opened for user {} from {} via TLS仅存储一次作为模板 ID和变量alice、10.0.1.42按文档存储。对于具有高模板重复率的日志这可以将 message 字段的存储减少高达 50%。配套的template_id子字段允许你按模板排序而 LogsDB 设置index.logsdb.default_sort_on_message_template会自动启用此功能。pattern_text在 9.3 中正式发布。TEXT 将每条日志消息作为每个文档的完整字符串存储——八个几乎相同的块副本。PATTERN_TEXT 对其进行分解共享模板存储一次ID 为 T0只有变量列user,ip按文档存储。pattern_text确实会带来索引 CPU 成本在写入时将每条消息分解为模板和变量比直接存储原始字符串需要更多工作。这种权衡是否合理取决于你的数据集和优先级。如果你的日志消息遵循高度重复的模式结构化应用日志、Kubernetes 事件、访问日志存储收益将非常巨大且 CPU 开销是有限的。如果你的消息是自由格式或重复率低压缩增益会缩小而 CPU 成本大致保持不变。对于保留数月或数年的数据累积的存储减少通常是值得的。对于存储不是瓶颈的高基数、快速变化的消息可能并不划算。9.3 还带来了二进制 doc values 的压缩使wildcard字段类型显著提高了存储效率。在内部wildcard 字段在二进制 doc values 列中存储三元组trigrams的倒排索引该列现在使用 Zstandard 压缩而非原始存储。在一次基准测试中URL 字段从 2.92 GB 降至 1.12 GB压缩率超过 60%。如果你大量使用wildcard字段这种增益是自动获得的无需更改映射。同样在 9.3 中针对timestamp和host.name的跳表skip lists成为了 LogsDB 的可选配置。跳表允许 Elasticsearch 在 doc values 列中向前跳过而无需读取每个条目从而加快大型段上的时间范围查询。其他索引模式默认禁用跳表在 LogsDB 中你可以有选择地为最常进行范围查询的字段启用它们。同样在 9.3 中失败存储 (Failure Store) 对logs-*-*数据流默认启用。失败的文档映射冲突、摄取管道错误现在会进入专用的::failures索引而非被直接拒绝这意味着 LogsDB 严格的合成_source要求在迁移过程中不太可能导致静默数据丢失。不仅仅是存储还有性能LogsDB 始于存储优化早期版本确实带来了吞吐量成本——排序、合成_source重构和 ZSTD 都会在写入时增加工作量。经过两年的版本迭代我们夺回了这些性能。现在的索引吞吐量已与用户启用 LogsDB 之前的水平持平。你在获得存储缩减的同时无需牺牲以往的摄取速率。自技术预览版以来吞吐量青色已从约 2.5k 攀升至约 3.5k docs/s。在相同的基准测试数据集上磁盘存储蓝色从约 65 GB 降至约 36 GB。在分层发布的推动下两条曲线都向着正确的方向移动。实时数据见 elasticsearch-benchmarks.elastic.co。这两个趋势互补。更少的存储意味着更少需要合并的段从而释放了 CPU 用于索引。合成_source重构的计算成本比存储和复制原始块更低。每一个缩小索引的版本也减少了后台 I/O这反过来又提升了吞吐量。实际结果是如果你两年前运行标准 Elasticsearch 进行日志摄取那么你当时拥有的吞吐量大致就是 LogsDB 现在能提供的——同时还伴随着缩小了 50-75% 的索引。如何启用从 9.0 开始logs-*-*数据流会自动默认使用 LogsDB。如果你的数据流匹配该模式你已经在使用了。想要动手尝试使用 LogsDB 将 Elasticsearch 日志存储成本降低 76%介绍了创建两个索引、重新索引并使用_statsAPI 衡量差异的过程包括针对 8.x 集群的版本特定启用说明。对于其他索引模式请在模板中进行设置PUT_index_template/logs-template{index_patterns:[logs-*],template:{settings:{index.mode:logsdb}}}合成_source会随index.mode: logsdb自动开启。对于路由优化 (8.18)再添加一个设置PUT_index_template/logs-template{index_patterns:[logs-*],template:{settings:{index.mode:logsdb,index.logsdb.route_on_sort_fields:true}}}这将按排序字段值而非_id路由分片在增加 1-4% 摄取损耗的情况下增加约 20% 的存储缩减。它要求在timestamp之外至少有两个排序字段并使用自动生成的_id。将现有索引切换到 LogsDB 需要重新索引reindex。回滚也是如此。目前没有就地转换功能因此请先在新的数据流上进行尝试。随着段的合并存储空间会进一步优化——新写入的数据压缩效果不错但合并后的段压缩效果更好。未来展望由于其搜索引擎的根源Elasticsearch 仍然带有一些结构性开销。_id和_seq_no就是两个例子两者都消耗了大量的磁盘空间在小文档上它们可能占到索引大小的一半以上但对于日志分析工作负载来说这两者都不是必不可少的。我们已经为 TSDB 迈出了第一步PR #144026 通过从 doc values 即时重构字段消除了 TSDB 索引中存储的_id字节这与合成_source使用的方法相同。我们正在为 LogsDB 探索同样的方向。9.4 及以后。该架构仍有改进空间我们正在努力。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2542162.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!