ClickHouse 部署方式、架构选型与提效工具手册
先隔离一条分析链路
第一次把 ClickHouse 接进项目时,不要从“能否跑 SQL”开始判断。真正要闭合的是一条分析链路:脱敏事件批量进入 MergeTree,排序键帮助查询跳过无关数据块,资源限制约束一次查询的影响,低权限账号只能看到被授权的 database,测试表最终能被受控清理。
ClickHouse 是面向分析的列式数据库,不是 MySQL 的透明替代品。列式存储、压缩、向量化执行和稀疏索引适合大批量追加与聚合扫描;data part、后台 merge、排序键和分区又决定了小批量写入、频繁更新和错误表模型为何会迅速放大成本。先在回环地址上运行一个可删除实例,观察这些对象,再讨论副本、分片、Keeper 与托管方案。
动手需要 Docker Engine 或 Docker Desktop、可用的 docker compose 和 curl。Native 协议可使用本机或容器内的 clickhouse-client。创建一个隔离实验目录,例如 your-project/,准备不含真实手机号、邮箱、订单号、IP 明细或用户行为的小样例,并为 HTTP 与 Native 协议预留宿主端口 8124、9001。真实地址、密码、云账号和 S3 key 不进入仓库或命令历史。
先确认 Docker 与端口状态:
docker version
docker compose version
docker ps --format "table {{.Names}}\t{{.Ports}}\t{{.Status}}"Windows:
netstat -ano | findstr ":8123"
netstat -ano | findstr ":8124"
netstat -ano | findstr ":9000"
netstat -ano | findstr ":9001"Linux / macOS:
lsof -i :8123
lsof -i :8124
lsof -i :9000
lsof -i :9001如果默认端口已被占用,继续使用 8124:8123 和 9001:9000,不要直接复用生产或共享实例。
镜像固定为 clickhouse/clickhouse-server:26.6.1.1193,避免 latest、head 或浮动系列标签改变重建结果。ClickHouse 的 生产版本选择说明 将发行包分为 stable 与 lts:stable 约每月发布,默认更适合持续升级团队;lts 每年两次并支持一年,适合升级窗口更保守的系统。标签只是起点,升级仍需让真实查询、写入、物化视图和 schema migration 在预生产通过,并检查 changelog 中的 backward-incompatible change。
自建 ClickHouse server 代码采用 Apache 2.0 许可,客户交付时仍要单独处理商标、驱动、Operator、备份工具和托管服务条款。官方容器提供 amd64 与 arm64 架构:amd64 需要 SSE3,arm64 需要 ARMv8.2-A 及 RCpc;较新的 Ubuntu 基础镜像还要求 Docker Engine 具备相应 seccomp 修复。团队应在实际 CI runner 和生产 CPU 上拉取并启动锁定 tag,不能把开发机能运行当成平台兼容证明。
容器使用显式用户和密码,不依赖 default 用户,也不设置 CLICKHOUSE_SKIP_USER_SETUP=1。HTTP 默认端口是 8123,Native TCP 默认端口是 9000;宿主映射只绑定 127.0.0.1。共享环境还要为用户、配额、settings profile、TLS 和网络入口分别建模。
| 入口 | 适合 | 不适合 | 必须确认 |
|---|---|---|---|
| 本机安装 | 学习服务进程、配置文件、客户端 | 团队统一模板 | 版本、配置目录、服务自启动、卸载 |
| Docker 单容器 | 个人最小验证、快速跑通 HTTP / Native 查询 | 共享长期实例、分布式能力验证 | 端口、用户密码、volume、清理 |
| Docker Compose | 项目本地依赖、初始化库表、脚本化验证 | 生产集群 | .env、init scripts、资源限制、healthcheck |
| clickhouse-local | 本地文件分析、CSV / Parquet 临时查询 | 长期服务和共享实例 | 数据路径、内存、导出 |
| 共享开发实例 | 多服务联调、重型数据样例、统一数据集 | 个人随意压测和 drop table | owner、database、配额、清理窗口 |
| 单分片多副本 | 基础生产高可用、读扩展 | 海量横向扩展 | ReplicatedMergeTree、Keeper、备份 |
| 多分片多副本 | 大规模明细数据和高吞吐分析 | 未完成 shard key / 查询评审的团队 | Distributed、shard、replica、Keeper、数据重平衡 |
| ClickHouse Cloud | 降低运维复杂度、弹性和托管 | 数据边界、成本和区域未确认 | 费用、备份、网络、权限、退出 |
| Kubernetes Operator | 平台化交付和集群内标准化 | 单项目临时依赖 | CRD、存储、Keeper、升级、恢复 |
Docker 单容器
为隔离容器创建独立网络:
docker network create te-clickhouse-dev启动:
docker run -d \
--name te-clickhouse-single \
--network te-clickhouse-dev \
-p 127.0.0.1:8124:8123 \
-p 127.0.0.1:9001:9000 \
-e CLICKHOUSE_USER=te_app \
-e CLICKHOUSE_PASSWORD=YOUR_STRONG_CLICKHOUSE_PASSWORD \
clickhouse/clickhouse-server:26.6.1.1193HTTP 验证:
curl --fail "http://te_app:YOUR_STRONG_CLICKHOUSE_PASSWORD@127.0.0.1:8124/?query=SELECT%201"Native 客户端验证:
docker exec -it te-clickhouse-single clickhouse-client \
--user te_app \
--password YOUR_STRONG_CLICKHOUSE_PASSWORD \
--query "SELECT version(), currentUser()"清理:
docker rm -f te-clickhouse-single
docker network rm te-clickhouse-dev单容器只证明服务能跑、协议能通、表能创建,不代表分布式表、复制、备份恢复、资源隔离已经准备好。
Docker Compose
项目模板建议放到明确目录:
dev-dependencies/
clickhouse/
compose.yaml
.env.example
config.d/
users.d/
init/01-schema.sql
data/events.jsonl
scripts/verify.sh
scripts/clean-dry-run.sh
scripts/clean-confirmed.sh.env.example:
CLICKHOUSE_VERSION=26.6.1.1193
CLICKHOUSE_USER=te_app
CLICKHOUSE_PASSWORD=YOUR_STRONG_CLICKHOUSE_PASSWORD
CLICKHOUSE_DATABASE=te_analytics
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
CLICKHOUSE_HTTP_PORT=8124
CLICKHOUSE_NATIVE_PORT=9001compose.yaml:
services:
clickhouse:
image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION}
container_name: your-project-clickhouse
environment:
CLICKHOUSE_USER: ${CLICKHOUSE_USER}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD}
CLICKHOUSE_DB: ${CLICKHOUSE_DATABASE}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: ${CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT:-1}
ports:
- "127.0.0.1:${CLICKHOUSE_HTTP_PORT:-8124}:8123"
- "127.0.0.1:${CLICKHOUSE_NATIVE_PORT:-9001}:9000"
volumes:
- clickhouse-data:/var/lib/clickhouse
- clickhouse-logs:/var/log/clickhouse-server
- ./init:/docker-entrypoint-initdb.d:ro
- ./config.d:/etc/clickhouse-server/config.d:ro
- ./users.d:/etc/clickhouse-server/users.d:ro
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test:
[
"CMD-SHELL",
"wget --quiet --tries=1 --spider http://127.0.0.1:8123/ping || exit 1"
]
interval: 10s
timeout: 5s
retries: 30
volumes:
clickhouse-data:
clickhouse-logs:启动:
docker compose --env-file dev-dependencies/clickhouse/.env \
-f dev-dependencies/clickhouse/compose.yaml \
up -d停止但保留数据:
docker compose --env-file dev-dependencies/clickhouse/.env \
-f dev-dependencies/clickhouse/compose.yaml \
down个人环境重置:
docker compose --env-file dev-dependencies/clickhouse/.env \
-f dev-dependencies/clickhouse/compose.yaml \
down -v共享环境不能随手 down -v,要先确认 owner、database、table、备份和清理窗口。
clickhouse-local
clickhouse-local 适合不启动常驻服务时分析本地 CSV / Parquet / JSONEachRow 文件:
clickhouse local \
--query "
SELECT service, count() AS total
FROM file('events.jsonl', 'JSONEachRow')
GROUP BY service
ORDER BY total DESC"它不是共享数据库,也不该伪装成生产 ClickHouse。若把 clickhouse-local 启成轻量 HTTP / TCP listener,必须绑定 loopback、配置用户,并明确每个连接的 session 边界。
环境变量
| 配置 | 示例 | 说明 |
|---|---|---|
CLICKHOUSE_VERSION | 26.6.1.1193 | 固定到已验证的具体 tag |
CLICKHOUSE_USER | te_app | 本地应用账号 |
CLICKHOUSE_PASSWORD | YOUR_STRONG_CLICKHOUSE_PASSWORD | 不提交真实值 |
CLICKHOUSE_DATABASE | te_analytics | 项目分析库 |
CLICKHOUSE_HTTP_PORT | 8124 | 宿主 HTTP 端口 |
CLICKHOUSE_NATIVE_PORT | 9001 | 宿主 Native 端口 |
config.d/ 合并 server、存储、网络和 Keeper 等配置,users.d/ 承载用户、profile 与 quota;SQL 创建的用户、角色和表结构则写入 ClickHouse 自己的访问控制或元数据。可重新加载的配置也要用新连接和 system table 验证,启动期配置按滚动重启处理;容器重启不会撤销 CREATE USER、ALTER TABLE、TTL 或 settings profile。每次变更都要保存旧配置与 SHOW CREATE 结果,并按“文件回退、DDL 逆向变更或恢复到新表”选择对应回滚方式。
初始化表
init/01-schema.sql:
CREATE DATABASE IF NOT EXISTS te_analytics;
CREATE TABLE IF NOT EXISTS te_analytics.event_demo
(
event_date Date DEFAULT today(),
event_time DateTime DEFAULT now(),
service LowCardinality(String),
event_type LowCardinality(String),
user_id UInt64,
request_id String,
cost_ms UInt32,
success UInt8,
tags Array(String)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_date)
ORDER BY (service, event_type, event_time, user_id)
TTL event_date + INTERVAL 30 DAY DELETE
SETTINGS index_granularity = 8192;为什么这样写:
PARTITION BY toYYYYMM(event_date) 避免按天或按用户过细分区。ORDER BY 先放常用过滤维度,再放时间,服务日志类查询能更容易跳过数据块。LowCardinality(String) 适合低基数字符串,不要给高基数字段乱用。
TTL 用于测试数据生命周期,不替代生产合规删除流程。
样例数据
data/events.jsonl:
{"service":"api","event_type":"request","user_id":1001,"request_id":"req-001","cost_ms":42,"success":1,"tags":["local","demo"]}
{"service":"api","event_type":"request","user_id":1002,"request_id":"req-002","cost_ms":180,"success":1,"tags":["local","slow"]}
{"service":"worker","event_type":"job","user_id":1003,"request_id":"req-003","cost_ms":900,"success":0,"tags":["local","error"]}event_date 和 event_time 由 server 在插入时填入当前值,样例文件不携带固定日历值。导入:
cat dev-dependencies/clickhouse/data/events.jsonl | \
curl --fail \
"http://te_app:YOUR_STRONG_CLICKHOUSE_PASSWORD@127.0.0.1:8124/?input_format_defaults_for_omitted_fields=1&query=INSERT%20INTO%20te_analytics.event_demo%20FORMAT%20JSONEachRow" \
--data-binary @-input_format_defaults_for_omitted_fields=1 要求 JSONEachRow 对缺失字段计算表上的默认表达式;漏掉它时,不应假设所有客户端和发行线都会得到同一默认值。导入后立即查询 min(event_date)、max(event_date) 和行数,确认数据进入预期分区且没有被 TTL 当成过期数据。
用正反实验观察排序键
先用三行样例闭合连接、导入、聚合、索引解释和 part 状态,再用一条与排序键不匹配的查询做反例。样例很小,不能拿耗时证明性能;这里要观察的是计划与状态字段如何变化。
设置变量:
export CLICKHOUSE_URL="http://te_app:YOUR_STRONG_CLICKHOUSE_PASSWORD@127.0.0.1:8124"连接:
curl --fail "${CLICKHOUSE_URL}/?query=SELECT%20version(),%20currentUser()"输出应包含固定镜像对应的版本号和 te_app。若显示 default,说明请求没有使用预期凭证;若连接成功但用户不对,不要继续执行建表或清理。
查看表:
curl --fail "${CLICKHOUSE_URL}/?query=SHOW%20TABLES%20FROM%20te_analytics"导入后聚合查询:
curl --fail --get "${CLICKHOUSE_URL}/" \
--data-urlencode "query=
SELECT
service,
event_type,
count() AS total,
round(avg(cost_ms), 2) AS avg_cost_ms
FROM te_analytics.event_demo
GROUP BY service, event_type
ORDER BY total DESC"样例的稳定结果是 api / request 共 2 行、平均 111,worker / job 共 1 行、平均 900。若计数翻倍,通常是初始化或导入脚本被重复执行;先按 owner 或测试表清理,不要把重复数据当成查询错误。
查看执行计划:
curl --fail --get "${CLICKHOUSE_URL}/" \
--data-urlencode "query=
EXPLAIN indexes = 1
SELECT *
FROM te_analytics.event_demo
WHERE service = 'api'
AND event_type = 'request'
AND event_date = today()"计划中的 PrimaryKey 条件应包含排序键前缀 service、event_type,并显示 granule 裁剪。接着把过滤条件改成只有 request_id = 'req-001':该字段不在排序键中,计划无法获得同等的主键裁剪。这条反例说明“字段存在”不等于“能跳过数据”,也为是否调整 ORDER BY、增加 projection 或接受扫描成本提供证据。
查看 parts:
curl --fail --get "${CLICKHOUSE_URL}/" \
--data-urlencode "query=
SELECT
database,
table,
partition,
count() AS parts,
sum(rows) AS rows
FROM system.parts
WHERE active AND database = 'te_analytics'
GROUP BY database, table, partition
ORDER BY parts DESC"清理测试数据:
curl --fail --get "${CLICKHOUSE_URL}/" \
--data-urlencode "query=DROP TABLE IF EXISTS te_analytics.event_demo"共享环境不要把 DROP TABLE 放进普通脚本。共享脚本必须先 dry-run 列出 database、table、row count、owner 和确认变量。
项目里建议保留:
dev-dependencies/clickhouse/
compose.yaml
.env.example
config.d/
users.d/
init/01-schema.sql
data/events.jsonl
scripts/verify.sh
scripts/clean-dry-run.sh
docs/dependency-setup.md
docs/analytics-contract.md应用连接原则:
默认使用环境变量提供连接地址、用户名和密码。本地开发优先连接 127.0.0.1:8124 或 127.0.0.1:9001,共享实例必须显式命名。写入用批量导入或批量 insert,不要逐行小写入。
建表、修改表、TTL、materialized view、projection 进入脚本和评审,不靠 GUI 手点。只读查询账号与写入账号分开。
Spring Boot / JDBC 连接参数:
CLICKHOUSE_JDBC_URL=jdbc:clickhouse://127.0.0.1:8124/te_analytics
CLICKHOUSE_USER=te_app
CLICKHOUSE_PASSWORD=YOUR_STRONG_CLICKHOUSE_PASSWORDNode.js / HTTP 查询骨架:
const endpoint = process.env.CLICKHOUSE_HTTP_URL;
const query = "SELECT service, count() FROM te_analytics.event_demo GROUP BY service";
const response = await fetch(endpoint, {
method: "POST",
body: query,
headers: { "Content-Type": "text/plain" }
});
if (!response.ok) {
throw new Error(`ClickHouse query failed: ${response.status}`);
}不要在业务代码里拼接真实密码、生产 host 或无限制查询。团队模板里要写默认超时、最大返回行数、查询 owner 和慢查询记录方式。
ClickHouse 解决的核心架构问题
ClickHouse 适合:
大批量追加写入。明细日志、指标、埋点、宽表和聚合分析。列式扫描和压缩带来的高吞吐查询。
按时间、服务、租户、事件类型等维度过滤和聚合。通过副本、分片和分布式表扩展存储和查询。
ClickHouse 不适合:
高频单行更新和删除。强事务 OLTP。小批量逐条写入。
复杂跨行事务一致性。还没有明确查询模式就盲目建宽表。
架构师判断重点不是“ClickHouse 快不快”,而是查询模式、导入方式、排序键、分区、数据生命周期、资源隔离和团队维护能力是否匹配。
主流生产架构
单节点
组成:一个 ClickHouse server,一个数据目录,一个或多个 database。
优点:简单、成本低、适合个人开发、小型报表、低风险内部分析。
问题:单点故障、容量受限、没有自动故障切换。
适用:开发验证、轻量分析、临时数据集、非核心内部工具。
单分片多副本
组成:多个 ClickHouse server 存同一份数据副本,表使用 ReplicatedMergeTree,元数据协调依赖 ClickHouse Keeper 或 ZooKeeper。
能力:
副本冗余。读扩展。单节点故障后可继续查询。
核心问题:
Keeper / ZooKeeper 本身要治理。副本延迟和写入确认要评审。DDL、数据一致性、备份恢复复杂度增加。
适用:生产基础高可用和读扩展。
多分片多副本
组成:多个 shard,每个 shard 多个 replica,查询通过 Distributed table 路由。
能力:
横向扩展存储。横向扩展写入和查询吞吐。分摊大表和大查询压力。
核心问题:
shard key 和数据分布决定长期命运。跨分片聚合、join、去重和排序成本高。Distributed 表可能让开发者误以为“查一张表”没有成本。
扩缩容、重平衡和备份恢复不是开发脚本能解决的事。
适用:数据量和查询量明确超过单分片能力,且团队有分布式运维和治理能力。
冷热数据与对象存储
ClickHouse 可以结合 TTL、磁盘策略和对象存储做冷热分层,决策时至少拆开四个问题:
热数据服务高频查询。冷数据降低存储成本。TTL 可移动、重压缩或删除数据。
数据平台与运维 owner 必须共同验收迁移失败、对象存储不可用和恢复路径。
ClickHouse Cloud / 托管
托管服务适合减少集群运维复杂度,但架构师仍要确认:
区域、网络、IP allowlist、PrivateLink。版本和升级策略。存储、计算、冷热分层和费用。
备份恢复和数据导出。权限、审计和退出方案。
升级与回退
版本升级会同时触碰 server、client、驱动、表元数据、Keeper 协议和查询行为。上线前在有代表性的预生产环境重放写入、查询、schema migration、物化视图、分布式表和备份恢复,并记录 SELECT version()、system.build_options、配置差异与 changelog 中的 incompatible change。复制集群一次只滚动一个副本,先确认该副本重新追平复制队列,再继续下一台;Keeper 的升级顺序和仲裁可用性单独演练。
不要把“镜像 tag 改回去”当成可靠回退。新版本可能已经写入旧版本无法读取的元数据或 part 格式,直接让旧二进制复用该数据卷可能启动失败。升级窗口要保留旧镜像、旧配置、驱动版本和升级前可恢复备份;异常时先停止继续滚动、隔离新写入,优先把流量切回尚未升级的副本。若数据格式已经改变,则在隔离的旧版本集群执行恢复并校验,而不是原地反复升降级。
核心机制
列式存储和压缩
ClickHouse 按列存储,查询只读需要的列,适合扫描和聚合。好处是压缩率高、扫描快;代价是单行更新和小批量频繁写入不友好。
MergeTree 和 part
MergeTree 写入会产生 data parts,后台 merge 负责合并。parts 太多会导致:
查询变慢。后台合并压力增大。文件句柄和磁盘元数据压力上升。
Too many parts 报错。
团队写入规范:尽量批量写入,不要每条事件一个 insert。
ORDER BY 和 PRIMARY KEY
ClickHouse 的 ORDER BY 是表内数据排序键,直接影响稀疏索引、压缩和查询跳过能力。PRIMARY KEY 默认等于排序键,但它不是逐行唯一约束。
判断标准:
高频过滤字段放在排序键前面。时间字段常用于范围过滤,但不一定永远放第一。高基数字段放太前可能降低压缩和跳过效率。
排序键一旦选错,重建大表成本高。
PARTITION BY
分区用于数据管理和分区裁剪,不是越细越好。官方文档明确不建议过细 partition,也不建议按 client id、用户名这类高基数字段分区。
分区取值要同时看保留周期和 part 数量:
常见按月分区。观测日志可能按天分区,但要评估 parts 数量。不按用户、订单、设备、request_id 分区。
TTL
TTL 可以删除、移动、重压缩或聚合数据。它适合数据生命周期治理,不适合替代审计、合规删除和业务纠错流程。
Materialized View 和 Projection
Materialized View 可做预聚合或数据转写,Projection 可优化查询路径。它们都不是免费加速:
增加写入和存储成本。增加排障复杂度。修改和回填要评审。
Keeper / ZooKeeper
ClickHouse Keeper 或 ZooKeeper 负责复制协调。不要把 Keeper 当作“容器里顺手再起一个组件”:
它影响复制表元数据和一致性。需要独立资源和备份。网络抖动可能造成副本异常。
生产 runbook 必须记录 Keeper 的监控、备份、升级顺序和故障接管责任。
查看版本和当前用户:
SELECT version(), currentUser();查看数据库和表:
SHOW DATABASES;
SHOW TABLES FROM te_analytics;查看表结构:
SHOW CREATE TABLE te_analytics.event_demo;查看 parts:
SELECT database, table, partition, count() AS parts, sum(rows) AS rows
FROM system.parts
WHERE active
GROUP BY database, table, partition
ORDER BY parts DESC;查看最近查询:
SELECT query_start_time, user, query_duration_ms, read_rows, read_bytes, memory_usage, query
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY query_start_time DESC
LIMIT 10;查看磁盘:
SELECT name, path, free_space, total_space
FROM system.disks;查看设置:
SELECT name, value, changed
FROM system.settings
WHERE name IN ('max_memory_usage', 'max_threads', 'max_execution_time');导入 CSV:
cat events.csv | \
curl --fail \
"${CLICKHOUSE_URL}/?query=INSERT%20INTO%20te_analytics.event_demo%20FORMAT%20CSVWithNames" \
--data-binary @-导出 CSV:
curl --fail --get "${CLICKHOUSE_URL}/" \
--data-urlencode "query=SELECT * FROM te_analytics.event_demo FORMAT CSVWithNames" \
> ./tmp/event_demo.csv备份到本地磁盘示例。前提是 config.d 已声明名为 backups 的备份 disk,团队模板要把备份目录、权限和清理策略写清楚:
BACKUP TABLE te_analytics.event_demo
TO Disk('backups', 'event_demo_<RUN_ID>.zip');恢复到临时表后校验:
RESTORE TABLE te_analytics.event_demo
AS te_analytics.event_demo_restore
FROM Disk('backups', 'event_demo_<RUN_ID>.zip');
SELECT count() FROM te_analytics.event_demo_restore;备份是否有效只看恢复结果,不看 zip 文件是否存在。使用对象存储、增量、跨副本或跨分片恢复时,runbook 必须同时记录 S3 凭证来源、恢复顺序、预期 RTO、校验查询和失败回退;没有恢复演练就没有可用备份。
端口通但认证失败
现象:HTTP 返回认证错误,或者 clickhouse-client 登录失败。
判断:
curl -i "http://127.0.0.1:8124/?query=SELECT%201"
curl -i "http://te_app:YOUR_STRONG_CLICKHOUSE_PASSWORD@127.0.0.1:8124/?query=SELECT%201"常见原因:密码没设置、旧 volume 保留旧用户、连接串里密码特殊字符未处理、连错 host。
旧 volume 让配置不生效
现象:改了环境变量或初始化 SQL,重启后仍是旧库表、旧用户。
判断:
docker volume ls | findstr clickhouse
docker logs your-project-clickhouse --tail 100个人环境可 down -v 重置;共享环境必须迁移和变更,不得直接删数据卷。
Too many parts
现象:写入报 parts 太多,查询和 merge 变慢。
判断:
SELECT database, table, partition, count() AS parts
FROM system.parts
WHERE active
GROUP BY database, table, partition
ORDER BY parts DESC;常见原因:小批量频繁 insert、分区过细、后台 merge 跟不上。修复优先合并写入批次和分区设计,不要只调大阈值。
查询内存爆
现象:查询失败,日志里出现 memory limit exceeded。
判断:
SELECT query_duration_ms, memory_usage, read_rows, read_bytes, query
FROM system.query_log
WHERE type = 'Exception'
ORDER BY query_start_time DESC
LIMIT 10;修复:减少扫描列、加过滤、调整 ORDER BY、预聚合、设置查询限制,不要把共享实例当个人临时算力池。
ORDER BY 选错
现象:EXPLAIN 看不到索引裁剪,查询读大量 rows / bytes。
判断:
EXPLAIN indexes = 1
SELECT *
FROM te_analytics.event_demo
WHERE service = 'api' AND event_date = today();修复:重新评估高频过滤和聚合路径。大表改排序键通常意味着重建表和回填数据。
FINAL 滥用
现象:为了查“最终结果”到处加 FINAL,查询成本飙升。
原因:ReplacingMergeTree / CollapsingMergeTree 的合并语义被误解。修复要从表引擎、去重键、写入逻辑和物化视图设计解决,不把 FINAL 当万能修补。
Distributed 表误用
现象:开发者查一张 Distributed 表,以为只是本地表,实际打到多个 shard。
判断:看表引擎、集群配置、system.clusters 和 query_log 读行数。
修复:README 明确本地表、分布式表、写入表和查询表边界。
磁盘满或只剩只读
现象:插入失败、merge 失败、查询临时目录报错。
判断:
SELECT * FROM system.disks;
SELECT database, table, sum(bytes_on_disk) AS bytes
FROM system.parts
WHERE active
GROUP BY database, table
ORDER BY bytes DESC;修复:清理测试表、调整 TTL、扩容磁盘、迁移冷数据。共享环境必须有 owner 和清理窗口。
ClickHouse 凭证治理重点:
默认用户不进入应用配置。HTTP 和 Native 端口默认只绑定本机,除非明确要共享。共享环境按 database、role、quota、settings profile 隔离。
S3 / 对象存储 key、云账号、备份密码不进仓库。query_log 里可能出现查询文本,避免把敏感值写进 SQL。BI / DataGrip / DBeaver 连接默认只读。
敏感信息扫描:
rg -n "CLICKHOUSE_PASSWORD|clickhouse://|http://.*:.*@|AWS_ACCESS_KEY|AWS_SECRET|BEGIN .*PRIVATE KEY" .团队最小账号模型:
| 账号 | 用途 | 权限边界 |
|---|---|---|
| admin | 本地初始化、紧急救援 | 不给普通开发长期使用 |
| writer | 数据导入 | 只写目标 database / table |
| reader | BI / 排障 | 只读目标 database |
| maintainer | 建表、索引、TTL、MV | 走变更审查 |
| ci | 冒烟验证、清理测试表 | 只操作测试前缀 |
账号表必须落到可执行授权。用具备 access management 权限的初始化账号创建只读用户:
CREATE USER IF NOT EXISTS te_reader
IDENTIFIED WITH sha256_password BY 'YOUR_READONLY_PASSWORD';
GRANT SELECT ON te_analytics.* TO te_reader;随后分别验证读与写。第一条应返回数据,第二条应失败并出现 ACCESS_DENIED;若插入成功,立即检查 SHOW GRANTS FOR te_reader,不要在应用层假装只读。
curl --fail --user 'te_reader:YOUR_READONLY_PASSWORD' --get \
'http://127.0.0.1:8124/' \
--data-urlencode 'query=SELECT count() FROM te_analytics.event_demo'
curl --fail-with-body --user 'te_reader:YOUR_READONLY_PASSWORD' \
'http://127.0.0.1:8124/' \
--data-binary "INSERT INTO te_analytics.event_demo SELECT today(), now(), 'forbidden', 'request', 0, 'deny-001', 1, 1, []"团队模板至少沉淀:
dev-dependencies/clickhouse/compose.yaml。.env.example,只保留占位符。init/01-schema.sql,创建 database、table、TTL 和必要视图。
data/ 小样例,必须脱敏。scripts/verify.sh,验证连接、导入、聚合、EXPLAIN 和 system.parts。scripts/clean-dry-run.sh 和 scripts/clean-confirmed.sh。
docs/analytics-contract.md,写明表模型、导入批次、ORDER BY、PARTITION BY、TTL、owner、保留期和资源限制。
团队职责:
| 角色 | 负责 |
|---|---|
| 架构 / 数据 owner | 表模型、排序键、分区、TTL、分布式边界 |
| 后端 / 数据接入 owner | 批量写入、连接池、超时、重试和脱敏 |
| 测试 owner | 样例数据、清理脚本和回归查询 |
| 平台 / 运维 owner | 共享实例、资源、备份恢复、监控和升级 |
| 安全 owner | 账号、端口、S3 key、BI 导出和审计 |
ClickHouse 不是 OLTP 数据库
如果业务要频繁单行更新、强事务、按主键点查和复杂在线修改,ClickHouse 不是默认答案。判断标准:写入是否批量追加,查询是否聚合分析,更新是否低频且可接受异步合并。
默认端口和账号会放大误连风险
HTTP 8123 和 Native 9000 很容易被工具自动发现。个人本地绑定 127.0.0.1,共享环境必须有认证、只读账号、网络隔离和连接命名。
旧 volume 会保留旧用户和旧 schema
改 .env 和 init SQL 不生效时,先查数据卷。个人可 down -v,共享环境必须用迁移 SQL 和变更记录,不能直接删 /var/lib/clickhouse。
小批量写入会制造 parts 雪崩
现象是 system.parts 同一 partition 下 part 数高、merge backlog 增长、写入报错。修复优先批量写入、Buffer / Kafka / 批处理链路和合理分区。
ORDER BY 是架构决策
ORDER BY 决定稀疏索引和数据排序。它不是唯一约束,也不是随便把所有字段都放进去。大表改排序键意味着建新表、回填、校验和切换。
分区不能按高基数字段炫技
官方文档明确不建议过细 partition。按用户、租户、设备、request_id 分区会制造海量 partition / part。常见按月,观测场景可评估按天。
FINAL 是昂贵兜底
ReplacingMergeTree 场景里滥用 FINAL 会让查询代价很高。先评审写入去重、版本列、物化视图和查询口径,再决定是否允许 FINAL。
Distributed 表不是普通视图
Distributed 表会把查询打到集群,跨 shard 聚合、排序、join 都有成本。开发 README 必须明确本地表、分布式表、写入表、查询表。
Keeper / ZooKeeper 不是顺手依赖
复制表依赖协调服务,网络抖动、磁盘压力、备份缺口和版本不一致都会反映为副本延迟或只读状态。生产 runbook 要把 Keeper 健康、副本队列、恢复顺序和责任人连成同一条处置链。
query_log 也可能泄露敏感信息
查询文本里如果拼真实 token、手机号、邮箱或 SQL 注释,会进入日志。应用层必须参数化、脱敏,排障导出 query_log 时也要审查。
备份不是导出一个 CSV
CSV 只能作为小样本导出。生产备份要覆盖 metadata、parts、mutation、replica、对象存储、恢复耗时和查询校验,并在隔离目标库完成一次计时恢复;只生成文件不算备份成功。
ClickHouse Cloud 不是成本黑盒
托管降低运维,但计算、存储、冷数据、备份、网络出口和查询峰值都会影响费用。团队必须有预算、配额、owner 和退出方案。
上线与移交核对
本机跑通:
Docker / Compose 可用。宿主端口 8124 和 9001 未冲突。使用固定版本 tag,不使用 latest 或 head。
用户和密码为占位变量,真实值不入库。HTTP SELECT 1 成功。Native client 能返回 version() 和 currentUser()。
初始化表使用 MergeTree。样例数据可导入。聚合查询可返回结果。
EXPLAIN indexes = 1 有验证记录。system.parts 可查看 part 数。测试表可受控清理。
项目接入:
连接串来自环境变量或 secret。写入批量化,不逐行小 insert。表模型、ORDER BY、PARTITION BY、TTL 进入脚本和评审。
只读账号和写入账号分离。BI / GUI 默认只读。query_log 不含真实敏感值。
架构判断:
单节点、单分片多副本、多分片多副本、Cloud / Operator 边界已写清。复制架构明确 Keeper / ZooKeeper 边界。分布式架构有 shard key、查询模式和成本评审。
冷热数据和 TTL 有 owner、保留期和恢复路径。备份恢复至少演练一次。
安全治理:
HTTP / Native 端口没有裸露到不可信网络。默认账号不进应用。S3 / 云密钥不进仓库。
清理脚本有 dry-run、环境确认和 owner 过滤。共享实例有配额、慢查询治理和资源责任人。
