Logs:Loki、ClickHouse、Elasticsearch、OpenObserve 的取舍
日志是可观测性三大支柱中最”重”的一支:数据量是 Metrics 的 100 倍,格式比 Traces 更随意,查询需求又从”全文搜索”到”聚合分析”到”审计合规”都有。选错方案,要么月账单超预算,要么在关键故障时因查询太慢无从排查。本文从日志场景分类开始,逐一剖析四大主流方案的内部机制,给出可执行的选型依据。
一、日志场景分类
1.1 场景矩阵
不同业务对日志系统的需求本质不同:
| 场景 | 典型需求 | 关键指标 |
|---|---|---|
| 故障排查(Debug) | 快速过滤关键词,查找错误堆栈 | 查询延迟 < 3s,支持全文搜索 |
| 聚合分析 | 统计 QPS 趋势、错误率、P99 | SQL 或 LogQL,GROUP BY + 时间序列 |
| 审计日志 | 不可篡改,长期保存(3-7 年) | 写入后不可删除,合规存储 |
| 追踪关联 | 通过 trace_id 关联 Logs↔︎Traces | 高基数字段快速查询 |
| 安全分析(SIEM) | 实时规则匹配,检测异常行为 | 低延迟写入,规则引擎集成 |
这五类场景没有一个方案能完美覆盖。选型的第一步是明确你的主场景是哪一类,或哪两类。
1.2 日志数据体量估算
生产环境日志量的粗略估算公式:
日志总量(GB/天)= 平均日志行大小(字节)× QPS × 86400 / 1e9
示例:
平均日志行 = 500 字节(JSON 格式含 trace_id)
服务 QPS = 10000
日志采样率 = 10%(仅记录 10% 请求)
日志量 = 500 × 10000 × 0.1 × 86400 / 1e9 ≈ 43 GB/天
加上压缩(zstd,约 5:1):≈ 8.6 GB/天存储
保留 30 天:≈ 258 GB 存储成本
二、Elasticsearch / OpenSearch 架构
2.1 核心架构
弹性搜索(Elasticsearch,简称 ES)由 Shay Banon 于 2010 年创建,基于 Apache Lucene,是目前全文搜索领域最成熟的方案。
写入路径:
客户端 ──REST API──► 协调节点(Coordinating Node)
↓ 路由(shard = hash(doc_id) % primary_shards)
主分片(Primary Shard)
↓ 同步复制
副本分片(Replica Shard)×n
查询路径:
客户端 ──REST API──► 协调节点
↓ 广播查询到所有相关分片
所有分片(并行)
↓ 返回局部结果
协调节点合并结果
↓
返回客户端
2.2 Lucene 倒排索引
ES 的核心是 Lucene 的倒排索引(Inverted Index)。写入一条日志时,ES 对所有字段分词(Tokenize)并建立索引:
原始日志:
{"message": "user login failed", "user": "alice", "status": 401}
倒排索引:
term "user" → [doc1, doc5, doc12, ...]
term "login" → [doc1, doc3, doc8, ...]
term "failed" → [doc1, doc7, ...]
user=alice → [doc1, ...]
status=401 → [doc1, doc3, ...]
查询 “user login failed” 时,取三个词的 Posting List 的交集,速度极快。这也是 ES 在全文搜索场景无可替代的原因。
但代价是:索引大小通常是原始数据的 2-5 倍。对于每天数 TB 日志的场景,存储成本急剧膨胀。
2.3 日志索引模板(Index Template)
// PUT _index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.codec": "best_compression",
"index.refresh_interval": "5s",
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
},
"mappings": {
"dynamic_templates": [
{
"strings_as_keyword": {
"match_mapping_type": "string",
"match": "*_id",
"mapping": {"type": "keyword"}
}
},
{
"strings_as_text": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"norms": false,
"fields": {
"keyword": {"type": "keyword", "ignore_above": 256}
}
}
}
}
],
"properties": {
"@timestamp": {"type": "date"},
"level": {"type": "keyword"},
"service": {"type": "keyword"},
"trace_id": {"type": "keyword"},
"span_id": {"type": "keyword"},
"message": {"type": "text", "analyzer": "standard"},
"http.status": {"type": "integer"},
"duration_ms": {"type": "float"}
}
}
}
}2.4 ILM(Index Lifecycle Management)
// PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "1d"
},
"set_priority": {"priority": 100}
}
},
"warm": {
"min_age": "3d",
"actions": {
"shrink": {"number_of_shards": 1},
"forcemerge": {"max_num_segments": 1},
"set_priority": {"priority": 50}
}
},
"cold": {
"min_age": "30d",
"actions": {
"freeze": {},
"set_priority": {"priority": 0}
}
},
"delete": {
"min_age": "90d",
"actions": {"delete": {}}
}
}
}
}2.5 ES Query DSL 常用查询
// 5xx 错误的全文检索 + 聚合
POST /logs-*/_search
{
"size": 20,
"query": {
"bool": {
"filter": [
{"range": {"@timestamp": {"gte": "now-15m"}}},
{"term": {"level": "ERROR"}},
{"range": {"http.status": {"gte": 500}}}
],
"must": [
{"match": {"message": "NullPointerException"}}
]
}
},
"aggs": {
"by_service": {
"terms": {"field": "service", "size": 10},
"aggs": {
"error_count": {"value_count": {"field": "_id"}},
"timeline": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1m"
}
}
}
}
},
"sort": [{"@timestamp": {"order": "desc"}}]
}三、Grafana Loki:标签驱动的低成本日志
3.1 设计理念
Loki 由 Grafana Labs 于 2018 年创建,核心设计决策:不对日志内容建立全文索引,只索引标签(Labels)。
这个决策让 Loki 的存储成本极低:
- 日志内容以压缩块(Chunk)存储在对象存储(S3/GCS/阿里云 OSS)
- 只有标签(如
app、namespace、pod)建立索引 - 查询时先通过标签过滤缩小范围,再对日志内容做正则/字符串匹配
代价是:纯全文搜索性能远低于 ES,必须先指定标签才能查询。
3.2 架构组件
写入路径:
Promtail/Fluentd/Vector
──push──► Distributor(一致性哈希)
↓
Ingester(内存 WAL + Chunk)
↓ flush(chunk 写满 / 超时)
Object Store(S3/OSS/GCS)
查询路径:
Grafana ──LogQL──► Query Frontend(分片 + 缓存)
↓
Querier(并行查询)
┌──────┴───────┐
Ingester Store Gateway(历史)
(新鲜数据) (对象存储块)
单体模式(-target=all)将所有组件合并为单个进程,适合小规模部署。
3.3 LogQL 查询语言
LogQL 是 Loki 的查询语言,语法类似 PromQL。
日志流选择器(Log Stream Selector):
# 选择 namespace=production 的 app=nginx 的所有日志
{namespace="production", app="nginx"}
# 正则过滤(慢,需全内容扫描)
{app="nginx"} |~ "status=5[0-9]+"
# 字符串包含过滤(快,KMP 算法)
{app="nginx"} |= "error"
# 多条件组合
{app="api"} |= "error" != "timeout" | json | level="ERROR"
指标提取(Metric Queries):
# 统计每分钟 5xx 错误数(从日志提取指标)
sum(
rate(
{app="nginx"} |= "HTTP/1.1 5" [1m]
)
) by (pod)
# 解析 JSON 日志,计算 P99 延迟
histogram_quantile(0.99,
sum by (le) (
rate(
{app="api"} | json | duration_ms > 0
| unwrap duration_ms [5m]
)
)
)
# 提取字段并过滤(pipeline)
{app="order-service"}
| json
| line_format "{{.level}} {{.message}}"
| label_format service="{{ .service }}"
| level="ERROR"
| duration_ms > 1000
3.4 Loki 配置示例
# loki.yaml
auth_enabled: true # 多租户模式
server:
http_listen_port: 3100
grpc_listen_port: 9095
ingester:
chunk_idle_period: 30m
chunk_block_size: 262144 # 256 KiB
chunk_target_size: 1572864 # 1.5 MiB(未压缩)
chunk_retain_period: 0
wal:
enabled: true
dir: /data/loki/wal
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
storage_config:
aws:
s3: s3://loki-logs-bucket
region: cn-northwest-1
endpoint: s3.cn-northwest-1.amazonaws.com.cn
tsdb_shipper:
active_index_directory: /data/loki/tsdb-index
cache_location: /data/loki/tsdb-cache
limits_config:
ingestion_rate_mb: 64
ingestion_burst_size_mb: 128
max_streams_per_user: 100000
max_label_names_per_series: 30
max_label_value_length: 4096
retention_period: 30d
per_stream_rate_limit: 10MB
per_stream_rate_limit_burst: 20MB3.5 Promtail 配置示例
# promtail.yaml
server:
http_listen_port: 9080
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: production # 多租户 ID
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
# 解析 Docker JSON 格式
- docker: {}
# 解析应用 JSON 日志
- json:
expressions:
level: level
message: msg
trace_id: traceId
service: service
duration: duration
# 提取为标签(注意:高基数字段不要设为标签)
- labels:
level:
service:
# 过滤 DEBUG 级别日志(节省存储)
- drop:
source: level
value: DEBUG
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod四、ClickHouse 作为日志后端
4.1 为什么 ClickHouse 适合日志
ClickHouse 是俄罗斯 Yandex 开发的列式 OLAP 数据库,2016 年开源。用于日志场景的优势:
- 极高的写入吞吐:顺序写 MergeTree,SSD 可轻松达到 100 MB/s+
- 极高的压缩率:zstd 列式压缩,日志数据通常 5:1-10:1
- 强大的聚合:GROUP BY 可以秒级处理数十亿行
- 灵活的 SQL:无需学新查询语言
代价:不支持 PromQL/LogQL,需要 SQL 适配层;不支持原生多租户。
4.2 日志表设计
-- 生产级日志表
CREATE TABLE logs (
-- 分区键:按天分区
date Date DEFAULT toDate(timestamp),
-- 时间戳(毫秒精度)
timestamp DateTime64(3, 'UTC'),
-- 基础字段(作为 ORDER BY 提升范围查询性能)
level LowCardinality(String),
service LowCardinality(String),
namespace LowCardinality(String),
-- 追踪相关(keyword 级别,高基数)
trace_id String,
span_id String,
-- 日志内容
message String,
-- 结构化字段(KV 存储)
fields Map(String, String),
-- HTTP 相关
http_method LowCardinality(String),
http_status UInt16,
duration_ms Float32,
-- 原始 JSON(备用)
raw String CODEC(ZSTD(3))
)
ENGINE = MergeTree()
PARTITION BY (date)
ORDER BY (service, level, timestamp)
TTL date + INTERVAL 30 DAY DELETE
SETTINGS
index_granularity = 8192,
min_bytes_for_wide_part = 10485760; -- 10 MiB
-- 跳数索引:加速 trace_id 点查
ALTER TABLE logs
ADD INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 4;
-- 跳数索引:加速 message 全文搜索
ALTER TABLE logs
ADD INDEX idx_message message TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4;4.3 常用查询
-- 查询最近 5 分钟的错误日志(利用 ORDER BY 索引)
SELECT timestamp, service, message, trace_id
FROM logs
WHERE
date >= today() - 1
AND timestamp >= now() - INTERVAL 5 MINUTE
AND level = 'ERROR'
ORDER BY timestamp DESC
LIMIT 100;
-- 统计各服务错误率(GROUP BY 聚合)
SELECT
service,
countIf(level = 'ERROR') AS errors,
count() AS total,
round(countIf(level = 'ERROR') * 100.0 / count(), 2) AS error_rate_pct
FROM logs
WHERE date = today()
GROUP BY service
ORDER BY error_rate_pct DESC;
-- trace_id 关联查询(通过 Bloom Filter 快速定位)
SELECT timestamp, service, level, message
FROM logs
WHERE trace_id = '4bf92f3577b34da6a3ce929d0e0e4736'
ORDER BY timestamp;
-- 全文搜索(tokenbf 索引加速)
SELECT timestamp, service, message
FROM logs
WHERE hasToken(message, 'NullPointerException')
AND date >= today() - 3
ORDER BY timestamp DESC
LIMIT 50;
-- 时间序列:每分钟错误数(用于生成图表)
SELECT
toStartOfMinute(timestamp) AS ts,
service,
count() AS error_count
FROM logs
WHERE date = today()
AND level = 'ERROR'
GROUP BY ts, service
ORDER BY ts;4.4 ClickHouse TTL 与分层存储
-- TTL + 数据移动(冷数据转移到对象存储磁盘)
ALTER TABLE logs MODIFY TTL
date + INTERVAL 7 DAY TO DISK 'cold_disk',
date + INTERVAL 90 DAY DELETE;
-- cold_disk 配置(config.xml)
/*
<storage_configuration>
<disks>
<cold_disk>
<type>s3</type>
<endpoint>https://oss-cn-hangzhou.aliyuncs.com/logs-cold/</endpoint>
<access_key_id>...</access_key_id>
<secret_access_key>...</secret_access_key>
</cold_disk>
</disks>
<policies>
<tiered>
<volumes>
<hot>
<disk>default</disk>
<max_data_part_size_bytes>10737418240</max_data_part_size_bytes>
</hot>
<cold>
<disk>cold_disk</disk>
</cold>
</volumes>
<move_factor>0.2</move_factor>
</tiered>
</policies>
</storage_configuration>
*/五、OpenObserve
5.1 简介
OpenObserve(前身 ZincObserve)由 Prabhat Sharma 等人于 2023 年用 Rust 编写,主打低成本统一可观测性(Logs + Metrics + Traces)。核心特点:
- 数据以 Apache Parquet 格式存储在对象存储(S3/MinIO/GCS/本地)
- 内置 tantivy(Rust 全文搜索库)实现全文检索
- 支持 SQL 和 LogQL 查询
- 单二进制部署,极易运维
- 号称比 ES 便宜 140×(存储成本)
# docker-compose.yml — OpenObserve 最小部署
version: "3"
services:
openobserve:
image: public.ecr.aws/zinclabs/openobserve:latest
ports:
- "5080:5080"
environment:
ZO_ROOT_USER_EMAIL: admin@example.com
ZO_ROOT_USER_PASSWORD: admin_password
ZO_DATA_DIR: /data
ZO_S3_BUCKET_NAME: openobserve-data
ZO_S3_REGION_NAME: cn-northwest-1
ZO_S3_ACCESS_KEY: ${S3_ACCESS_KEY}
ZO_S3_SECRET_KEY: ${S3_SECRET_KEY}
ZO_S3_ENDPOINT: https://s3.cn-northwest-1.amazonaws.com.cn
volumes:
- ./data:/data5.2 OpenObserve 查询
-- 使用 SQL 查询日志
SELECT *
FROM "default"."logs"
WHERE _timestamp > (NOW() - interval '5 minute')
AND level = 'ERROR'
ORDER BY _timestamp DESC
LIMIT 100;
-- 全文搜索
SELECT _timestamp, service, message
FROM "default"."application_logs"
WHERE match_all('NullPointerException')
AND _timestamp > (NOW() - interval '1 hour')
ORDER BY _timestamp DESC;六、Quickwit 与 SigNoz 简介
6.1 Quickwit
Quickwit 是 Rust 编写的云原生搜索引擎,专为日志和可观测性设计:
- 基于 Apache Tantivy(类 Lucene 的 Rust 实现)
- 数据存储在对象存储,接近 ES 的全文检索能力
- 支持 ES Query API(可作为 ES 替代)
- 无状态节点,水平扩展简单
# quickwit.yaml 最小配置
version: 0.7
node_id: searcher-1
metastore_uri: s3://quickwit-meta/
default_index_root_uri: s3://quickwit-data/
searcher:
fast_field_cache_capacity: 1G
split_footer_cache_capacity: 500M
max_num_concurrent_split_searches: 100
indexer:
split_store_max_num_bytes: 100G6.2 SigNoz
SigNoz 是基于 OpenTelemetry 的全栈可观测性平台(Logs + Metrics + Traces),使用 ClickHouse 作为存储后端,对 K8s 原生支持好,适合不想自己拼 Grafana 仪表盘的团队。
七、LogQL vs ES Query DSL 详细对比
7.1 基础过滤
需求:查找过去 15 分钟内包含 "database connection" 的 ERROR 日志
LogQL:
{app="api", namespace="prod"} |= "database connection" | json | level="ERROR"
ES Query DSL:
{
"query": {
"bool": {
"filter": [
{"range": {"@timestamp": {"gte": "now-15m"}}},
{"term": {"level": "ERROR"}}
],
"must": [
{"match_phrase": {"message": "database connection"}}
]
}
}
}
7.2 聚合对比
需求:统计各服务最近 1h 每分钟 ERROR 数量
LogQL:
sum by (service) (
count_over_time(
{namespace="production"} | json | level="ERROR" [1m]
)
)
ES Query DSL:
{
"size": 0,
"query": {
"bool": {
"filter": [
{"range": {"@timestamp": {"gte": "now-1h"}}},
{"term": {"level": "ERROR"}}
]
}
},
"aggs": {
"by_service": {
"terms": {"field": "service.keyword"},
"aggs": {
"timeline": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1m"
}
}
}
}
}
}
7.3 能力对比总结
| 能力 | LogQL | ES Query DSL |
|---|---|---|
| 全文搜索 | 简单正则/字符串 | ✓ 完整倒排索引 |
| 字段聚合 | ✓ 通过 unwrap 提取 | ✓ Aggregation API |
| 嵌套对象查询 | 通过 json parser | ✓ 原生嵌套 |
| 地理位置查询 | ✗ | ✓ Geo Query |
| 日志到 Metrics 转换 | ✓ 内置 | 需要 Elastic APM |
| 学习曲线 | 低(类 PromQL) | 高(JSON DSL 冗长) |
八、日志格式标准
8.1 JSON 格式(推荐)
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "ERROR",
"service": "order-service",
"version": "1.2.3",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"message": "Failed to process order",
"error": {
"type": "DatabaseException",
"message": "Connection timeout after 5000ms",
"stack": "..."
},
"http": {
"method": "POST",
"path": "/api/v1/orders",
"status": 500,
"duration_ms": 5234
},
"user_id": "usr_12345",
"order_id": "ord_98765"
}8.2 Logfmt 格式
ts=2024-01-15T10:30:45.123Z level=ERROR service=order-service trace_id=4bf92f35 msg="Failed to process order" duration_ms=5234 status=500
Logfmt 更紧凑,Loki 的 logfmt parser
可以直接解析。
8.3 OpenTelemetry Logs Data Model
OpenTelemetry 定义的日志数据模型,是未来的标准方向:
# OTel Log Record 结构
LogRecord:
Timestamp: Unix 纳秒时间戳
ObservedTimestamp: 采集到的时间戳
TraceId: bytes[16] # 与 Trace 关联
SpanId: bytes[8]
TraceFlags: uint8
SeverityText: "ERROR" # 人类可读级别
SeverityNumber: 17 # 数字级别(枚举)
Body: AnyValue # 日志内容(string/bytes/map/array)
Resource: # 来源资源(服务、版本)
Attributes:
service.name: "order-service"
service.version: "1.2.3"
deployment.environment: "production"
InstrumentationScope: # 产生日志的库
Name: "github.com/company/order"
Version: "0.1.0"
Attributes: # 自定义属性
http.method: "POST"
http.status_code: 500
order.id: "ord_98765"九、国内案例:B 站与知乎的 ClickHouse 日志平台
9.1 B 站日志平台演进
B 站(哔哩哔哩)在 2021 年公开了其日志平台技术选型(基于公开技术博客整理):
演进路线:ELK(2016 年)→ ClickHouse(2020 年)
关键驱动因素: - 业务日志量增长至每天数十 TB,ES 的索引磁盘成本不可承受 - ES 在 GC 频繁时查询延迟 P99 超过 10s,影响故障排查 - ClickHouse 相同存储量的磁盘成本约为 ES 的 1/5
B 站 ClickHouse 日志架构:
日志采集(Fluentd/自研 Agent)
↓
Kafka(缓冲 + 解耦)
↓
ClickHouse Kafka Engine(消费)
↓
MergeTree 表(热数据,SSD)
↓ TTL 迁移
MergeTree 表(冷数据,HDD)
关键配置决策: - 按日期 +
服务分区:PARTITION BY (toYYYYMM(timestamp), service)
- 去掉 ES 的 message 字段全文索引,改为
ClickHouse tokenbf 跳数索引 - 使用 ZSTD(3)
压缩:同等数据量磁盘占用降低 70%
9.2 知乎日志分析平台
知乎在 2022 年分享了其日志分析平台(基于公开资料整理):
核心挑战:知乎的数据规模不支持 ES 的高索引成本,但又需要全文检索能力。
解决方案:双写架构
日志 → Fluentd
├──► Loki(轻量,用于故障排查,按标签过滤)
└──► ClickHouse(重查询,用于聚合分析)
- Loki 负责故障排查:开发者通过 Grafana 快速按 pod/namespace 过滤
- ClickHouse 负责聚合报表:业务分析、SLO 计算、定期报告
这个双写架构实际上是对”没有一个方案能覆盖所有场景”这一结论的工程化回答。
十、工程坑点
10.1 ES Mapping 爆炸
现象:ES 集群节点频繁出现
circuit_breaking_exception,内存报警,最终
OOM。
根因:某服务将 JSON
日志中所有字段动态映射(Dynamic Mapping),有人在
extra 字段中放了随机 key(如
request_param_{uuid}),导致 mapping 字段数从
50 个增长到 500 万个,字段元数据全在内存中。
解决方案:
// 禁用动态 mapping,只允许已知字段
PUT /logs-*/_mapping
{
"dynamic": "strict",
"properties": {
"message": {"type": "text"},
"level": {"type": "keyword"},
"service": {"type": "keyword"},
"timestamp": {"type": "date"},
"extra": {"type": "object", "dynamic": false, "enabled": false}
}
}同时设置全局 mapping 字段上限:
# elasticsearch.yml
indices.mapping.total_fields.limit: 200010.2 Loki Label 错用导致流爆炸
现象:Loki Ingester OOM,Distributor 报错 “too many outstanding requests”,查询延迟从 0.1s 上升到 30s。
根因:Promtail 配置了如下 Label:
# 错误:把 trace_id 设为 Label!
pipeline_stages:
- json:
expressions:
trace_id: trace_id
- labels:
trace_id: # 每个请求不同,产生无数 stream!Loki 的每个唯一 Label 集合都是一个独立 Stream,trace_id 有无数唯一值,导致 Stream 数量爆炸。
正确做法:
pipeline_stages:
- json:
expressions:
trace_id: trace_id
service: service
level: level
# 只把低基数字段设为 Label
- labels:
service:
level:
# trace_id 作为 structured_metadata(Loki 2.9+)
- structured_metadata:
trace_id:Loki 的最佳实践:Label 数量 < 20,每个 Label 的唯一值 < 1000。
10.3 ClickHouse TTL 设置不当导致数据丢失
现象:某次排查事故时发现 3 天前的关键日志已经消失。
根因:TTL 设置为天级别,但使用了
Date 类型(精度只到天):
-- 错误:TTL 基于 Date 字段,删除时机不可预测
TTL date + INTERVAL 7 DAY DELETEClickHouse 的 TTL 是惰性删除,在下次 Merge
时才真正删除。上述配置可能在 7 DAY 后的任意
Merge 时刻删除数据,并不保证 7 天内数据一定存在。
正确做法:
-- 基于精确 DateTime64 字段,设置充裕的 TTL
ALTER TABLE logs MODIFY TTL
toDateTime(timestamp) + INTERVAL 8 DAY DELETE;
-- 查看 TTL 删除进度
SELECT
table,
engine,
partition,
rows,
bytes_on_disk
FROM system.parts
WHERE table = 'logs'
ORDER BY partition DESC;十一、选型建议
11.1 场景到方案的映射
主场景:故障排查 + 全文搜索
→ 优先 ES(功能最强)
→ 成本受限:Quickwit / OpenObserve(tantivy 支持全文)
主场景:K8s 日志 + Grafana 生态 + 低成本
→ Loki(Grafana 原生集成,标签驱动)
→ 注意:不要把高基数字段设为 Label
主场景:日志聚合分析 + SQL 团队
→ ClickHouse(聚合性能极强,运维相对复杂)
→ 配合 Grafana ClickHouse 数据源插件
主场景:中小团队统一 Logs+Metrics+Traces
→ OpenObserve(单二进制,对象存储,成本极低)
→ SigNoz(OTel 原生,ClickHouse 后端)
主场景:既要全文搜索又要低成本
→ Loki + ClickHouse 双写(知乎方案)
→ 或 Quickwit(全文搜索 + 对象存储)
11.2 落地清单
参考资料
- Elasticsearch 官方文档 — https://www.elastic.co/guide/en/elasticsearch/reference/
- Grafana Loki 官方文档 — https://grafana.com/docs/loki/
- ClickHouse 官方文档 — https://clickhouse.com/docs/
- OpenObserve 官方文档 — https://openobserve.ai/docs/
- Quickwit 官方文档 — https://quickwit.io/docs/
- B 站技术博客,“从 ELK 迁移到 ClickHouse 的日志平台实践”(2021 年)
- 知乎技术博客,“知乎日志分析系统的演进与实践”(2022 年)
- Grafana Labs, “Loki: Like Prometheus, but for Logs” (KubeCon NA 2018)
- ClickHouse 开发者博客,“How to Store Logs in ClickHouse” — https://clickhouse.com/blog/
- OpenTelemetry Logs Specification — https://opentelemetry.io/docs/specs/otel/logs/
- Yury Izrailevsky (Netflix), “Architecting for Logs at Scale” (re:Invent 2019)
上一篇:时序数据库内核:TSM、TSI、倒排索引与 Gorilla 压缩
下一篇:日志管道:Fluent Bit、Vector、Logstash、Filebeat 实战
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【可观测性工程】可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱
从控制论到云原生:拆解可观测性的五大信号支柱,对比监控与可观测性的本质区别,梳理开源/商业/SaaS 分类,以及国内互联网公司三大支柱落地现状与典型工程坑点。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。
【可观测性工程】可观测性 vs 监控:从 Zabbix/Nagios 到 OpenTelemetry 的二十年
监控与可观测性不是新旧迭代,而是认知模型的根本转换。本文梳理从 1999 年 Nagios 到 2019 年 OpenTelemetry 的二十年演进时间线,对比 push/pull 模型、数据模型差异,以及国内从 Zabbix 到 Prometheus 再到 OTel 的典型迁移路径与工程坑点。
【可观测性工程】Metrics:Prometheus、VictoriaMetrics、Thanos、Mimir、M3
从 Prometheus 架构与数据模型出发,系统梳理 Remote Write、PromQL 进阶、Thanos 全局聚合、Mimir 多租户、VictoriaMetrics 性能、M3DB 原理,以及五者在大规模生产场景下的对比矩阵与迁移实践。