ClickHouse Kafka Connect Sink Connector

repository·main·Indexed 41 days ago

https://github.com/clickhouse/clickhouse-kafka-connect

The official Kafka Connect sink connector for ClickHouse, designed to deliver data from Kafka topics directly into ClickHouse tables. It supports all ClickHouse types, provides exactly-once delivery semantics via native block deduplication and a state machine, and includes transformations like KeyToValue and ExtractTopic.

Tokens
7.1K
Snippets
20
Records
27
Agent score
45%

What's inside ClickHouse Kafka Connect

  1. Core requirements of ClickHouse Kafka Connect

    main

    The ClickHouse Kafka Connect Sink is designed around four primary architectural requirements:

    1. Independence from ClickHouse: The connector operates as an architecturally independent component from the ClickHouse database.
    2. Push-based architecture: Data is pushed from Kafka to ClickHouse. ClickHouse does not need to connect to Kafka, making this suitable for environments where ClickHouse is cloud-hosted and Kafka is self-managed.
    3. Full ClickHouse type support: The connector supports all ClickHouse types, including complex types like Map. (Note: Support for Tuple and JSON types is currently in development).
    4. Exactly-once delivery: The connector ensures that every Kafka message is delivered to ClickHouse exactly one time, preventing both message loss and duplication.
  2. How exactly-once delivery is achieved

    main

    The ClickHouse Kafka Connect Sink achieves exactly-once delivery semantics by combining Kafka Connect's at-least-once delivery with ClickHouse's native block deduplication.

    Core Mechanism

    1. ClickHouse Deduplication: Replicated ClickHouse instances (including ClickHouse Cloud) use a replicated_deduplication_window to ignore identical data blocks. When a block is inserted, a hash is stored in ClickHouse Keeper. Subsequent inserts with the same hash are ignored.
    2. State Machine: To ensure that retries always produce identical blocks (a requirement for deduplication), the connector maintains a state machine per topic and partition using the KeeperMap engine. This state machine tracks the minOffset and maxOffset of the last processed batch and whether the last operation was BEFORE_PROCESSING or AFTER_PROCESSING.
    3. Deterministic Retries: If a crash occurs, the connector uses the stored state to reconstruct the exact same batches (same size, same rows, same order) that were being processed, allowing ClickHouse to recognize and deduplicate them.

    Requirements for Deduplication

    For deduplication to work, blocks must be identical: they must have the same size and the same rows in the same order. The connector ensures this by using a state machine to manage batch boundaries and offsets.

  3. Understand the Sink State Machine logic

    main

    The connector uses a state machine to reconcile the current Kafka batch [currentMin, currentMax] against the previously stored state [storedMin, storedMax].

    State Transitions

    Previous FlagScenarioAction
    NONEFirst batch or crash before BEFOREInsert batch, then store AFTER
    BEFORE_PROCESSINGSame offsets(Re-)Insert batch (deduplicated by CH), then store AFTER
    BEFORE_PROCESSINGOverlapping (currentMin == storedMin)Split at storedMax. (Re-)Insert first chunk, then store AFTER for chunk; then process second chunk
    BEFORE_PROCESSINGContains (current range inside stored)ERROR: Data sent to Dead Letter Queue (DLQ)
    BEFORE_PROCESSINGNew (currentMin > storedMax)Insert batch, then store AFTER
    AFTER_PROCESSINGSame or ContainsDo nothing (already processed)
    AFTER_PROCESSINGNewStore BEFORE, insert batch, store AFTER
    AFTER_PROCESSINGOverlappingInsert only the new records (the part after storedMax)

    Batch Cropping

    If the current batch starts before the previous state (currentMin < storedMin), the connector drops the prefix (all records where offset < storedMin) and then applies the state machine rules to the remaining range.

  4. Run a Full-Stack Local Environment with Docker Compose

    main

    You can run a complete local environment containing Kafka (broker, schema-registry, connect, control-center, ksqldb-server, rest-proxy) and ClickHouse using Docker Compose.

    Prerequisites:

    • Ensure you have a ~/kafka-full.yml file configured.
    • Replace the CLUSTER_ID in the broker service with a unique base64 UUID generated via bin/kafka-storage.sh random-uuid.
    • Note that CLASSPATH is required in the connect service due to CC-2422.

    Execution: Run the following command to start the stack:

    docker compose -f ~/kafka-full.yml up
  5. Use the Log Analyzer tool to investigate Kafka logs

    main

    The log-analyzer.jar tool allows you to investigate logs for specific Kafka topics, partitions, and offsets. It provides a summary of logs and highlights areas of interest, such as failed batches or specific query IDs.

    To analyze all logs in a directory: java -jar log-analyzer.jar <path_to_logs>

    To investigate a specific Kafka location, use the -topic, -partition, and -offset flags. This provides a summary of the logs specifically around that offset.

    # Analyze all logs in a directory
    java -jar log-analyzer.jar ~/Desktop/Logs
    
    # Analyze logs for a specific topic, partition, and offset
    java -jar log-analyzer.jar ~/Desktop/Logs -topic=<topicName> -partition=<partitionNumber> -offset=<offset>
  6. Compare insert performance (V1 vs V2)

    main

    You can use the kafka_connector.InsertClientBenchmark to compare the insert performance between client V1 and V2 across different formats and data sizes:

    • Formats supported: rowbinary (schema records), json (schemaless map records), and string (JSONEachRow payload).
    • Data sizes: 10000, 50000, and 100000 rows.

    By default, the benchmark uses Testcontainers to start a temporary ClickHouse instance. To use an external ClickHouse instance instead, provide the following environment variables:

    Environment VariableDescription
    CLICKHOUSE_HOSTHost of the external ClickHouse instance
    CLICKHOUSE_PORTPort of the external ClickHouse instance
    CLICKHOUSE_USERClickHouse username
    CLICKHOUSE_PASSWORDClickHouse password
    CLICKHOUSE_SSLSet to false to disable SSL
    CLICKHOUSE_IMAGE(Optional) Override the default Testcontainers image

    Example using an external instance:

    CLICKHOUSE_HOST=localhost \
    CLICKHOUSE_PORT=8123 \
    CLICKHOUSE_USER=default \
    CLICKHOUSE_PASSWORD= \
    CLICKHOUSE_SSL=false \
    ./gradlew run --args="-b kafka_connector.InsertClientBenchmark -i 3 -t 15"
  7. Run a Quarter-Stack setup (Cloud Kafka/ClickHouse + Local Connector)

    main

    Use this setup when you want to connect to existing cloud-hosted Kafka and ClickHouse instances using a local Dockerized Kafka Connect instance.

    1. Ensure your .env file is configured with your cloud credentials.
    2. Run the following command:
    docker compose -f ~/kafka-connect.yml up

    Key Configuration Details:

    • The connect service uses confluentinc/cp-kafka-connect:latest.
    • It maps ports 8083 (REST API), 7778 (JMX), and 8020 (Prometheus JMX).
    • It requires a CLASSPATH for monitoring interceptors (see CC-2422).
    • The CONNECT_PLUGIN_PATH includes /usr/share/dockershare for local plugin injection.
  8. Set up Prometheus and Grafana for JMX monitoring

    main

    To monitor JMX metrics via Prometheus and Grafana, add the following services to your docker-compose.yml file. This configuration assumes you have a prometheus.yml configuration file located in a local directory mapped to ~/DockerShare.

     prometheus:
       hostname: prometheus
       container_name: prometheus
       image: prom/prometheus
       volumes:
         - "~/DockerShare:/usr/share/dockershare/:ro"
       command:
         - '--config.file=/usr/share/dockershare/prometheus.yml'
       ports:
         - 9090:9090
    
     grafana:
       image: grafana/grafana
       hostname: grafana
       container_name: grafana
       depends_on:
         - prometheus
       volumes:
         - "~/DockerShare:/usr/share/dockershare/:ro"
       ports:
         - 3000:3000
  9. Run ClickHouse Kafka Connect benchmarks

    main

    The benchmark project uses JMH (Java Microbenchmark Harness) to test different components of the ClickHouse Kafka connector, ranging from specific type conversions to end-to-end record conversion.

    Before running benchmarks, you must publish the connector to your local Maven repository from the repository root:

    ./gradlew publishToMavenLocal

    To execute a benchmark, use the ./gradlew run command from the benchmark directory with the appropriate --args.

    ./gradlew run --args="-b kafka_connector.SimpleBenchmark -i 2 -t 10"
  10. Run a Half-Stack setup (Cloud ClickHouse + Local Kafka)

    main

    Use this setup when you want to run a full local Kafka ecosystem (Broker, Schema Registry, Connect, Control Center, ksqlDB, and REST Proxy) while connecting to a cloud-hosted ClickHouse instance.

    1. Run the following command:
    docker compose -f ~/kafka-half.yml up

    Important Requirements:

    • Cluster ID: You must replace the CLUSTER_ID in the broker service with a unique base64 UUID. You can generate one using the Kafka storage tool: bin/kafka-storage.sh random-uuid.
    • Classpath: A CLASSPATH is required for monitoring interceptors due to CC-2422.
    • Services Provided:
      • broker: Kafka broker (KRaft mode).
      • schema-registry: Confluent Schema Registry.
      • connect: Kafka Connect instance.
      • control-center: Confluent Control Center UI (port 9021).
      • ksqldb-server: ksqlDB server (port 8088).
      • rest-proxy: Kafka REST Proxy (port 8082).