何时值得进行隔离隔离面向具有持续摄取的大规模部署。若每月存储数据量大致低于 100 TB,单个读写 service 通常足以同时承载这两类工作负载,很可能无需第二个 service。请使用资源估算模型估算每月的压缩后数据量。
为何要将读与写隔离
- 写入不再拖慢读取。 持续的 OpenTelemetry 摄取——既包括插入本身,也包括随之而来的后台合并——会与仪表板和搜索查询争抢 CPU 和内存。摄取进行期间,读取延迟可能明显变差,摄取停止后才会恢复。
- 读取不再干扰写入。 这种争抢是双向的:一次繁重的临时查询或一次开销高昂的仪表板渲染可能耗尽服务的内存,直接导致插入失败,而不只是变慢。
- 只读计算资源完全专用于查询。 只读服务除系统表外不执行任何后台合并。它们还能立即进入空闲状态,而读写服务则可能因合并而一直保持唤醒。
- 两侧各自独立进行容量规划。 容量规划模型会分别估算摄取所需计算资源和查询所需计算资源,而仓库允许你将两者各自部署为独立的服务。一旦超过该模型 1 QPS 的基线,查询计算资源便占据主导——其 5 QPS 下的实例演算得出摄取需要 58 个 vCPU,而查询需要 290 个——因此一个较小的写入服务即可支撑一个大得多的读取服务。
- 空闲与自动扩缩容按服务分别配置。 每个服务都有各自的副本数、自动扩缩容和自动空闲设置,因此写入服务可以为持续摄取保持始终在线,而读取服务可在非工作时间进入空闲状态。
- 存储不会重复。 同一仓库中的服务共享相同的对象存储目录和相同的表,并且存储只计费一次。
- 可按端点限制访问。 IP 访问列表按服务生效,因此写入端点可以只允许你的 collector 访问,而读取端点只允许你的 ClickStack 部署访问。请参阅我们关于网络访问控制的指南。
架构
推荐的拓扑是:一个 warehouse 中包含一个用于摄取的读写 service 和一个供 ClickStack 使用的只读 service:
规划拓扑时请注意以下几点:
- warehouse 中的第一个 service 始终为读写类型,且 service 的类型在创建时即已固定——若要在只读与读写之间切换,需在该 warehouse 中新建一个 service。
- 同一 warehouse 中的所有 service 共享相同的云提供商、区域、ClickHouse 版本和 Keeper,并沿用 primary service 的 upgrade schedule。
- 摄取只使用一个读写 service。合并任务会分配到共享同一存储的所有读写 service 上,因此某个 service 上的插入所触发的合并可能由另一个 service 执行。如果那个 service 同时还承担着繁重的查询,这些查询就会与合并争抢它所在 service 的 CPU 和内存——导致第一个 service 的插入所对应的合并变慢,进而拖累插入性能。请把查询工作负载放在只读 service 上,仅在需要将合并与摄取分离时才增加第二个读写 service。
搭建隔离部署
1
准备读写服务
使用现有 service——或新建 warehouse 的 primary service——进行摄取,并根据资源估算模型中的摄取 compute 需求来确定其规格。在该 service 上创建数据库和专用的摄取用户。由于 warehouse 中的所有 service 共享 access controls,在此处创建的用户可在该 warehouse 的每个 service 上使用:请使用
openssl rand -base64 24 等工具生成密码,并将其保存在密钥管理器中,而不要写入清单或 shell 历史记录。更多详情请参阅我们的指南创建摄取用户。如果该 service 已属于某个仓库,请注意:当仓库中的另一个 service 处于休眠状态时,数据库级别的 DDL 可能会挂起——参见管理与 DDL。2
向仓库添加只读服务
在 ClickHouse Cloud 控制台中,点击刚刚准备好的服务上的加号,创建第二个与其共享数据的服务。将服务类型选择为 read-only,并依据 sizing model 为查询计算资源选择合适的规格。完整操作步骤请参阅我们的指南如何设置仓库。
3
将摄取指向读写服务
配置你的 collector,将数据导出到 read-write service endpoint,并以摄取用户的身份进行认证:详情请参阅 collector 配置选项,或 Vector 及其他摄取路径的对应设置。发送到只读端点的写入请求会被拒绝,因此 collector 必须始终指向读写 service。
4
将 ClickStack 连接到只读服务
ClickStack UI 始终连接到在 ClickHouse Cloud 控制台中启动它的那个 ClickHouse 服务。若要在只读计算资源上运行:
- 在 ClickHouse Cloud 控制台中选择只读服务。
- 从左侧导航菜单中选择 ClickStack。
5
验证拆分
在 ClickStack 中执行一次搜索或打开一个 dashboard,然后查看这些查询最终落到了哪里。这里返回空结果本身并不意味着查询被发往了别处:使用此查询时需注意两点:已进入休眠的 service 不会返回任何行,因此如需完整结果,请先将其唤醒;此外,
system 表写入在执行该查询的节点上,因此对于包含多个副本的 service,需要使用 clusterAllReplicas 并指定 default cluster 名称,才能覆盖所有副本。在 read-only service 上,你应当能看到 ClickStack 发出的查询:system.query_log 是定期刷写的——默认每 7.5 秒一次——因此在执行搜索后立即运行的查询可能还看不到记录。稍等片刻再重新运行,或者在你拥有相应 grant 的情况下用 SYSTEM FLUSH LOGS 强制刷写。按 user 和 http_user_agent 分组正是归因流量来源的关键:无论你的 source 指向哪些表,它都能区分出 UI、SQL 控制台以及其他连接到该端点的客户端。对 is_initial_query = 1 进行筛选可确保每个提交的查询只保留一行——来自 Distributed 执行的次级查询,以及用于计算 materialized view 的内部查询,会以 is_initial_query = 0 单独记录。在读写服务上,同样的查询应当显示来自摄取用户的 insert 操作,且不存在任何 ClickStack 查询流量。逐个在每个服务上运行该查询才是可靠的检查方式,因为 default cluster 仅包含你当前所连接服务的副本。若要获得跨整个仓库的汇总视图,请改用 all_groups.default 这一 cluster 名称:hostName() 标识的是副本而非 service——若要将活动归因到特定 service,请直接查询该 service。将合并与摄取分离
在极高的持续摄取速率下,真正的主要开销来自合并而非插入本身。由于合并会分配到共享同一存储的所有读写服务上,它们也可能被调度到你原本另有用途的服务上。 对于这类部署,可以将合并完全移出摄取服务,形成三服务拓扑:需要提交支持请求在读写服务上禁用合并无法通过 Cloud 控制台配置。请联系支持团队为某个服务启用该配置。
- 不要依赖任一读写服务的自动空闲。 禁用了合并的服务仍会处理仓库中其他服务插入数据所产生的 part 下载与移除事件,而未合并 parts 数量过多本身也会阻止进入空闲状态。请按两个读写服务持续保持唤醒来规划。
- 不要让查询落到任何一个读写服务上。 在读写服务上执行繁重的
SELECT查询会与合并工作争抢 CPU 和内存,而这正是该拓扑要规避的故障模式。请按上文所述,将 ClickStack 指向只读服务。 - 如果存在变更,它们会在执行它们的服务上被跟踪。 变更在可观测性场景中很少见——ClickStack 的 schema 设置了
ttl_only_drop_parts = 1,因此常规的数据保留会在 TTL 合并期间整体删除已过期的 parts,而不是通过变更逐行删除数据。如果你确实向摄取服务提交了会产生变更的ALTER,它将由合并服务执行,其进度也会出现在合并服务的system.mutations中,而不是摄取服务上。
管理与 DDL
所有 schema 变更都必须在 read-write 服务上执行,包括:- 建表 —— 由 ClickStack collector 在首次摄取时自动完成
- 修改生存时间 (TTL) 以调整数据保留策略
- 创建 materialized views 以加速查询
- 添加跳过索引、projections 及其他性能优化
隔离智能体工作负载
通过 ClickStack MCP 服务器连接的 AI assistant 和仪表盘一样都属于读流量,但负载模式截然不同:正在排查事故的 agent 会在极短时间内连续发出大量探索性查询,查询范围也无人事先设定。如果让智能体和 UI 共用同一个 read-only service,这类突发流量就会直接挤占工程师在同一起事故中正在查看的仪表盘。 此处同样适用仓库模式——为智能体单独配备 read-only compute:1
添加第二个只读服务
按照上文的配置方式,在仓库中再创建一个 read-only service。它与为 UI 提供服务的那个服务读取相同的表,无需复制任何数据。然后按照将 ClickStack 指向只读服务的说明,在 Cloud 控制台上为其启动一次 ClickStack。Cloud MCP 要求服务同时启用 ClickStack 和 MCP 本身——参见 MCP 前置条件。规格应按智能体预期产生的查询负载来确定,而不是按 sizing model 中的仪表盘 QPS,并保持 auto-idling 处于启用状态:智能体的使用通常是间歇性的,因此该服务可以在两次调查之间进入空闲。
2
在该服务上启用 MCP
在 ClickHouse Cloud 控制台中打开该 read-only service,点击 Connect,选择 Connect with MCP 并将其开启。参见启用 Remote MCP server。
3
将 MCP 客户端指向该服务
Cloud MCP endpoint 对所有服务都相同——请求通过 任何 MCP client 都可以携带该请求头——关于 Cursor、VS Code 等工具中的等效配置,请参见指向特定服务。
x-service-id 请求头进行路由,若不携带该请求头,请求会发往你的账户使用的第一个 ClickStack service。复制现有的 MCP 配置,并加上携带新 read-only service ID 的请求头:告警
ClickStack 会在创建该告警的 service 上评估告警,因此告警与 UI 运行在同一套 compute 上——在本拓扑中即 read-only service。托管 ClickStack要启用告警,至少需要有一位拥有 Service Admin 权限的用户登录过一次 ClickStack。这会 provision 出用于运行告警 query 的专用 database user,该用户在 warehouse 中的所有 service 之间共享。请参阅我们的指南为托管 ClickStack 授予访问权限。
隔离告警评估
告警负载无法集中路由,因为告警是由用户创建的:谁在 ClickStack 中添加告警,该告警就会落到他当时所用的 service 上,并消耗该 service 的 compute 进行评估。没有任何 SETTING 能把某个 service 的告警挪到别处。 你真正能隔离的,是由你集中维护的那部分告警——即平台团队为整个 organization 维护的告警,它们通常也是评估最频繁的。可以在 warehouse 中为它们单独准备一个 read-only service,并从该 service 上启动的 ClickStack 中创建这些告警:
其余的 trade-off 都源自状态按 service 隔离这一事实:
- 公共告警以及与之配套的仪表盘只存在于告警 service 上,在查询 service 上工作的用户看不到它们。但无论哪种方式,通知都会发送到相同的目标端,因此用户失去的只是对定义的可见性,而非告警能力本身。
- 告警 service 上的 source 是独立的对象。使用默认 OpenTelemetry schema 的 source 会被自动检测,但自定义 source 也必须在这里配置一遍,告警才能引用它们。