2. Loki 存储模型:Chunk 存储原理、Index 存储原理(单 TSDB 与多 TSDB 模式)、对象存储(S3/GCS/MinIO)作为后端
Loki 的存储模型是其高性能与低成本的核心基石。与 Prometheus 或 Cortex 不同,Loki 不进行全文索引,而是采用 “仅索引元数据 + 压缩存储日志块” 的设计哲学。本章深入剖析 Chunk(数据块)与 Index(索引)的存储原理,并详解对象存储作为后端的最佳实践。
2.1 Chunk 存储原理
Chunk 是 Loki 中存储日志数据的最小物理单元。当日志流(Log Stream)被采集后,Ingester 组件会将多条日志条目压缩并封装为一个 Chunk。
2.1.1 Chunk 的生命周期
- 写入阶段:Ingester 接收日志流,按
stream hash + labels分组,将日志追加到内存中的 Chunk 缓冲区。 - 压缩与封装:当 Chunk 达到目标大小(默认 1.5 MB)或最大超时时间(默认 1 小时)时,Ingester 使用 gzip(默认)或 snappy / lz4 压缩日志数据。
- 上传:压缩后的 Chunk 被上传至对象存储(S3/GCS/MinIO),同时将 Chunk 元数据(如时间范围、标签集、Checksum)写入 Index。
- 只读与查询:上传后的 Chunk 变为只读,Querier 通过 Index 定位 Chunk 并直接从对象存储读取。
2.1.2 Chunk 内部结构
| 字段 | 说明 | 大小占比 |
|---|---|---|
| Header | Chunk 版本号、压缩算法标识、元数据长度 | ~0.1% |
| Metadata | 日志流标签集、时间范围(最小/最大时间戳)、条目数 | ~0.5% |
| Log Entries | 压缩后的日志行数据,按时间戳排序 | ~99% |
| Footer | Checksum(CRC32)用于完整性校验 | ~0.4% |
💡 优化要点: 调整
chunk_target_size(默认 1.5MB)可平衡内存占用与对象存储请求次数。增大 Chunk 大小可减少存储 API 调用,但会增加内存压力。
2.2 Index 存储原理:单 TSDB 与多 TSDB 模式
Index 负责存储日志流的元数据(标签、Chunk 引用、时间范围),是查询加速的关键。Loki 从 2.8 版本开始引入 TSDB (Time Series Database) 索引格式,逐步替代旧的 BoltDB 索引。
2.2.1 单 TSDB 模式(Single TSDB)
- 架构:每个 Ingester 或 Compactor 维护一个全局的 TSDB 索引文件。
- 存储方式:索引文件以 TSDB 块 形式写入对象存储,每个块包含完整的标签倒排索引与 Chunk 引用列表。
- 适用场景:中小规模集群(< 100 个 Ingester),日志流数量 < 50 万。
- 优点:部署简单,索引查询延迟低(单文件顺序读取)。
- 缺点:索引文件可能过大(> 10GB),导致 Compactor 合并压力大,查询时需加载整个索引。
2.2.2 多 TSDB 模式(Multi TSDB / Sharded TSDB)
- 架构:将全局索引按 时间范围 + 标签哈希 拆分为多个独立的 TSDB 分片(Shard)。
- 存储方式:每个分片是一个独立的 TSDB 块,Compactor 定期合并小分片为更大块。
- 适用场景:大规模集群(> 100 个 Ingester),日志流数量 > 100 万。
- 优点:索引分片可并行查询,降低单文件大小;Compactor 可独立合并每个分片,提升吞吐。
- 缺点:需要配置分片策略(如
tsdb_shipper.resync_interval),查询时需合并多个分片结果。
| 对比维度 | 单 TSDB 模式 | 多 TSDB 模式 |
|---|---|---|
| 索引文件大小 | 单个大文件(可能 > 10GB) | 多个小文件(每个 < 1GB) |
| 查询延迟 | 低(单文件顺序读) | 中(需合并多分片) |
| Compactor 压力 | 高(合并大文件) | 低(并行合并小分片) |
| 扩展性上限 | ~50 万日志流 | ~500 万+ 日志流 |
| 配置复杂度 | 低 | 中(需调分片数) |
📌 生产建议: 新集群默认启用多 TSDB 模式(
schema_config.configs[].index.type: tsdb)。对于已有集群,可通过 tsdb_shipper.active_index_directory 和 tsdb_shipper.cache_location 控制本地缓存路径。
2.3 对象存储作为后端:S3 / GCS / MinIO
Loki 将 Chunk 和 Index 统一存储在对象存储中,实现计算与存储分离。对象存储的高可用、低成本、无限扩展特性使其成为理想后端。
2.3.1 支持的存储后端对比
| 后端 | 协议 | 典型延迟 | 成本(每 GB/月) | 适用场景 |
|---|---|---|---|---|
| AWS S3 | S3 API | 5-20 ms | ~$0.023 | 生产环境,高可用,全球部署 |
| GCS | GCS API | 5-20 ms | ~$0.020 | GCP 原生集成,强一致性 |
| MinIO | S3 兼容 API | 1-5 ms(本地) | 自建硬件成本 | 本地/私有云,低延迟,合规需求 |
| Azure Blob | Azure API | 10-30 ms | ~$0.018 | Azure 生态,冷数据归档 |
2.3.2 对象存储配置示例(MinIO)
storage_config:
aws:
s3: http://minio:9000
bucketnames: loki-data
access_key_id: minioadmin
secret_access_key: minioadmin
s3forcepathstyle: true
boltdb_shipper:
active_index_directory: /data/loki/index
cache_location: /data/loki/index-cache
shared_store: s3
⚠️ 关键配置:
s3forcepathstyle: true必须为 MinIO 启用,否则路径解析错误。shared_store: s3确保 Index 也写入对象存储,实现无状态 Ingester。- 生产环境建议启用 版本控制 和 生命周期策略(如 30 天后转为冷存储)。
2.4 存储架构总览
下图展示了 Loki 存储模型中 Chunk、Index 与对象存储的交互关系:
2.5 存储优化最佳实践
- Chunk 大小调优:设置
chunk_target_size: 2097152(2MB)可减少对象存储 PUT 请求次数,降低存储成本。 - 索引分片策略:多 TSDB 模式下,
tsdb_shipper.resync_interval: 5m控制索引同步频率;compactor.working_directory需有足够磁盘空间(建议 100GB+)。 - 对象存储生命周期:配置 S3 生命周期策略,将超过 30 天的 Chunk 自动转为 Glacier 或 Deep Archive,节省 80% 存储费用。
- 本地缓存:启用
chunk_store_config.chunk_cache_config和index_cache_config,使用 Redis 或 Memcached 缓存热数据,减少对象存储读取延迟。 - 压缩算法选择:gzip 压缩比最高(~5:1),但 CPU 开销大;snappy 压缩比略低(~3:1),但速度更快。日志量大时推荐 snappy。
🔧 实战案例: 某电商平台每天产生 10TB 原始日志。通过将 Chunk 目标大小从 1.5MB 提升至 3MB,并启用 snappy 压缩,对象存储 PUT 请求减少 40%,存储成本降低 25%。同时启用多 TSDB 模式(16 分片),查询 P99 延迟从 2.3s 降至 1.1s。