> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 隔离读写工作负载

> 使用 ClickHouse Cloud 仓库分离 ClickStack 的摄取与查询工作负载

export const ScalePlanFeatureBadge = ({feature = '此功能', linking_verb_are = false}) => {
  return <div className="scalePlanFeatureContainer">
            <div className="scalePlanFeatureBadge">
                Scale 计划功能
            </div>
            <div>
                <p>{feature} {linking_verb_are ? '仅在' : '仅在'} Scale 和 Enterprise 计划中可用。要升级，请访问 Cloud Console 中的计划页面。</p>
            </div>
        </div>;
};

可观测性工作负载对同一份数据提出了两类截然不同的需求。摄取是持续进行、以写入为主的,即使 insert 已经完成,后台合并仍会长时间占用 CPU 与内存。查询负载则并不均衡:仪表盘和搜索会在故障期间达到峰值,而此时响应缓慢恰恰最难以接受。

借助 ClickHouse Cloud [仓库](/docs/zh/products/cloud/features/infrastructure/warehouses),这两类工作负载可以基于同一份数据、由各自独立的计算资源来提供服务,从而互不争抢 CPU 与内存。

<ScalePlanFeatureBadge feature="Compute-compute separation" />

仓库是 ClickHouse Cloud 的功能,因此此处描述的配置方式适用于运行在 ClickHouse Cloud 之上的 ClickStack:在单份数据之上,分离后的两侧可各自独立地调整规格、伸缩与休眠。

<Note>
  **何时值得进行隔离**

  隔离面向具有持续摄取的大规模部署。若每月存储数据量大致低于 100 TB,单个读写 service 通常足以同时承载这两类工作负载,很可能无需第二个 service。请使用[资源估算模型](/docs/zh/clickstack/managing/estimating-resources)估算每月的压缩后数据量。
</Note>

<h2 id="why-isolate">
  为何要将读与写隔离
</h2>

* **写入不再拖慢读取。** 持续的 OpenTelemetry 摄取——既包括插入本身，也包括随之而来的后台合并——会与仪表板和搜索查询争抢 CPU 和内存。摄取进行期间，读取延迟可能明显变差，摄取停止后才会恢复。
* **读取不再干扰写入。** 这种争抢是双向的：一次繁重的临时查询或一次开销高昂的仪表板渲染可能耗尽服务的内存，直接导致插入失败，而不只是变慢。
* **只读计算资源完全专用于查询。** 只读服务除系统表外不执行任何后台合并。它们还能立即进入空闲状态，而读写服务则可能因合并而一直保持唤醒。
* **两侧各自独立进行容量规划。** [容量规划模型](/docs/zh/clickstack/managing/estimating-resources)会分别估算摄取所需计算资源和查询所需计算资源，而仓库允许你将两者各自部署为独立的服务。一旦超过该模型 1 QPS 的基线，查询计算资源便占据主导——其 5 QPS 下的[实例演算](/docs/zh/clickstack/managing/estimating-resources#worked-example)得出摄取需要 58 个 vCPU，而查询需要 290 个——因此一个较小的写入服务即可支撑一个大得多的读取服务。
* **空闲与自动扩缩容按服务分别配置。** 每个服务都有各自的副本数、自动扩缩容和自动空闲设置，因此写入服务可以为持续摄取保持始终在线，而读取服务可在非工作时间进入空闲状态。
* **存储不会重复。** 同一仓库中的服务共享相同的对象存储目录和相同的表，并且[存储只计费一次](/docs/zh/products/cloud/features/infrastructure/warehouses#pricing)。
* **可按端点限制访问。** IP 访问列表按服务生效，因此写入端点可以只允许你的 collector 访问，而读取端点只允许你的 ClickStack 部署访问。请参阅我们关于[网络访问控制](/docs/zh/products/cloud/features/infrastructure/warehouses#network-access-control)的指南。

<h2 id="architecture">
  架构
</h2>

推荐的拓扑是：一个 warehouse 中包含一个用于摄取的读写 service 和一个供 ClickStack 使用的只读 service：

| Service | Type | 职责 | 客户端 |
| - | - | - | - |
| Primary | Read-write | 摄取、background merges、DDL (建表、TTL、materialized views) | OpenTelemetry collector、ClickPipes、Vector、用于管理的 SQL 控制台 / 客户端 |
| Secondary | Read-only | 搜索、仪表盘、笔记本、alert 评估 | ClickStack UI (HyperDX) |

规划拓扑时请注意以下几点：

* warehouse 中的第一个 service 始终为读写类型，且 service 的类型在**创建时即已固定**——若要在只读与读写之间切换，需在该 warehouse 中新建一个 service。
* 同一 warehouse 中的所有 service 共享相同的云提供商、区域、ClickHouse 版本和 Keeper，并沿用 primary service 的 upgrade schedule。
* 摄取只使用**一个**读写 service。合并任务会分配到共享同一存储的所有读写 service 上，因此某个 service 上的插入所触发的合并可能由另一个 service 执行。如果那个 service 同时还承担着繁重的查询，这些查询就会与合并争抢它所在 service 的 CPU 和内存——导致第一个 service 的插入所对应的合并变慢，进而拖累插入性能。请把查询工作负载放在只读 service 上，仅在需要[将合并与摄取分离](#separating-merges)时才增加第二个读写 service。

<h2 id="setup">
  搭建隔离部署
</h2>

<Steps>
  <Step title="准备读写服务" id="prepare-read-write-service">
    使用现有 service——或新建 warehouse 的 primary service——进行摄取，并根据[资源估算模型](/docs/zh/clickstack/managing/estimating-resources)中的摄取 compute 需求来确定其规格。

    在该 service 上创建数据库和专用的摄取用户。由于 warehouse 中的所有 service 共享 access controls，在此处创建的用户可在该 warehouse 的每个 service 上使用：

    ```sql theme={null}
    CREATE DATABASE otel;
    CREATE USER hyperdx_ingest IDENTIFIED WITH sha256_password BY '<strong-password>';
    GRANT SELECT, INSERT, CREATE DATABASE, CREATE TABLE, CREATE VIEW ON otel.* TO hyperdx_ingest;
    ```

    请使用 `openssl rand -base64 24` 等工具生成密码，并将其保存在密钥管理器中，而不要写入清单或 shell 历史记录。更多详情请参阅我们的指南[创建摄取用户](/docs/zh/clickstack/ingesting-data/collector#creating-an-ingestion-user)。

    如果该 service 已属于某个仓库，请注意：当仓库中的另一个 service 处于休眠状态时，数据库级别的 DDL 可能会挂起——参见[管理与 DDL](#administration)。
  </Step>

  <Step title="向仓库添加只读服务" id="add-read-only-service">
    在 ClickHouse Cloud 控制台中，点击刚刚准备好的服务上的加号，创建第二个与其共享数据的服务。将服务类型选择为 **read-only**，并依据 sizing model 为查询计算资源选择合适的规格。

    完整操作步骤请参阅我们的指南[如何设置仓库](/docs/zh/products/cloud/features/infrastructure/warehouses#setup-warehouses)。
  </Step>

  <Step title="将摄取指向读写服务" id="point-ingestion">
    配置你的 collector，将数据导出到 **read-write** service endpoint，并以摄取用户的身份进行认证：

    ```shell theme={null}
    CLICKHOUSE_ENDPOINT=https://<read-write-service>.clickhouse.cloud:8443
    CLICKHOUSE_USER=hyperdx_ingest
    CLICKHOUSE_PASSWORD=<strong-password>
    HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE=otel
    ```

    详情请参阅 [collector 配置选项](/docs/zh/clickstack/managing/config#otel-collector)，或 [Vector](/docs/zh/clickstack/ingesting-data/vector) 及其他摄取路径的对应设置。

    发送到只读端点的写入请求会被拒绝，因此 collector 必须始终指向读写 service。
  </Step>

  <Step title="将 ClickStack 连接到只读服务" id="point-clickstack">
    ClickStack UI 始终连接到在 ClickHouse Cloud 控制台中启动它的那个 ClickHouse 服务。若要在只读计算资源上运行：

    1. 在 ClickHouse Cloud 控制台中选择只读服务。
    2. 从左侧导航菜单中选择 **ClickStack**。

    此后 UI 发出的每个查询都会在该只读计算资源上运行，无需在 ClickStack 中做任何配置。请参阅我们的指南[将 ClickStack 与只读计算资源配合使用](/docs/zh/clickstack/deployment/managed#clickstack-read-only-compute)。

    <Warning>
      **ClickStack 状态的作用域限定于服务**

      仪表盘、已保存搜索、告警和数据源均归属于启动 ClickStack 的那个服务，不会随你一起转移到同一仓库中的另一个服务——即便两个服务共享相同的数据。使用[默认 OpenTelemetry schema](/docs/zh/clickstack/deployment/managed#adding-data-sources) 的数据源会在新服务上被自动检测，因此可以立即搜索这些数据；但自定义或手动配置的数据源——以及你保存的其他所有内容——都需要重新创建。

      在构建仪表盘之前，请先确定要从哪个服务运行 ClickStack。如果你要切换一个已有的部署，请注意：在原服务上创建的告警会继续在那里运行——消耗该服务的计算资源——直到你将其删除。
    </Warning>
  </Step>

  <Step title="验证拆分" id="verify">
    在 ClickStack 中执行一次搜索或打开一个 dashboard，然后查看这些查询最终落到了哪里。`system` 表写入在执行该查询的节点上，因此对于包含多个副本的 service，需要使用 [`clusterAllReplicas`](/docs/zh/reference/system-tables/overview#querying-across-nodes) 并指定 `default` cluster 名称，才能覆盖所有副本。在 **read-only** service 上，你应当能看到 ClickStack 发出的查询：

    ```sql theme={null}
    SELECT 
        user, 
        query_kind, 
        http_user_agent,
        count()
    FROM clusterAllReplicas('default', system.query_log)
    WHERE event_time > now() - toIntervalMinute(10) 
      AND type = 'QueryFinish'
      AND is_initial_query = 1
    GROUP BY ALL
    ORDER BY count() DESC;
    ```

    这里返回空结果本身并不意味着查询被发往了别处：`system.query_log` 是定期刷写的——默认每 7.5 秒一次——因此在执行搜索后立即运行的查询可能还看不到记录。稍等片刻再重新运行，或者在你拥有相应 grant 的情况下用 [`SYSTEM FLUSH LOGS`](/docs/zh/reference/statements/system#flush-logs) 强制刷写。

    按 `user` 和 `http_user_agent` 分组正是归因流量来源的关键：无论你的 source 指向哪些表，它都能区分出 UI、SQL 控制台以及其他连接到该端点的客户端。对 `is_initial_query = 1` 进行筛选可确保每个提交的查询只保留一行——来自 Distributed 执行的次级查询，以及用于计算 [materialized view](/docs/zh/reference/system-tables/query_views_log) 的内部查询，会以 `is_initial_query = 0` 单独记录。

    在**读写**服务上，同样的查询应当显示来自摄取用户的 insert 操作，且不存在任何 ClickStack 查询流量。

    逐个在每个服务上运行该查询才是可靠的检查方式，因为 `default` cluster 仅包含你当前所连接服务的副本。若要获得跨整个仓库的汇总视图，请改用 `all_groups.default` 这一 cluster 名称：

    ```sql theme={null}
    SELECT 
        hostName() AS host, 
        query_kind, 
        count()
    FROM clusterAllReplicas('all_groups.default', system.query_log)
    WHERE event_time > now() - toIntervalMinute(10) 
      AND type = 'QueryFinish'
      AND is_initial_query = 1
    GROUP BY ALL;
    ```

    使用此查询时需注意两点：已进入休眠的 service 不会返回任何行，因此如需完整结果，请先将其唤醒；此外，`hostName()` 标识的是副本而非 service——若要将活动归因到特定 service，请直接查询该 service。
  </Step>
</Steps>

<h2 id="separating-merges">
  将合并与摄取分离
</h2>

在极高的持续摄取速率下,真正的主要开销来自合并而非插入本身。由于合并会分配到共享同一存储的所有读写服务上,它们也可能被调度到你原本另有用途的服务上。

对于这类部署,可以将合并完全移出摄取服务,形成三服务拓扑:

| 服务 | 类型 | 职责 |
| - | - | - |
| 摄取 | 读写,已禁用合并 | 仅接受插入 |
| 合并 | 读写 | 执行该仓库的所有后台合并与变更 |
| 查询 | 只读 | 为 ClickStack 提供服务 |

<Info>
  **需要提交支持请求**

  在读写服务上禁用合并无法通过 Cloud 控制台配置。请[联系支持团队](https://clickhouse.com/support/program)为某个服务启用该配置。
</Info>

如果仅摄取本身就足以让一个服务饱和,或者你需要两个读写服务(因为两者都必须写入),则值得考虑这种拓扑。如果你的查询工作负载完全由 ClickStack 承担(它只执行读取),那么更简单的[读写加只读拆分](#architecture)就能满足需求,而且是支持更完善的方案。

运行此拓扑时,请注意以下事项:

* **不要依赖任一读写服务的自动空闲。** 禁用了合并的服务仍会处理仓库中其他服务插入数据所产生的 part 下载与移除事件,而未合并 parts 数量过多本身也会阻止进入空闲状态。请按两个读写服务持续保持唤醒来规划。
* **不要让查询落到任何一个读写服务上。** 在读写服务上执行繁重的 `SELECT` 查询会与合并工作争抢 CPU 和内存,而这正是该拓扑要规避的故障模式。请按[上文](#point-clickstack)所述,将 ClickStack 指向只读服务。
* **如果存在变更,它们会在执行它们的服务上被跟踪。** 变更在可观测性场景中很少见——ClickStack 的 schema 设置了 [`ttl_only_drop_parts = 1`](/docs/zh/clickstack/managing/ttl),因此常规的数据保留会在 TTL 合并期间整体删除已过期的 parts,而不是通过变更逐行删除数据。如果你确实向摄取服务提交了会产生变更的 `ALTER`,它将由合并服务执行,其进度也会出现在合并服务的 [`system.mutations`](/docs/zh/reference/system-tables/mutations) 中,而不是摄取服务上。

<h2 id="administration">
  管理与 DDL
</h2>

所有 schema 变更都必须在 **read-write** 服务上执行，包括：

* 建表 —— 由 ClickStack collector 在首次摄取时自动完成
* [修改生存时间 (TTL)](/docs/zh/clickstack/managing/ttl#modifying-ttl) 以调整数据保留策略
* 创建 [materialized views](/docs/zh/clickstack/managing/materialized-views) 以加速查询
* 添加[跳过索引、projections 及其他性能优化](/docs/zh/clickstack/managing/performance-tuning)

用户、角色和授权不属于 schema 变更 —— 它们由仓库中的所有服务共享，因此只需在任意一个服务上创建一次即可。上文的[设置步骤](#prepare-read-write-service)会创建摄取用户。指向只读服务的其他客户端，都应以独立的只读查询用户身份进行认证，并具备 [ClickStack UI 所需的权限](/docs/zh/clickstack/managing/production#user-permissions)，而不是使用上文展示的摄取授权。

使用 [SQL 控制台或 ClickHouse 客户端](/docs/zh/clickstack/managing/admin)连接到 read-write 服务。由于仓库共享存储和访问控制，变更会立即对只读服务生效可见。如果你已[将合并与摄取分离](#separating-merges)，语句可以提交到任一 read-write 服务 —— 但请注意，mutations 会在合并服务上执行并跟踪。

<Warning>
  **当另一个服务处于休眠状态时，数据库 DDL 可能挂起**

  `CREATE`、`RENAME` 和 `DROP DATABASE` 语句可能被仓库中处于休眠或已停止的服务阻塞，从而导致挂起。在这种拓扑下很容易遇到该问题，因为只读服务会立即进入休眠。执行数据库级语句时请使用 [`distributed_ddl_task_timeout=0`](/docs/zh/reference/settings/session-settings/distributed-ddl#distributed_ddl_task_timeout)，可按查询设置，也可为整个 session 设置：

  ```sql theme={null}
  CREATE DATABASE otel
  SETTINGS distributed_ddl_task_timeout=0
  ```

  手动停止的服务必须重新启动后，才能在其上执行查询。
</Warning>

Materialized views 由插入操作触发，因此由 read-write 服务执行。只读服务会像查询普通表一样查询它们的 target tables，其中也包括为加速查询而[注册到 ClickStack 源](/docs/zh/clickstack/managing/config#materialized-views-settings)的那些 views。

<h2 id="agentic-workloads">
  隔离智能体工作负载
</h2>

通过 [ClickStack MCP 服务器](/docs/zh/clickstack/mcp)连接的 AI assistant 和仪表盘一样都属于读流量，但负载模式截然不同：正在排查事故的 agent 会在极短时间内连续发出大量探索性查询，查询范围也无人事先设定。如果让智能体和 UI 共用同一个 read-only service，这类突发流量就会直接挤占工程师在同一起事故中正在查看的仪表盘。

此处同样适用仓库模式——为智能体单独配备 read-only compute：

<Steps>
  <Step title="添加第二个只读服务" id="agentic-add-service">
    按照[上文的配置方式](#add-read-only-service)，在仓库中再创建一个 read-only service。它与为 UI 提供服务的那个服务读取相同的表，无需复制任何数据。

    然后按照[将 ClickStack 指向只读服务](#point-clickstack)的说明，在 Cloud 控制台上为其启动一次 ClickStack。Cloud MCP 要求服务同时启用 ClickStack 和 MCP 本身——参见 [MCP 前置条件](/docs/zh/clickstack/mcp#managed-prerequisites)。

    规格应按智能体预期产生的查询负载来确定，而不是按 sizing model 中的仪表盘 QPS，并保持 auto-idling 处于启用状态：智能体的使用通常是间歇性的，因此该服务可以在两次调查之间进入空闲。
  </Step>

  <Step title="在该服务上启用 MCP" id="agentic-enable-mcp">
    在 ClickHouse Cloud 控制台中打开该 read-only service，点击 **Connect**，选择 **Connect with MCP** 并将其开启。参见[启用 Remote MCP server](/docs/zh/products/cloud/features/ai-ml/mcp/remote-mcp#enable-remote-mcp-server)。
  </Step>

  <Step title="将 MCP 客户端指向该服务" id="agentic-point-clients">
    Cloud MCP endpoint 对所有服务都相同——请求通过 `x-service-id` 请求头进行路由，若不携带该请求头，请求会发往你的账户使用的第一个 ClickStack service。复制现有的 MCP 配置，并加上携带新 read-only service ID 的请求头：

    ```shell theme={null}
    claude mcp add --transport http clickstack https://mcp.clickhouse.cloud/clickstack \
      --header "x-service-id: <read-only-agent-service-id>"
    ```

    任何 MCP client 都可以携带该请求头——关于 Cursor、VS Code 等工具中的等效配置，请参见[指向特定服务](/docs/zh/clickstack/mcp#managed-service-override)。
  </Step>
</Steps>

<Warning>
  **MCP 会将状态写入其所指向的服务**

  MCP 服务器除了执行查询，还可以创建仪表盘、告警和 saved searches，而这些状态与所有 [ClickStack 状态](#point-clickstack)一样，只作用于请求被路由到的那个服务。agent 在智能体服务上创建的仪表盘，不会出现在由面向工程师的那个服务启动的 ClickStack UI 中；它在那里创建的 alert 也会在该服务的 compute 上进行评估——而处于 idling 状态的智能体服务会延迟甚至漏掉这些评估，详见[下文](#isolating-alerts)。对于预期会创建需长期保留的制品的智能体，请将其路由到你的团队所使用的同一个服务。
</Warning>

<h2 id="alerts">
  告警
</h2>

ClickStack 会在创建该告警的 service 上评估告警,因此告警与 UI 运行在同一套 compute 上——在本拓扑中即 read-only service。

<Note>
  **托管 ClickStack**

  要启用告警,至少需要有一位拥有 **Service Admin** 权限的用户登录过一次 ClickStack。这会 provision 出用于运行告警 query 的专用 database user,该用户在 warehouse 中的所有 service 之间共享。请参阅我们的指南[为托管 ClickStack 授予访问权限](/docs/zh/clickstack/deployment/managed#configure-access)。
</Note>

告警评估属于周期性的 query workload。在为 read-only service 做容量规划时,应将其计入 QPS——[sizing model](/docs/zh/clickstack/managing/estimating-resources#refining-sizing-assumptions) 会把搜索、dashboard 和告警 query 合并为一个汇总数值来计算。

<h3 id="isolating-alerts">
  隔离告警评估
</h3>

告警负载无法集中路由，因为告警是由用户创建的：谁在 ClickStack 中添加告警，该告警就会落到他当时所用的 service 上，并消耗该 service 的 compute 进行评估。没有任何 SETTING 能把某个 service 的告警挪到别处。

你真正能隔离的，是由你集中维护的那部分告警——即平台团队为整个 organization 维护的告警，它们通常也是评估最频繁的。可以在 warehouse 中为它们单独准备一个 read-only service，并从该 service 上启动的 ClickStack 中创建这些告警：

| Service | Type | 服务对象 |
| - | - | - |
| 摄取 | Read-write | OpenTelemetry collector |
| 查询 | Read-only | ClickStack UI，以及用户自行创建的告警 |
| 告警 | Read-only | 由平台团队维护的公共告警 |

<Warning>
  **关闭告警 service 上的 auto-idling**

  在某个 service 上配置了告警并不会让它保持唤醒。落到已休眠 service 上的告警评估会因唤醒过程而延迟，甚至直接失败，因此若告警 service 仍启用了 auto-idling，就可能漏掉评估。请关闭该 service 的 auto-idling，并按始终在线来规划。这一点对任何评估告警的地方都同样适用：如果告警运行在承载 UI 的 service 上，那么该 service 也不能任其休眠。
</Warning>

其余的 trade-off 都源自状态按 service 隔离这一事实：

* 公共告警以及与之配套的仪表盘只存在于告警 service 上，在查询 service 上工作的用户看不到它们。但无论哪种方式，通知都会发送到相同的[目标端](/docs/zh/clickstack/features/alerts)，因此用户失去的只是对定义的可见性，而非告警能力本身。
* 告警 service 上的 source 是独立的对象。使用[默认 OpenTelemetry schema](/docs/zh/clickstack/deployment/managed#adding-data-sources) 的 source 会被自动检测，但自定义 source 也必须在这里配置一遍，告警才能引用它们。

如果这一组告警本身规模很小，其评估负载相对于仪表盘流量只是舍入误差，那就把所有内容都放在同一个 read-only service 上——在两处维护定义带来的运维成本，才是二者中更大的那一项。

<h2 id="considerations">
  进一步考量
</h2>

**自动休眠。** 对已休眠的只读 service 发起的第一条查询需要等待该 service 启动，因此间歇性使用相当于用少量延迟换取更低的花费。不要指望用告警来阻止休眠——如[上文](#isolating-alerts)所述，对于你依赖其评估告警的 service，请禁用自动休眠。持续摄取确实能让读写 service 保持唤醒，但如果摄取是间歇性的或按计划执行的，休眠期结束后的第一个批次同样需要等待，表现为遥测数据延迟。

**备份。** 备份仅在主节点 service 上进行，覆盖整个仓库的数据。恢复备份会创建一个全新的 service，它与现有仓库并无关联。

**副本数量限制。** 仓库中所有 service 的副本总数默认存在上限——参见[使用限制](/docs/zh/products/cloud/guides/best-practices/usagelimits)。

**将 ClickStack 与其他工作负载隔离。** 如果你要将 ClickStack 添加到已运行其他工作负载 (例如实时应用 analytics) 的 service 上，可以借助同样的仓库特性为可观测性分配独立的 compute。请参阅我们的指南[隔离可观测性工作负载](/docs/zh/clickstack/managing/estimating-resources#isolating-workloads)。

有关仓库的完整行为与限制，请参阅我们的指南[仓库](/docs/zh/products/cloud/features/infrastructure/warehouses)。
