> ## 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.

> You can stream JSON messages from Pub/Sub into ClickHouse using a Google Dataflow template

# Dataflow Pub/Sub to ClickHouse template

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

The Pub/Sub to ClickHouse template is a streaming pipeline that reads JSON-encoded messages from a Pub/Sub subscription and writes them into a ClickHouse table.
Messages that fail to parse or fail to map to the target schema are routed to a dead-letter destination: a ClickHouse table, a Pub/Sub topic, or both.

<h2 id="pipeline-requirements">
  Pipeline requirements
</h2>

* The source Pub/Sub subscription must exist.
* Messages published to the subscription must be valid JSON.
* The target ClickHouse table must exist, and its column names must match the field names in the JSON payload.
* The ClickHouse host must be accessible from the Dataflow worker machines.
* At least one dead-letter destination (`clickHouseDeadLetterTable` or `deadLetterTopic`) must be provided. If both are provided, failed messages are routed to both destinations simultaneously.
* When `clickHouseDeadLetterTable` is set, the dead-letter table must already exist in ClickHouse with the schema shown in [Dead-letter handling](#dead-letter-handling).
* When `deadLetterTopic` is set, the Pub/Sub topic must already exist.

<h2 id="template-parameters">
  Template parameters
</h2>

<br />

<br />

| Parameter Name              | Parameter Description                                                                                                                                                            | Required | Notes                                                                                                                                                                                       |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inputSubscription`         | The Pub/Sub subscription to read messages from. Example: `projects/<PROJECT_ID>/subscriptions/<SUBSCRIPTION_NAME>`.                                                              | ✅        | Messages must be JSON-encoded.                                                                                                                                                              |
| `clickHouseUrl`             | The ClickHouse endpoint URL. Use `https://` for SSL connections (ClickHouse Cloud) or `http://` for non-SSL connections. Example: `https://<HOST>:8443` or `http://<HOST>:8123`. | ✅        | For ClickHouse Cloud, use the HTTPS endpoint on port `8443`.                                                                                                                                |
| `clickHouseDatabase`        | The name of the ClickHouse database where the target table resides. Example: `default`.                                                                                          | ✅        |                                                                                                                                                                                             |
| `clickHouseTable`           | The name of the ClickHouse table to write data into.                                                                                                                             | ✅        | The table must exist before running the pipeline.                                                                                                                                           |
| `clickHouseUsername`        | The username to authenticate with ClickHouse.                                                                                                                                    | ✅        |                                                                                                                                                                                             |
| `clickHousePassword`        | The password to authenticate with ClickHouse.                                                                                                                                    | ✅        |                                                                                                                                                                                             |
| `clickHouseDeadLetterTable` | The ClickHouse table to write failed messages into. Example: `my_table_dead_letter`.                                                                                             |          | At least one of `clickHouseDeadLetterTable` or `deadLetterTopic` must be provided. The table must exist with the dead-letter schema shown in [Dead-letter handling](#dead-letter-handling). |
| `deadLetterTopic`           | The Pub/Sub topic to publish failed messages to. Example: `projects/<PROJECT_ID>/topics/<TOPIC_NAME>`.                                                                           |          | At least one of `clickHouseDeadLetterTable` or `deadLetterTopic` must be provided. Failed payloads are published to the topic with `errorMessage` and `failedAt` set as message attributes. |
| `windowSeconds`             | Duration in seconds for time-based batching windows.                                                                                                                             |          | See [Batching and windowing](#batching-and-windowing) for the interaction with `batchRowCount`. If neither is set, combined mode uses defaults of `30s` and `1000` rows.                    |
| `batchRowCount`             | Number of rows to accumulate before flushing to ClickHouse.                                                                                                                      |          | See [Batching and windowing](#batching-and-windowing) for the interaction with `windowSeconds`.                                                                                             |
| `maxInsertBlockSize`        | Maximum number of rows per `INSERT` statement sent to ClickHouse. Defaults to `1,000,000`.                                                                                       |          | A `ClickHouseIO` option.                                                                                                                                                                    |
| `maxRetries`                | Maximum number of retry attempts for failed ClickHouse inserts. Defaults to `5`.                                                                                                 |          | A `ClickHouseIO` option.                                                                                                                                                                    |
| `insertDeduplicate`         | Whether to enable deduplication for `INSERT` queries in replicated ClickHouse tables. Defaults to `true`.                                                                        |          | A `ClickHouseIO` option.                                                                                                                                                                    |
| `insertQuorum`              | For `INSERT` queries in replicated tables, wait for the specified number of replicas to acknowledge the write and linearize the data addition. `0` disables quorum writes.       |          | A `ClickHouseIO` option. Disabled in default server settings.                                                                                                                               |
| `insertDistributedSync`     | If enabled, `INSERT` queries into distributed tables wait until data is sent to all nodes in the cluster. Defaults to `true`.                                                    |          | A `ClickHouseIO` option.                                                                                                                                                                    |

<Note>
  Default values for all `ClickHouseIO` parameters can be found in [`ClickHouseIO` Apache Beam Connector](/docs/integrations/connectors/data-ingestion/etl-tools/apache-beam#clickhouseiowrite-parameters).
</Note>

<h2 id="message-format-and-schema-mapping">
  Message format and schema mapping
</h2>

Pub/Sub messages must be JSON objects whose top-level field names exactly match the column names of the target ClickHouse table.

To map incoming messages onto the target table, the pipeline performs the following at startup:

1. Fetches the schema of the target ClickHouse table.
2. Builds a Beam `Row` schema from that ClickHouse schema.
3. For each incoming Pub/Sub message, parses the JSON payload and assembles a row by reading the fields named in the ClickHouse schema.

<br />

<Warning>
  JSON field names must exactly match the ClickHouse column names (matching is case-sensitive). Fields in the message that do not correspond to a ClickHouse column are ignored. If a ClickHouse column has no matching field in the JSON payload, the pipeline attempts to write `NULL` for that column — which only succeeds when the column is declared as [`Nullable`](/docs/reference/data-types/nullable). Messages that fail to parse, whose values cannot be coerced into the column type, or that would write `NULL` to a non-nullable column, are routed to the dead-letter destination.
</Warning>

<h3 id="type-conversion">
  Type conversion
</h3>

JSON values are coerced into the corresponding ClickHouse column type:

| ClickHouse Type                                                                    | Notes                                                                                                          |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [`Float32`](/docs/reference/data-types/float)                                           | Parsed via `Float.valueOf`.                                                                                    |
| [`Float64`](/docs/reference/data-types/float)                                           | Parsed via `Double.valueOf`.                                                                                   |
| [`Date`](/docs/reference/data-types/date)                                               | Parsed as an ISO-8601 date string.                                                                             |
| [`DateTime`](/docs/reference/data-types/datetime)                                       | Parsed as an ISO-8601 datetime string (e.g. `2026-01-15T12:34:56Z`).                                           |
| [`Array(T)`](/docs/reference/data-types/array)                                          | JSON array; each element is converted to the element type `T`. Empty or missing arrays produce an empty array. |
| Integer types (`Int8`/`Int16`/`Int32`/`Int64`, `UInt8`/`UInt16`/`UInt32`/`UInt64`) | Parsed from the JSON number or its string representation.                                                      |
| [`String`](/docs/reference/data-types/string)                                           | Used as-is for textual fields; non-textual JSON nodes are serialized to their JSON string form.                |

<h2 id="batching-and-windowing">
  Batching and windowing
</h2>

Because the pipeline is streaming, incoming rows are accumulated into windows before being flushed to ClickHouse. The windowing strategy is selected from the parameters you provide:

| `windowSeconds` | `batchRowCount` | Behavior                                                                                                  |
| --------------- | --------------- | --------------------------------------------------------------------------------------------------------- |
| set             | unset           | Time-based fixed windows of `windowSeconds`.                                                              |
| unset           | set             | Global window with a count trigger; fires every `batchRowCount` rows.                                     |
| both set        | both set        | Global window with a combined trigger; fires on whichever condition is met first (time **or** row count). |
| neither set     | neither set     | Combined mode with defaults: `30` seconds or `1000` rows, whichever comes first.                          |

Tuning these values lets you trade latency against insert efficiency. Smaller windows reduce end-to-end latency; larger windows produce fewer, larger `INSERT` batches.

<h2 id="dead-letter-handling">
  Dead-letter handling
</h2>

Messages that fail JSON parsing, schema mapping, or type coercion are routed to the configured dead-letter destination(s). At least one of `clickHouseDeadLetterTable` or `deadLetterTopic` must be provided; if both are set, failed messages are sent to both.

<h3 id="clickhouse-dead-letter-table">
  ClickHouse dead-letter table
</h3>

When `clickHouseDeadLetterTable` is set, the dead-letter table must already exist with this fixed schema:

| Column          | Type       | Description                                            |
| --------------- | ---------- | ------------------------------------------------------ |
| `raw_message`   | `String`   | The original Pub/Sub message payload as UTF-8 text.    |
| `error_message` | `String`   | The exception message describing why the row failed.   |
| `stack_trace`   | `String`   | The full Java stack trace captured at failure time.    |
| `failed_at`     | `DateTime` | The processing-time timestamp at which the row failed. |

A minimal definition for a single-node deployment:

```sql theme={null}
CREATE TABLE my_table_dead_letter (
    raw_message   String,
    error_message String,
    stack_trace   String,
    failed_at     DateTime
) ENGINE = MergeTree()
ORDER BY failed_at;
```

<Note>
  Adapt the engine and `ORDER BY` clause for your deployment — use `ReplicatedMergeTree` for replicated tables, add `ON CLUSTER` for distributed setups, and adjust partitioning or TTL as needed.
</Note>

<h3 id="pubsub-dead-letter-topic">
  Pub/Sub dead-letter topic
</h3>

When `deadLetterTopic` is set, each failed message is republished to the topic with:

* **Payload**: the original message bytes.
* **Attribute** `errorMessage`: the exception message captured at failure time.
* **Attribute** `failedAt`: the processing-time timestamp at which the row failed.

This makes it convenient to replay failed messages once the underlying schema or producer issue has been resolved.

<h2 id="running-the-template">
  Running the template
</h2>

The Pub/Sub to ClickHouse template is available from the Google Cloud Console.

<Note>
  Be sure to review this document, and specifically the above sections, to fully understand the template's configuration requirements and prerequisites.
</Note>

Sign in to your Google Cloud Console and search for Dataflow.

1. Press the `CREATE JOB FROM TEMPLATE` button.
   <Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/google-dataflow/create_job_from_template_button.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=ca429a13d8a9e99c43ae477bf14ad1a9" border alt="Dataflow console" width="1872" height="886" data-path="images/integrations/data-ingestion/google-dataflow/create_job_from_template_button.webp" />

2. Once the template form is open, enter a job name and select the desired region.

3. In the `Dataflow Template` input, type `ClickHouse` or `Pub/Sub`, and select the `Pub/Sub to ClickHouse` template.

4. Once selected, the form expands. Fill in:

   * The Pub/Sub input subscription, in the form `projects/<PROJECT_ID>/subscriptions/<SUBSCRIPTION_NAME>`.
   * The ClickHouse endpoint URL — for ClickHouse Cloud use `https://<HOST>:8443`.
   * The ClickHouse database, target table, username and password.
   * At least one dead-letter destination: a ClickHouse table or a Pub/Sub topic (or both).

5. Optionally customize batching (`windowSeconds`, `batchRowCount`) and `ClickHouseIO` tuning parameters, as detailed in the [Template parameters](#template-parameters) section.

<h3 id="monitor-the-job">
  Monitor the job
</h3>

Navigate to the [Dataflow Jobs tab](https://console.cloud.google.com/dataflow/jobs) in your Google Cloud Console to monitor the status of the job. You'll find the job details, including progress and any errors:

<Image img="https://mintcdn.com/private-7c7dfe99/pIetLsS_hOGHqoPJ/images/integrations/data-ingestion/google-dataflow/pubsub-inqueue-job.webp?fit=max&auto=format&n=pIetLsS_hOGHqoPJ&q=85&s=c5922b3ad406648be710f93d856f5fe8" size="lg" border alt="Dataflow console showing a running Pub/Sub to ClickHouse job" width="3016" height="470" data-path="images/integrations/data-ingestion/google-dataflow/pubsub-inqueue-job.webp" />

The template also emits the following custom metrics under the `PubSubToClickHouse` namespace, viewable from the Dataflow job page:

| Metric                  | Type         | Description                                                                    |
| ----------------------- | ------------ | ------------------------------------------------------------------------------ |
| `messages-received`     | Counter      | Total Pub/Sub messages received by the parsing step.                           |
| `rows-parsed-ok`        | Counter      | Messages successfully converted to a row and routed to the main output.        |
| `rows-parse-failed`     | Counter      | Messages that failed parsing or schema mapping and were routed to dead-letter. |
| `message-payload-bytes` | Distribution | Distribution of incoming Pub/Sub message payload sizes, in bytes.              |

<h2 id="troubleshooting">
  Troubleshooting
</h2>

<h3 id="code-241-dbexception-memory-limit-total-exceeded">
  Memory limit (total) exceeded error (code 241)
</h3>

This error occurs when ClickHouse runs out of memory while processing large batches of data. To resolve this issue:

* Increase the instance resources: Upgrade your ClickHouse server to a larger instance with more memory to handle the data processing load.
* Decrease the batch size: Reduce `batchRowCount` (and/or `maxInsertBlockSize`) in your Dataflow job configuration to send smaller chunks of data to ClickHouse, reducing memory consumption per batch.

<h3 id="all-messages-going-to-dlq">
  All messages are going to the dead-letter destination
</h3>

The most common causes are:

* The JSON field names do not match the ClickHouse column names exactly (matching is case-sensitive).
* A column type cannot be coerced from the JSON value (for example, a non-ISO-8601 string in a `DateTime` column).
* The target table schema has changed since the pipeline started — the schema is fetched once at startup. Restart the job after applying schema changes.

Inspect the `error_message` and `stack_trace` columns of the ClickHouse dead-letter table (or the `errorMessage` attribute on Pub/Sub dead-letter messages) to identify the root cause.

<h3 id="no-rows-arriving">
  Pipeline starts but no rows arrive in ClickHouse
</h3>

* Confirm the subscription is receiving messages — check the `messages-received` metric on the Dataflow job page.
* In time-based mode (`windowSeconds` only), rows are flushed only at window boundaries. Lower `windowSeconds` to verify flushes are occurring.
* Verify network reachability between Dataflow workers and the ClickHouse endpoint (firewall, VPC peering, or private service connect).

<h2 id="template-source-code">
  Template source code
</h2>

The template's source code is available in:

* [`GoogleCloudPlatform/DataflowTemplates`](https://github.com/GoogleCloudPlatform/DataflowTemplates/tree/main/v2/googlecloud-to-clickhouse) — the upstream Google Cloud Platform repository.
* [`ClickHouse/DataflowTemplates`](https://github.com/ClickHouse/DataflowTemplates) — ClickHouse's fork.
