
1. 为什么我用ClickHouse重做日志分析先聊聊背景和选型做后端运维和平台开发这么多年日志分析这块我前前后后折腾过不少方案。早期用过ELK那一套Elasticsearch确实好用模糊搜索、聚合统计都很顺手但到了数据量涨起来之后成本压力就非常明显了——ES的内存和磁盘开销相当“豪华”三台机器扛了半年堆内存频频告警索引分片一多写入性能也跟着抖。后来也试过Loki轻量是真轻量但它的索引能力太弱想按某个字段快速过滤、按时间范围做复杂聚合基本不太行更多是“看日志”而不是“分析日志”。真正让我下定决心切到ClickHouse是因为一个具体的业务场景。我们当时要做一个统一日志平台接入来源包括Nginx访问日志、后端服务打印的业务日志、Tomcat的catalina.out还有Windows服务器上的系统日志和应用日志。每天新增的日志量大概在80亿到120亿条之间高峰期写入速度要求每秒至少50万条。查询侧的需求也很明确按时间范围、服务名、机器IP、日志级别这几个维度做组合过滤然后对请求量、错误率、响应时间分位数做分钟级的聚合统计。说实话这个量级和查询模式ES已经有点吃力了Loki更是直接出局。ClickHouse的列式存储、极致的压缩比、向量化执行引擎简直就是为这种“高吞吐写入宽表聚合分析”的活量身定做的。我用一台16核64G的机器做POC测试导入10亿行Nginx日志存储占用不到90GB这个压缩比在同量级数据下ES至少得翻三倍。聚合查询方面按分钟分组算平均值和分位数大部分查询响应都在200毫秒以内这性能表现确实惊艳。这篇文章就围绕我实际落地ClickHouse做日志分析平台的完整过程来写从部署环境、表结构设计、数据入库链路到查询优化、可视化对接再到运维过程中碰到的各种坑一次性讲清楚。如果你也在纠结“日志太多了怎么办”“ES太贵了能不能换”那这篇文章应该能给你一些参考价值。提示ClickHouse适合的是“大规模、结构化程度较高、以聚合分析为主”的日志场景。如果你的日志格式极度不规则或者你主要做全文检索比如“搜日志里包含某个关键字的原始内容”那ClickHouse不一定是最优选这点后面会详细说。2. 环境准备与集群部署我建议从单机验证开始很多人一上来就奔着集群去了觉得分布式才显得“专业”。我的建议恰恰相反——先老老实实把单机版跑通把表结构、写入链路、查询语句这些核心逻辑验证好再考虑扩集群。原因很简单ClickHouse单机的性能已经足够强大部分中等规模的日志平台日增百亿条以内单机甚至双副本就能扛住一上来就搞集群只会把问题复杂度拉高好几倍。2.1 操作系统与安装方式的选择我实际部署的环境有两套一套测试环境是Ubuntu另一套生产环境用的是Rocky Linux 9这也符合大多数公司的现状——测试和生产往往不是一个系统。ClickHouse官方对这两个系统都提供了良好的支持安装方式也很简单我习惯用官方RPM/DEB源来装方便后续用systemctl管理。先说一下Rocky Linux 9上的安装步骤# 添加官方仓库 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo # 安装服务端和客户端 sudo yum install -y clickhouse-server clickhouse-client # 启动并设置开机自启 sudo systemctl enable clickhouse-server sudo systemctl start clickhouse-server # 验证服务状态 sudo systemctl status clickhouse-serverUbuntu上用DEB包思路一致# 添加官方仓库 sudo apt-get install -y apt-transport-https ca-certificates dirmngr sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4 echo deb https://packages.clickhouse.com/deb stable main | sudo tee /etc/apt/sources.list.d/clickhouse.list sudo apt-get update # 安装服务端和客户端 sudo apt-get install -y clickhouse-server clickhouse-client # 启动并设置开机自启 sudo systemctl enable clickhouse-server sudo systemctl start clickhouse-server关于“最新版下载”这个事多说一句。ClickHouse的版本迭代非常快我建议安装当前最新的稳定版stable而不是latest版本。latest一般是预发布版本虽然功能新但稳定性风险高生产环境没必要当小白鼠。2.2 关键配置项内存、并发和存储路径安装完之后默认配置文件在/etc/clickhouse-server/config.xml我通常会重点调整几个参数。!-- 允许通过网络访问默认只监听本机 -- listen_host0.0.0.0/listen_host !-- 并发线程数一般设置为CPU核数不要盲目调大 -- max_thread_pool_size16/max_thread_pool_size !-- 单次查询最大内存限制防止大查询拖垮整个服务 -- max_memory_usage10000000000/max_memory_usage !-- 数据存储路径建议单独挂载数据盘不要和系统盘共用 -- path/data/clickhouse//path !-- 临时数据路径用于排序和聚合的中间结果 -- tmp_path/data/clickhouse/tmp//tmp_path内存这块我踩过一次坑。早期我总觉得“内存越大给ClickHouse的越多越好”结果把max_memory_usage调得特别高导致一个聚合大查询直接把内存吃满其他所有查询全部超时。后来我学乖了这个值一般控制在物理内存的50%-60%比较稳妥剩下的留给系统缓存和其他组件。存储路径这块要特别注意ClickHouse对磁盘IO的要求很高。如果条件允许数据目录放到独立的NVMe SSD上写入性能和查询响应都会有质的提升。机械硬盘不建议用于生产日志分析场景IO会成为明显瓶颈。另外tmp_path也建议放到SSD上排序和聚合的中间数据落盘时快很多。2.3 从单机到多副本什么时候需要上集群ClickHouse的集群架构分为两层分片shard和副本replica。分片解决的是数据量水平扩展的问题比如单机磁盘快满了就把数据分散到多台机器上副本解决的是高可用问题一个分片的数据存多份某台机器挂了不影响查询。根据我的实践经验判断是否需要上集群可以从两个维度来评估数据量维度单机磁盘存储已经超过总容量的60%且增长趋势不减这时候考虑分片扩展。可用性维度日志平台是核心监控设施不允许宕机窗口至少需要配置双副本。如果你是双副本架构ClickHouse内置的ReplicatedMergeTree引擎会基于ZooKeeper或ClickHouse Keeper来协调数据同步。我配置集群时用的三套ClickHouse节点加三套ClickHouse Keeper轻量级替代ZooKeeper的方案架构还算清爽。不过说实话初期单机加定期冷备也能撑住大多数场景等到规模真上来了再平滑扩展也不迟。3. 日志数据模型设计表引擎选型是成败关键日志分析场景下表结构设计的重要性怎么强调都不为过。用ClickHouse写SQL和MySQL差别不大但如果不懂它的存储引擎原理很容易设计出一张“看起来没问题、用起来卡到爆”的表。3.1 MergeTree家族从基础到专用因地制宜日志分析最常用的就是MergeTree家族我按实际使用频率给它们排个序MergeTree最基础的表引擎支持主键、分区、数据TTL适合通用的日志存储分析场景。如果你的日志数据不需要副本用这个就够了。ReplacingMergeTree适合需要对同一主键去重的场景。比如你从消息队列里读取日志可能因为重复投递导致同一条日志被写入了多次用这个引擎可以在后台合并时按版本号去重。ReplicatedMergeTree带副本机制的引擎底层是MergeTree通过ZooKeeper/Keeper同步多个副本的数据。SummingMergeTree适合预先聚合的场景。比如你只需要按分钟统计每个服务的请求量、错误量可以提前把明细数据聚合成汇总行大大降低存储成本和查询耗时。我实际用的最多的是ReplicatedMergeTree因为生产环境要高可用。但这里有个需要注意的点如果你的集群只有单节点或者暂时不考虑副本直接用MergeTree不要给表加副本机制否则会引入Keeper依赖和额外的同步开销。3.2 日志表结构设计字段、分区与排序键日志数据有个特点——写多读少、按时间聚合、按维度过滤。表设计要围绕这个特点来。我们以Nginx访问日志为例这是我设计的表结构CREATE TABLE default.nginx_access_log ( log_time DateTime64(3) COMMENT 日志时间, host String COMMENT 客户端IP, request_method String COMMENT 请求方法, request_uri String COMMENT 请求URI, request_protocol String COMMENT 请求协议, status UInt16 COMMENT HTTP状态码, body_bytes_sent UInt64 COMMENT 响应体大小, request_time Float32 COMMENT 请求耗时秒, upstream_response_time Float32 COMMENT 上游响应耗时秒, user_agent String COMMENT User-Agent, referer String COMMENT 来源页面, server_name String COMMENT 域名或服务名, remote_port UInt16 COMMENT 客户端端口, timestamp DateTime64(3) DEFAULT now64(3) COMMENT 写入时间 ) ENGINE ReplicatedMergeTree(/clickhouse/tables/{shard}/nginx_access_log, {replica}) PARTITION BY toYYYYMMDD(log_time) ORDER BY (log_time, host, server_name, status) TTL toDateTime(log_time) INTERVAL 30 DAY SETTINGS index_granularity 8192;简单解释几个关键设计点分区键用toYYYYMMDD(log_time)也就是按天分区。这样做的直接好处是如果只需要查询最近7天的数据ClickHouse可以快速跳过其他分区的数据只扫描这7个分区的文件查询效率大幅提升。但分区粒度也不能太细如果按小时分区分区文件数量会暴增后台合并任务会看不过来反而拖慢性能。按天是大多数日志场景的黄金平衡点。排序键是(log_time, host, server_name, status)这个设计很多人容易忽略。ClickHouse的排序键直接影响数据的物理存储顺序查询时如果能利用排序键做二分查找效率会高得多。我们的查询模式通常是“先定位到某个时间范围再按机器IP、服务名、状态码过滤”所以排序键的顺序也按这个查询频率来排。TTL设置30天过期数据自动删除省去手动清理的麻烦。日志平台一般保留30天明细更久远的数据要么转存对象存储要么只保留聚合结果。3.3 分区粒度估算不要盲目按天上面说按天分区是默认选择但具体情况还是要做点数学题的。分区的合适大小大概在100MB到500MB之间。如果分区太小比如单天数据量只有几十MB会产生大量小分区文件合并线程忙不过来查询也要打开很多文件如果分区太大比如单天几个GB那么查询一天的数据就要扫描大量数据跳过无用数据的效果就差了。估算方法很简单统计每天的原始日志行数估算单行日志的大小然后算一下单天的数据量。假设单行Nginx日志平均500字节一天5000万行一天的数据量5000万 * 500B 25GB这个体量按天分区完全没问题。再比如一天只有10万行单天数据量不到5MB按天分区就太碎了这时候可以考虑按周甚至按月分区。3.4 Windows和Tomcat日志的建模差异热搜词里也提到了Windows日志分析和Tomcat日志分析这两个我都实际做过建模思路略有不同顺便展开说说。Windows日志一般分为系统日志、应用程序日志、安全日志通过Logstash或Fluent Bit采集后字段比较固定比如EventID、Level、ProviderName、Message等。建模的关键点是EventID和Level这两个字段一定要单独存成数值类型不要和Message混在一起这样过滤和统计会快很多。Message字段是全文内容不需要单独建全文索引ClickHouse用ilike做模糊匹配已经够用。Tomcat日志有两种典型格式catalina.out和access log。catalina.out是运行日志就是Java进程打印的堆栈、异常、业务日志格式很不规整我的建议是整体存成一个message字段配合timestamp、日志级别、服务名几个维度字段即可。Tomcat的access log就很规整通常有remote_ip、request_uri、status、bytes_sent、request_time这些字段设计表结构和Nginx基本一个套路。4. 数据写入链路从采集到ClickHouse的完整管道表结构设计好了接下来要解决“日志怎么进到ClickHouse”的问题。这里选择太多但核心要把握一个原则不管中间用什么组件最终写入ClickHouse的SQL一定要是批量插入每一批建议在1万行以上否则性能完全发挥不出来。4.1 常见采集方案对比Logstash vs Fluent Bit vs Vector我在不同阶段用过不同的采集链路逐一说说实际感受。Logstash最重但最强。输入输出插件极多过滤能力强悍。缺点是内存占用大、配置复杂。我们早期用它采集20个采集通道轻轻松松吃掉2个G内存后来数据量大了不得不换。Fluent Bit轻量级采集器内存占用常年在10-20MB之间适合部署在每台业务机器上。C语言编写性能很好插件生态虽然不如Logstash丰富但覆盖Nginx、Tomcat、Windows事件日志这些常见场景绰绰有余。我现在的主力采集器就是它。VectorRust编写的采集器性能和内存表现都不错最舒服的是它的配置语法非常清晰管道处理逻辑写起来像写代码一样直观。如果你团队的采集器运维能力比较强Vector是个好选择。我的推荐方案是Fluent Bit做边缘采集Kafka做消息缓冲ClickHouse官方提供的clickhouse-kafka引擎或自研消费程序负责最终入库。Kafka这层非常重要它能削峰填谷防止ClickHouse因为瞬时写入洪峰导致合并任务堆积或者查询变慢。4.2 核心入库方式批量写与异步写的取舍ClickHouse有几个写入通道各有适用场景HTTP接口官方提供的http://host:8123接口支持INSERT INTO table FORMAT JSONEachRow之类的格式适合程序直接写入。clickhouse-client命令行适合离线导入、批量初始化。JDBC驱动适合Java系程序写入。Kafka引擎表直接把Kafka topic映射成一张ClickHouse表通过物化视图把数据写入真正的明细表配置起来最省事也是我目前生产环境用的方案。用Kafka引擎表入库有一个好处——天然支持批量消费一个批次默认拉取几千条再写入不像HTTP接口需要自己攒批。下面是Kafka引擎表的配置示例-- 先创建Kafka引擎表对应Kafka中的原始消息 CREATE TABLE default.nginx_access_log_kafka ( message String ) ENGINE Kafka() SETTINGS kafka_broker_list kafka1:9092,kafka2:9092, kafka_topic_list nginx_access_log, kafka_group_name clickhouse_nginx_consumer, kafka_format JSONAsString, kafka_max_block_size 65536; -- 通过物化视图将Kafka表的数据解析后写入真正的明细表 CREATE MATERIALIZED VIEW default.nginx_access_log_mv TO default.nginx_access_log AS SELECT JSONExtractString(message, log_time) AS log_time, JSONExtractString(message, host) AS host, JSONExtractString(message, request_method) AS request_method, ... FROM default.nginx_access_log_kafka;这个方案的好处是写入路径短、无需额外的消费者代码只要Kafka里的数据格式稳定ClickHouse就能持续消费并写入。需要注意一点Kafka引擎表的消费是自动的如果Kafka里的数据量远大于ClickHouse的处理能力消费延迟会越来越大。这时候就需要消费者性能监控了。如果不用Kafka直接走HTTP接口我一般会用Python写好批量写入脚本控制批次大小import requests import json def batch_insert_to_clickhouse(rows: list[dict], table: str): 将一批日志行批量写入ClickHouse rows: 字典列表 # 拼接成JSONEachRow格式 payload \n.join(json.dumps(row, ensure_asciiFalse) for row in rows) # 这里用gzip压缩日志文本重复度高压缩率很可观 resp requests.post( fhttp://clickhouse:8123/, params{query: fINSERT INTO {table} FORMAT JSONEachRow}, datapayload.encode(utf-8), headers{Content-Encoding: gzip}, timeout30 ) if resp.status_code ! 200: raise Exception(fInsert failed: {resp.text})批量大小我一般压到1万到5万行一批太大容易导致单次请求内存暴涨太小则写入频率太高、效率上不去。4.3 数据解析的几种路径SQL函数解析在入库前还是入库后日志原始格式多种多样有JSON的、有键值对的、有任意文本的。解析逻辑放在哪一层是一个需要琢磨的问题。我的原则是格式越规整解析越靠前格式越随意解析越靠后。如果是服务端主动推送的JSON日志建议在Fluent Bit或Logstash阶段就解析好字段以JSON格式发到KafkaClickHouse侧用JSONEachRow直接写入解析开销最小。如果是Tomcat的catalina.out这种非结构化文本建议原始文本先入库查询时再用ClickHouse的字符串函数按需解析。这种方式存储上会多一点开销但胜在灵活后续想怎么拆字段都行不会因为入库时解析错了还得重新回放数据。ClickHouse的解析函数确实能打常用的JSONExtractString、splitByChar、extractAllGroupsHorizontal之类的性能都不错。但需要注意的是每次查询都要解析一遍如果数据量大查询耗时会显著增加。对于高频查询的字段最好还是入库时就拆好不要依赖查询时现算。4.4 写入性能调优的几个关键参数写入性能始终是日志平台的重点。除了批量插入之外还有几个参数我也踩过不少坑整理出来供参考max_insert_block_size默认是1048576行但实际使用中插入块过大可能会导致内存暴涨。我一般设为65万行左右。max_threads写线程数默认是CPU核数没必要调很高写入性能瓶颈通常在网络和磁盘IO上。index_granularity默认8192行意思是每8192行建一个索引标记。日志表如果单行很长可以适当调小到4096因为大字段会放大索引文件体积。如果单行很短则可以调大到16384减少索引文件数量。parts_to_throw_insert当数据分区碎片数量过多时ClickHouse会拒绝新数据写入防止文件数量无限膨胀。如果高峰期写入快、分区多适当调大这个值可以避免写入被拒但也有风险不建议盲目调。5. 查询优化实战让日志分析快起来的几个关键技巧部署好了、数据也进去了接下来就是你每天都要面对的日常——写查询、看报表、排查问题。日志分析场景中的查询和传统OLTP查询有着本质区别它追求的是扫描大数据集时的高吞吐和低延迟而不是单条记录的点查。因此查询优化思路也要跟着变。5.1 聚合统计的正确姿势提前用物化视图日志分析中“实时聚合”是最高频的需求。比如实时统计最近5分钟每个服务的请求量、错误率、P99耗时。如果每次查询都直接扫描原始明细表做group by数据量一大就撑不住。我的做法是用物化视图把汇总结果实时计算出来查询时直接查汇总表。ClickHouse物化视图就像是一个“预计算器”数据写入明细表时它会自动把聚合结果更新到另外一张表里。以“按分钟统计请求量、错误量、平均响应时间”为例-- 创建聚合结果表 CREATE TABLE default.nginx_access_log_agg_minute ( log_time DateTime COMMENT 统计时间分钟粒度, server_name String COMMENT 服务名, status_group String COMMENT 状态码分组2xx/3xx/4xx/5xx, request_count UInt64 COMMENT 请求量, error_count UInt64 COMMENT 错误量4xx5xx, avg_request_time Float64 COMMENT 平均响应时间, p95_request_time Float64 COMMENT P95响应时间, p99_request_time Float64 COMMENT P99响应时间 ) ENGINE SummingMergeTree() PARTITION BY toYYYYMMDD(log_time) ORDER BY (log_time, server_name, status_group); -- 创建物化视图实时计算并写入聚合表 CREATE MATERIALIZED VIEW default.nginx_access_log_agg_minute_mv TO default.nginx_access_log_agg_minute AS SELECT toStartOfMinute(log_time) AS log_time, server_name, multiIf(status 500, 5xx, status 400, 4xx, status 300, 3xx, 2xx) AS status_group, count() AS request_count, countIf(status 400) AS error_count, avg(request_time) AS avg_request_time, quantile(0.95)(request_time) AS p95_request_time, quantile(0.99)(request_time) AS p99_request_time FROM default.nginx_access_log GROUP BY log_time, server_name, status_group;这套方案好处是明细数据的聚合是增量计算的查询时只需扫聚合表里很少的数据毫秒级返回不是问题。当然代价是占用额外的存储空间和写入CPU成本但日志场景下这个代价完全值得。5.2 常见查询模板按时间范围过滤、状态码统计、Top N接口耗时聚合统计查汇总表详细排查还是要查明细表。下面是我日常最常用的几个查询模板几乎覆盖了日志分析的绝大多数场景。查询某段时间内所有服务请求量和错误率SELECT server_name, count() AS total_count, countIf(status 400) AS error_count, round(error_count / total_count * 100, 2) AS error_rate_percent FROM default.nginx_access_log WHERE log_time now() - INTERVAL 15 MINUTE AND log_time now() GROUP BY server_name ORDER BY total_count DESC LIMIT 20;查询最近1小时最慢的20个接口SELECT request_method, request_uri, server_name, quantile(0.99)(request_time) AS p99_time FROM default.nginx_access_log WHERE log_time now() - INTERVAL 1 HOUR GROUP BY request_method, request_uri, server_name ORDER BY p99_time DESC LIMIT 20;按IP维度统计访问频次识别异常来源SELECT host AS client_ip, count() AS request_count, uniqCombined(server_name) AS unique_services, max(log_time) AS last_access_time FROM default.nginx_access_log WHERE log_time now() - INTERVAL 1 DAY GROUP BY client_ip ORDER BY request_count DESC LIMIT 50;这里有几个函数值得关注。uniqCombined是ClickHouse专门为大数据量去重统计设计的比count(DISTINCT)快得多误差也在可接受范围内。quantile系列函数用于分位数计算日志分析里的P95、P99靠它来实现。5.3 全文检索怎么做ilike和match的取舍很多从ES迁移过来的同学最不适应的就是全文检索能力。ClickHouse没有倒排索引新版本其实已经加了实验性支持但用对函数很多场景依然能应付。短文本模糊搜索优先用ilikeSELECT count() FROM default.nginx_access_log WHERE request_uri ILIKE %/api/v1/order% AND log_time now() - INTERVAL 10 MINUTE;ilike是大小写不敏感的模糊匹配ClickHouse对它做了优化在排序键前缀匹配的情况下能走索引加速。如果模糊匹配的字段是request_uri这种高频查询字段可以考虑把它加入排序键的靠前位置效果会明显。复杂正则匹配用match但要注意性能SELECT count() FROM default.nginx_access_log WHERE match(request_uri, ^/api/v[0-9]/order/\\d$) AND log_time now() - INTERVAL 10 MINUTE;match走的是正则引擎性能开销远大于ilike在大数据集上做全表正则扫描CPU一下就打满了。我一般只在ilike满足不了需求的场景才用正则。如果你想做真正的全文检索比如搜索Message字段里的任意关键词ClickHouse其实也提供了基于ngram的倒排索引需要在建表时显式声明INDEX idx_message message TYPE ngrambf_v1(4, 100000, 0, 0) GRANULARITY 4这个索引适合“包含某个词”的高频查询原理是提前生成ngram布隆过滤器查询时先过滤大部分不相关的数据块再对剩余数据做精确匹配。效果比不上ES的全文索引但在日志关键词过滤场景下性能提升还是相当明显的。6. 可视化与告警日志平台最后一块拼图日志分析不只是写SQL查数最终还是要展示给别人看、要能在异常时第一时间通知到人。这一块我用的方案是Grafana加Alertmanager原因很简单社区插件成熟、配置灵活、和ClickHouse数据源对接顺畅。6.1 Grafana对接ClickHouse的配置要点Grafana对接ClickHouse推荐用官方插件clickhouse-datasource。安装好插件、配置好数据源之后最关键的是查询语言的写法。Grafana的ClickHouse数据源支持两种查询模式SQL模式和Table模式。我一般用SQL模式直接写聚合SQL然后绑定时间字段。需要注意的一个坑是Grafana的模板变量和ClickHouse的语法要配合好例如SELECT $timeSeries AS t, count() AS request_count FROM default.nginx_access_log WHERE log_time $timeFilter AND server_name IN ($server_names) GROUP BY t ORDER BY t;这里的$timeSeries、$timeFilter、$server_names都是Grafana的宏变量会被自动替换为实际的时间范围和选中的服务名。设置好之后就能在Dashboards上看到按时间序列动态刷新的流量曲线和状态码分布了。6.2 常用监控面板搭建思路我搭建的日志监控面板包含这样几个核心图表每个都对应一类实际问题QPS和错误率总览按分钟展示总请求量、各服务请求量、4xx/5xx错误率一眼看出整体健康状况。接口Top N耗时按P99耗时排序展示最慢的接口定位性能瓶颈。IP访问Top榜按请求量排序的客户端IP辅助发现异常访问和攻击行为。状态码饼图展示当前时间窗口内2xx/3xx/4xx/5xx状态码占比快速判断流量质量。Tomcat/Windows系统日志级别分布展示error/warn/info级别分布Java堆栈异常和系统错误一目了然。6.3 告警规则怎么配才不“狼来了”告警配置最大的坑就是“告警风暴”。配置太松出了问题没人看配置太紧天天被噪音打扰最终大家选择性忽略。我的经验是分层级配置紧急告警页面/电话比如5xx错误率连续5分钟超过5%、P99响应时间连续5分钟超过2秒、某个服务写入量突然降为0。警告告警IM群比如错误率超过2%持续10分钟、请求量较基线突降30%等。信息告警日报汇总每天汇总各项指标不实时打扰。Grafana Alerting的规则配置比较直观选择查询、设置阈值、配置通知渠道即可。建议大家一定要设定“持续多长时间才触发”这个条件避免瞬时抖动引起误报。7. 运维踩坑实录那些官方文档没写明白的坑这部分是全文最有价值的部分也是我踩坑踩得最多的地方。每一个问题都是真实遇到、花了不少时间排查才解决的希望能帮你少走弯路。7.1 重启报错failed to flush system log already exists这个报错是热搜词里提到的说明遇到的人不少。我第一次遇到是在一次版本升级后的重启操作中当时看到这个报错一脸懵DB::Exception: Cannot flush system log, because flush failed: Code: 57. DB::Exception: Table default.system.query_log already exists.查了一圈原因是ClickHouse的系统日志表query_log、query_thread_log等在重启恢复时尝试重建一个已经存在的同名表对象导致冲突。这个问题的根源通常是系统表元数据损坏或版本升级后残留数据不一致。我的处理办法是-- 在另一个未受影响的节点或从其他客户端连接先尝试删除系统日志表对应的物理文件 -- 实际上更安全的做法是备份数据文件后执行 DETACH TABLE system.query_log; ATTACH TABLE system.query_log;如果DETACH/ATTACH操作不成功就直接找系统表对应的目录通常在/data/clickhouse/data/system/下备份后删除query_log相关目录再重启服务。重启后ClickHouse会自动重建这些系统表问题就解决了。不过这里要提醒一下操作前一定要做好数据目录的备份系统表虽然不存业务数据但误删可能导致元数据不一致引发更大的问题。7.2 磁盘空间告急日志TTL为什么不生效我们上线初期遇到过一个诡异问题明明表里配置了TTL磁盘空间却仍然持续上涨一度把数据盘给撑满了。排查之后发现TTL只在数据合并merge之后才真正删除过期分区里的数据而当时因为写入量太大、合并任务一直被推迟导致过期数据迟迟未被物理清除。解决方法是手动触发合并或者降低后台合并任务的优先级门槛-- 强制触发分区合并清理过期数据 OPTIMIZE TABLE default.nginx_access_log FINAL;OPTIMIZE TABLE ... FINAL是强制合并所有分区把过期的TTL数据物理删除。需要注意的是这个操作比较重如果表很大的话会占用不少CPU和IO最好在业务低峰期执行不要频繁使用。另一个经验是TTL清理有滞后性磁盘规划时要留足余量。我一般会预留30%左右的空闲空间用于应对TTL滞后和合并临时文件的空间开销。7.3 Too many parts 异常的应对策略存储到一定量之后你可能会在日志里看到类似这样的错误DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts.这个报错表示某个分区下的小文件数量parts超过阈值了ClickHouse拒绝新的写入以防文件数量继续膨胀。原因通常是写入频率太高、或者分区选择不当比如按小时分区但每个小时数据量少导致产生大量小parts而合并的速度跟不上插入速度。我的处理措施有几步降低写入频率把批量从每批1万行提升到每批5万行减少part数量。调整分区粒度如果按小时分区改为按天分区减少分区总数。调大合并线程数在配置文件里调整background_pool_size参数增加后台合并任务的并发能力。临时措施调高parts_to_throw_insert阈值至少保证数据能写进去但这是临时方案治标不治本。最终根子上的解法还是控制分区数量和批量插入行数让part生成速度慢于合并速度这个平衡关系要配合监控来调。7.4 高峰期查询超时内存不够和CPU被打满怎么办日志平台上线一个月后问题开始暴露在查询侧。每天10:00和14:00的流量高峰dashboard加载很慢有的聚合查询直接超时。排查后发现有些BI同事写的SQL没有加时间范围条件直接对全表数据跑了一次深度聚合把CPU和内存都打满了。我的解决方式是从两个层面入手。第一在ClickHouse用户配置中限制单次查询的资源消耗将大查询隔离到独立的资源池profiles default max_memory_usage8000000000/max_memory_usage max_execution_time60/max_execution_time /default readonly max_memory_usage2000000000/max_memory_usage max_execution_time20/max_execution_time /readonly /profiles第二通过Grafana的查询编辑规范强制要求所有可视化查询必须绑定时间范围从源头上避免全表扫描。配合ClickHouse的system.query_log表定期分析慢查询把高频次、高耗时的SQL整理出来逐条优化。7.5 多节点集群数据倾斜分片键选择要慎重上集群之后我们又遇到一个头疼的问题三个分片的磁盘使用率不一样其中一个已经70%另外两个才30%。查了一圈问题出在分片键的选择上。我们之前用rand()作为分片键理论上数据是均匀分配的但实际场景中某个大客户的服务日志量特别大rand()虽然能保证行数均匀却不能保证“数据大小”均匀——大字段日志会集中在某些行里。解决办法是把分片键改为cityHash64(server_name)按服务名做哈希分片这样同一个服务的日志会落到同一个分片数据量的大小分布就会均衡很多。当然了这样也会带来一个新问题单个服务日志量暴增时对应的分片会首先变热。这个就需要根据业务情况权衡没有任何一种分片策略是万能的。8. 日志分析场景的横向对比ClickHouse、ES和Loki怎么选关于选型最后用一点篇幅做个总结性对比。很多朋友问“ClickHouse是不是能完全替代ES”我个人观点是在日志分析这个细分领域ClickHouse确实能替代大部分ES场景但并不能在所有场景下完全替代。先从几个维度做个客观对比维度ClickHouseElasticsearchLoki存储引擎列式存储压缩比高倒排索引DocValues存储开销大对象存储索引轻量设计写入性能极高批量写入可达百万行/秒中等受限于索引构建开销高依赖对象存储聚合分析极强向量化执行引擎中等聚合深了容易OOM较弱主要面向日志检索全文检索较弱支持有限极强核心能力所在标签过滤较强正文检索较弱运维成本较低单机能力突出较高集群和内存调优复杂低适用位置结构化日志、指标分析非结构化全文检索、业务搜索轻量日志查看、云原生环境从实用性角度来说如果你需要“全文搜原始日志内容”的场景非常多比如“搜索所有日志里包含某个用户ID或错误堆栈的原始内容”那ES依然是更好的选择倒排索引在这种场景下的优势是ClickHouse目前无法取代的。但如果你更关心的是“有多少请求、错误率多高、哪些接口最慢、哪个IP在频繁访问”——也就是所有基于结构化字段的聚合统计分析ClickHouse是明显更优的选型。性能和成本都是碾压级的优势。Loki则更偏向“只需要能看日志、不需要复杂分析”的轻量场景尤其适合云原生和Kubernetes环境下成本要求敏感、有对象存储可用的情况。所以我最终的选型建议是日志量小、查询简单直接用Loki或Elasticsearch怎么方便怎么来。日志量大、分析需求为主ClickHouse尤其是本文介绍的表结构和查询方案。日志量大且全文搜索需求很强可以考虑ClickHouseES混合方案热数据进ES供搜索全量数据进ClickHouse供聚合分析。虽然复杂一点但各有侧重、各得其所。我是一个实用主义者选型只信一条用最合适的工具解决当前最重要的问题。ClickHouse在日志分析领域的生态还在不断完善但它作为“大数据量下的日志聚合分析引擎”这个定位已经非常成熟可靠了。如果你正在日志分析的技术选型上犹豫不妨先用本文的方案搭一套POC拿自己真实的日志数据跑一跑数值会替你做出最后的决定。