VictoriaLogs使用列式布局存储日志
速览
VictoriaLogs的核心技术是列式布局存储,其日志按流(stream)分组进blocks,并将每个字段独立作为一列存储。数据先进入内存部分,再通过每日分区持久化到磁盘,包括时间戳、值、bloom过滤器和两级索引等文件。查询时仅读取所需列和块,避免全量扫描。这种设计使VictoriaLogs支持处理大量高基数日志,同时提供快速查询和低磁盘占用,对日志分析场景意义重大。
AI 深度解读
标题: VictoriaLogs 以列式布局存储日志
背景
VictoriaLogs 是 VictoriaMetrics 系列组件,专为高效处理大规模日志而设计。它支持多种协议接收日志(JSON Lines、Elasticsearch bulk、Loki push、OpenTelemetry、syslog 等),并通过数据 ingestion 文档提供完整列表。用户日常主要涉及发送日志、查询日志和设置 retention(防止磁盘爆满)三个操作,其余工作在磁盘后台静默完成。
这篇博客文章适合所有用户,无需编程背景或阅读 Go 代码。若想深入了解,VictoriaLogs 的源代码始终是权威参考。
核心内容
VictoriaLogs 将每条日志记录标准化为内部格式:包含时间戳、命名字段和“stream identity”(流标识)。每种协议都有专用处理器处理这一转换,用户可通过查询参数或请求头进行控制:
- 使用
ignore_fields查询参数或VL-Ignore-Fields头丢弃不需要存储的字段。 - 使用
decolorize_fields查询参数或VL-Decolorize-Fields头去除终端颜色码。 - 使用
extra_fields查询参数或VL-Extra-Fields头为每条记录附加额外字段。 - 使用
_msg_field查询参数或VL-Msg-Field头指定主要消息字段(默认_msg)。 - 使用
_time_field查询参数或VL-Time-Field头指定时间戳字段。 - 使用
_stream_fields查询参数或VL-Stream-Fields头定义流标识。
流标识 是这篇文章最核心的概念。共享相同流标识的日志被视为一个“流”,由用户自行决定。例如,设置 _stream_fields=pod,container,则同一 pod 和 container 下的所有日志形成一个流。
VictoriaLogs 将每个流的日志保存在磁盘上一起,这使得数据压缩效果极佳,并允许查询仅接触需要的流,而非扫描整个数据集。
操作实践建议:保持流字段稳定且低基数(如 host、app、pod、container),将高基数字段(如 trace_id、user_id)作为普通字段存储。
接收并标准化记录后,VictoriaLogs 不会逐条处理,而是将它们累积到内存缓冲区。大约每秒(或缓冲区满时更快)将整个批次转换为一个小可搜索的 RAM 块(in-memory part)。
缓冲区并非单一共享队列,而是按 CPU 核心拆分为多个分片(shards),以避免等待。每个分片独立刷新,大约每秒写入一个新 in-memory part。
part 是 VictoriaLogs 核心数据结构:一个自包含、可查询的数据包。
多数情况下,批次刷新为 in-memory part;少数情况下,若批次超过内存大小限制,则直接写入磁盘,作为 small part 或 big part。
监控提示:vl_insert_flush_duration_seconds 指标记录将缓冲批次转为 in-memory part 的耗时。
批次刷新时,每条日志被分入一个 partition(分区),一个分区精确包含一天的日志(UTC 时间)。根据时间戳确定所属日期。
日志按日期分离:磁盘上每个日期一个目录,例如 victoria-logs-data/partitions/20260109/ 等。
这一布局使两项日常操作成本极低:
- Retention 通过删除整个日期目录实现。超过
-retentionPeriod(默认 7 天)或磁盘保留触发时,VictoriaLogs 直接删除文件夹,而非逐条删除日志。 - 查询 几乎总是时间有界(如
_time:1h、_time:5m),因此仅需打开重叠的时间分区,其余可忽略。
partition 不只是磁盘文件夹,它同时拥有磁盘侧(已写入的 parts)和内存侧(缓冲分片和 in-memory parts)。查询分区时,同时从两侧获取结果,仅在必要时从磁盘拉取。
监控提示:vl_storage_parts 指标按类型(storage/inmemory、storage/small、storage/big)统计 parts 数量;vl_pending_rows{type="storage"} 统计仍停留在缓冲区未转为 part 的行数。
part 是缓冲区刷新后的可查询日志包,有三种形态(同一数据不同阶段):
- In-memory parts:首次创建,新鲜日志几乎立即可查询,无需等待磁盘。
- Small parts:in-memory parts 写入磁盘以确保持久性。
- Big parts:small parts 随时间合并而成。
日志先在内存缓冲区刷新为 in-memory parts,再触及磁盘,这确保了低廉的 ingestion 吞吐量(甚至在低 IOPS HDD 上可达每秒约 1 GiB)。
权衡:in-memory part 常驻 RAM,查询几秒内可见,但不持久。VictoriaLogs 通过 -inmemoryDataFlushInterval(默认 5s)标志保证定期刷新到磁盘。
磁盘上,每个 part 是一个 16 字符十六进制目录 ID,位于分区的 datadb 文件夹内。indexdb 文件夹单独保存该天的流目录:
victoria-logs-data/partitions/20260109/
├── indexdb/
└── datadb/
├── 1882C35B4CE64498/
├── 1882C35B4CE664F8/
├── 1882C35B4CE66BDB/
└── parts.json
parts.json 列出当前活跃 parts,启动时 VictoriaLogs 读取它们以确定要读取的目录。
part 是不可变的:写入后文件永不更改,这使得快照和备份安全且廉价。
small part 与 big part 的区别:源于操作系统的页面缓存(OS page cache)。页面缓存是 OS 保留的 RAM 片段,用于存放最近读取的文件数据。RAM 越多,缓存越大,更多日志可直接从内存服务。
Small parts 通过页面缓存写入并保持小尺寸(至少 10 MiB,由 VictoriaLogs 空闲 RAM 决定),通常驻留 RAM 并快速读取。Big parts 可达约 1 TB,不通过页面缓存写入大合并,避免逐出热数据。
datadb 之外,indexdb 持有流目录:哪些流存在及其字段。查询时先检查目录,避免扫描无关流。
关键要点
- VictoriaLogs 按流(稳定、低基数字段如 pod/container)分组日志,实现高效压缩和仅扫描必要流的查询。
- 日志按日期(UTC)分入分区,retention 删除整个目录,时间界查询仅触及重叠分区。
- data structure 基于不可变 parts:in-memory(立即查询但非持久)、small(持久化)、big(合并后)。
- 磁盘 part 为带 16 字符 ID 的目录,包含 metadata.json、timestamps.bin、values.binN 等文件。
- 每一文件均用于“读取少量、跳过大量”:metadata 跳过时间不符 parts,bloom filter 预过滤,column headers 定位精确 blob。
- 查询路径:先查 in-memory metaindex.bin(流+时间范围),再到 index.bin 的 block headers,最后精确到单字节。
意义与影响
VictoriaLogs 采用的 per-stream 分区 + columnar(按字段列存)布局是其高性能与低资源消耗的核心。它让 even 在 HDD 或高基数日志场景下仍能高效存储与查询(数 TiB 原始日志压缩至数百 GiB),并支持水平扩展。
对用户的直接影响:
- 查询更快、更可预测:时间界查询几乎零开销,wide logs(数十字段)通过
fields | keep进一步精简。 - 存储成本低:retention 仅删目录,压缩率高,节省磁盘和 I/O。
- 运维透明:通过
vl_storage_parts、vl_pending_rows等指标,快速定位问题(e.g. 内存部分或磁盘碎片)。 - 易扩展与备份:part 不可变,支持高效快照,无需复杂复制逻辑。
总体而言,这篇解读揭示了 VictoriaLogs 如何在底层将复杂日志处理转化为简单高效的“发送-查询-保留”循环,为大规模日志架构提供了可靠的工程基础。理解这些机制后,用户能更精准地调优和排除故障。
