Kratos直连ClickHouse写入易崩,因默认同步小批量插入导致Too many parts、ZooKeeper压力大;须开启async_insert与wait_for_async_insert、客户端攒批写入、合理配置表结构与参数,并用本地持久化队列防重启丢数。

Kratos 微服务直接连 ClickHouse 写数据,不加缓冲层、不调参数、不改写入模式,基本等于给服务埋雷——写入慢、报 Too many parts、ZooKeeper 压力飙升、磁盘 IO 扛不住,都是分分钟的事。
为什么 Kratos 默认写法在 ClickHouse 上容易崩
Kratos 本身不约束数据写入方式,但多数团队用 database/sql + clickhouse-go 或 JDBC 封装一层,走标准 INSERT INTO ... VALUES。问题就出在这里:
- 默认每条
Exec调用都触发一次同步插入,哪怕你Prepare了,底层仍可能按行或小批量提交(取决于 driver 实现和参数); -
clickhouse-go的Stmt.Exec在未显式启用async_insert=1时,仍是同步落 part,每批 → 一个 part → 后台 merge 压力翻倍; - Kratos 的 gRPC 接口常被高频调用(比如每秒几百次埋点上报),天然匹配“小批量、高频率”反模式,而 ClickHouse 最怕这个;
- 没配
wait_for_async_insert=1时,异步插入返回成功不代表数据已落盘,后续查不到还查不出错,排查黑洞。
必须开的两个服务端开关:async_insert 和 wait_for_async_insert
ClickHouse 26.2+ 版本起,async_insert 默认已是 1,但很多团队没确认版本或没在连接串/SESSION 中显式开启,导致实际没生效。关键不是“开不开”,而是“怎么开、开在哪”:
- 连接字符串里加参数:
?async_insert=1&wait_for_async_insert=1&async_insert_busy_timeout_ms=5000; - 或者在每次写入前执行:
SET async_insert = 1, wait_for_async_insert = 1, async_insert_busy_timeout_ms = 5000; -
wait_for_async_insert = 1是底线:它让客户端阻塞直到数据真正刷入磁盘(或超时),避免“写成功却查不到”的幻觉; - 别信
async_insert_use_adaptive_busy_timeout默认值——它会动态拉长超时,在高负载下可能卡住 Kratos 请求线程,固定设个5000更可控。
客户端写入代码必须绕过“自动拆批”,自己攒够再发
Kratos 里常见错误是把业务层的单条数据直接丢进 DAO 层 Insert 方法,DAO 再套一层 tx.Stmt.Exec。这等于把 ClickHouse 当 MySQL 用,必垮:
- 用
clickhouse-go时,别依赖db.Exec自动批量——它不会合并多条语句; - 必须在业务逻辑或中间件层做“攒批”:比如用
sync.Pool缓存[]interface{}切片,按时间(如 100ms)或大小(如 10000 行)触发 flush; - 插入时用
input()函数绕过 SQL 解析瓶颈:INSERT INTO t SELECT * FROM input('a String, b UInt64') FORMAT RowBinary,比拼接 SQL 快 3–5 倍; - 如果用 JDBC(比如 Kratos 集成 Spring Boot),禁用 MyBatis-Plus 的
saveBatch——它内部按 4MB 切割,极易拆成几十个小批;改用原生PreparedStatement+addBatch+executeBatch,并确保rewriteBatchedStatements=true生效。
表结构与集群配置不匹配,优化全白搭
就算代码全对,表引擎选错、分区键乱设、副本没配,照样写不快:
- 单机部署?别用
ReplicatedMergeTree,ZooKeeper 成瓶颈,直接上MergeTree; - 按天分区但数据跨天写入?
PARTITION BY toYYYYMMDD(event_time)+ORDER BY (event_time, user_id)是基础,但必须保证写入时event_time是有序的(比如用 Kafka 时间戳,而非服务本地时间); - 分布式表写入?确认
Distributed表的sharding_key是否导致热点(比如全写到同一个 shard); - 必须调的三个参数:
max_insert_threads(建议设为 CPU 核数 × 2)、max_partitions_per_insert_block(设为 100,防跨分区写放大)、background_pool_size(至少 16,merge 线程不够会积压 parts)。
最易被忽略的一点:Kratos 服务重启时,内存里未 flush 的异步插入批次会丢失。别指望 SYSTEM FLUSH ASYNC INSERT QUEUE 能救场——它只对当前 session 有效,且不能在服务退出 hook 里可靠调用。真正的解法是,在攒批层引入带持久化的本地队列(如 BadgerDB 或 RocksDB),写入前先落本地盘,再异步提交到 ClickHouse。


















