Skip to content

Commit 9a48af8

Browse files
authored
Merge pull request #796 from ClickHouse/07/17/26/fix_writing_with_v2
[writing data] RowBinaryInsert with Client V2
2 parents eee0ec3 + d3cc058 commit 9a48af8

14 files changed

Lines changed: 808 additions & 224 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# 1.5.0 (not published)
2+
3+
## Improvements
4+
* RowBinary inserts with client V2 now stream data directly to the network output stream via the client's
5+
`DataStreamWriter` API instead of first serializing the whole batch into an unsized `ByteArrayOutputStream`
6+
(which caused repeated internal array reallocations and copies) and then copying it again through a
7+
`ByteArrayInputStream`. Serialization is deterministic, so client-level retries resend a byte-identical
8+
block and ClickHouse block deduplication (and therefore exactly-once delivery) is unaffected.
9+
The record serialization loop is now shared between the V1 and V2 insert paths. Since client V2 transmits
10+
the INSERT statement as an HTTP query parameter, feature test coverage was added for table names containing
11+
URL-special characters (space, `+`, `&`, `=`, `%`, `?`, `#`), SQL quotes, and non-ASCII characters.
12+
113
# 1.4.0, 2026-07-15
214

315
## Security

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v1.4.0
1+
v1.5.0

benchmark/README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ different components of ClickHouse Kafka connector. The test scope can be small
77

88
## How to Run
99

10-
As simple as:
10+
This is a standalone project that depends on the connector artifact by version,
11+
resolved from the local Maven repository. Publish the connector locally first
12+
(run from the repository root):
13+
```shell
14+
./gradlew publishToMavenLocal
15+
```
16+
17+
Then, from this `benchmark` directory, run:
1118
```shell
1219
./gradlew run --args="-b kafka_connector.SimpleBenchmark -i 2 -t 10"
1320
```
@@ -18,6 +25,33 @@ two (2) iterations for ten (10) seconds each.
1825
Two files will be created as output. One with `.json` extension is benchmark report that can be visualized with https://jmh.morethan.io/.
1926
Another file with `.out` extension is standard output of the JMH.
2027

28+
### Insert benchmark (V1 vs V2)
29+
30+
Use this benchmark to compare insert performance between client `V1` and `V2` for:
31+
- `rowbinary` inserts (schema records)
32+
- `json` inserts (schemaless map records)
33+
- `string` inserts (JSONEachRow payload)
34+
35+
It runs with data sizes: `10000`, `50000`, `100000` rows.
36+
37+
```shell
38+
./gradlew run --args="-b kafka_connector.InsertClientBenchmark -i 3 -t 15"
39+
```
40+
41+
By default, the benchmark starts a temporary ClickHouse container via Testcontainers.
42+
You can point it to an external ClickHouse instance with environment variables:
43+
44+
```shell
45+
CLICKHOUSE_HOST=localhost \
46+
CLICKHOUSE_PORT=8123 \
47+
CLICKHOUSE_USER=default \
48+
CLICKHOUSE_PASSWORD= \
49+
CLICKHOUSE_SSL=false \
50+
./gradlew run --args="-b kafka_connector.InsertClientBenchmark -i 3 -t 15"
51+
```
52+
53+
Optional: override default Testcontainers image with `CLICKHOUSE_IMAGE`.
54+
2155
### Options
2256

2357
`-b <benchmark class>` - run specific benchmark.

benchmark/build.gradle.kts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,20 @@ java {
3232
}
3333

3434
dependencies {
35+
// The connector is the single source of truth for the ClickHouse Java client, the Kafka
36+
// Connect API, and (via its test fixtures) the Schema Registry / protobuf stack. They all
37+
// arrive transitively as `api` dependencies; re-declaring any of them here would let the
38+
// benchmark silently measure a different version set than the connector ships with.
3539
implementation("com.clickhouse.kafka:clickhouse-kafka-connect:${connectorVersion}")
36-
implementation("com.clickhouse.kafka:clickhouse-kafka-connect:${connectorVersion}:test-fixtures")
37-
implementation("org.apache.kafka:connect-api:2.7.0")
40+
implementation(testFixtures("com.clickhouse.kafka:clickhouse-kafka-connect:${connectorVersion}"))
3841

39-
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
40-
implementation("org.apache.commons:commons-lang3:3.18.0")
42+
// Used directly by the benchmarks themselves, versioned from the connector's catalog.
43+
implementation(libs.testcontainers)
44+
implementation(libs.testcontainers.clickhouse)
4145

4246
implementation("commons-cli:commons-cli:1.5.0")
4347
implementation("org.openjdk.jmh:jmh-core:1.37")
4448
implementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
4549
annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
46-
47-
implementation("com.google.protobuf:protobuf-java:3.25.1")
48-
implementation("io.confluent:kafka-protobuf-serializer:7.9.1")
49-
implementation("io.confluent:kafka-connect-protobuf-converter:7.9.1")
50-
51-
// // Schema Registry client for testing
52-
implementation("io.confluent:kafka-schema-registry-client:7.5.4")
53-
implementation("io.confluent:kafka-schema-registry:7.5.4")
54-
implementation("io.confluent:kafka-schema-serializer:7.5.4")
5550
}
5651

benchmark/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This file was generated by the Gradle 'init' task.
22
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
33

4-
org.gradle.configuration-cache=true
4+
org.gradle.configuration-cache=false
55
org.gradle.parallel=true
66
org.gradle.caching=true
77

benchmark/gradle/libs.versions.toml

Lines changed: 0 additions & 2 deletions
This file was deleted.

benchmark/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

benchmark/settings.gradle.kts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
/*
2-
* This file was generated by the Gradle 'init' task.
3-
*
42
* The settings file is used to specify which projects to include in your build.
53
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.14.2/userguide/multi_project_builds.html in the Gradle documentation.
6-
* This project uses @Incubating APIs which are subject to change.
74
*/
85

96
rootProject.name = "kafka-connector-benchmark"
7+
8+
/*
9+
* This is a standalone build. It does NOT use a Gradle composite build to consume
10+
* the connector from the parent project. Instead, it depends on the published
11+
* connector artifact by version, resolved from the local Maven repository (~/.m2).
12+
*
13+
* Before running the benchmark, publish the connector locally from the repo root:
14+
* ./gradlew publishToMavenLocal
15+
*/
16+
17+
// Reuse the connector's version catalog so the few libraries the benchmark has to
18+
// declare itself stay pinned to the versions the connector was built against.
19+
dependencyResolutionManagement {
20+
versionCatalogs {
21+
create("libs") {
22+
from(files("../gradle/libs.versions.toml"))
23+
}
24+
}
25+
}

benchmark/src/main/java/kafka_connector/BenchmarkMain.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import org.apache.commons.cli.OptionGroup;
99
import org.apache.commons.cli.ParseException;
1010
import org.openjdk.jmh.annotations.Mode;
11+
import org.openjdk.jmh.profile.GCProfiler;
12+
import org.openjdk.jmh.profile.MemPoolProfiler;
1113
import org.openjdk.jmh.results.format.ResultFormatType;
1214
import org.openjdk.jmh.runner.Runner;
1315
import org.openjdk.jmh.runner.RunnerException;
@@ -82,8 +84,8 @@ public static void main(String[] args) throws RunnerException {
8284
.warmupTime(TimeValue.seconds(15))
8385
.mode(Mode.SampleTime)
8486
.timeUnit(TimeUnit.MILLISECONDS)
85-
// .addProfiler(GCProfiler.class)
86-
// .addProfiler(MemPoolProfiler.class)
87+
.addProfiler(GCProfiler.class)
88+
.addProfiler(MemPoolProfiler.class)
8789
.jvmArgs("-Xms4g", "-Xmx4g")
8890
.resultFormat(ResultFormatType.JSON)
8991
.output(outputFile)

0 commit comments

Comments
 (0)