This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-12177-c2bb21809472e0874c8594e3f31b3c441fc6dc19 in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit 10ed8d433f07b01591d1c8cc699a8d7b7fe0e2a9 Author: zhiweiniu <[email protected]> AuthorDate: Tue Sep 8 12:15:49 2026 +0000 [Test][Zeta] Add ProtoStuff serializer microbenchmarks (#12177) --- .github/workflows/benchmarks.yml | 1 + docs/en/engines/zeta/benchmark.md | 11 +++ docs/zh/engines/zeta/benchmark.md | 10 +++ seatunnel-benchmarks/README.md | 6 ++ .../benchmark/ProtoStuffSerializerBenchmark.java | 97 ++++++++++++++++++++ .../ProtoStuffSerializerBenchmarkTest.java | 100 +++++++++++++++++++++ 6 files changed, 225 insertions(+) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index dcbd8fa654..62721a1476 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -37,6 +37,7 @@ on: - 'SeaTunnelRowBenchmark' - 'IntermediateQueueBenchmark' - 'DebeziumJsonFormatBenchmark' + - 'ProtoStuffSerializerBenchmark' - 'SeaTunnelPipelineBenchmark' - 'CheckpointingTimeBenchmark' - 'CheckpointStorageBenchmark' diff --git a/docs/en/engines/zeta/benchmark.md b/docs/en/engines/zeta/benchmark.md index ed760947cf..3400737f67 100644 --- a/docs/en/engines/zeta/benchmark.md +++ b/docs/en/engines/zeta/benchmark.md @@ -178,6 +178,17 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar SeaTunnelRowBenchmark \ For a quick functional validation, add `-f 1 -wi 0 -i 1 -r 1s` to shorten the run. A single un-warmed sample is not valid performance evidence. +### Run the ProtoStuff Serializer Microbenchmarks + +```bash +java -jar seatunnel-benchmarks/target/benchmarks.jar ProtoStuffSerializerBenchmark \ + -rf json -rff seatunnel-benchmarks/target/protostuff.json +``` + +Measures in-memory `IMapFileData` serialization and deserialization throughput (`ops/ms`) with four +threads by default. Both methods exercise shared schema-cache lookup, excluding file I/O and +Hazelcast scheduling. + ### Run the Checkpoint Benchmark ```bash diff --git a/docs/zh/engines/zeta/benchmark.md b/docs/zh/engines/zeta/benchmark.md index 754e20f96e..d30019eae6 100644 --- a/docs/zh/engines/zeta/benchmark.md +++ b/docs/zh/engines/zeta/benchmark.md @@ -169,6 +169,16 @@ java -jar seatunnel-benchmarks/target/benchmarks.jar SeaTunnelRowBenchmark \ 快速功能验证时可以增加 `-f 1 -wi 0 -i 1 -r 1s` 缩短运行时间。没有预热且只有一个样本的 结果不能用于性能结论。 +### 运行 ProtoStuff 序列化微基准 + +```bash +java -jar seatunnel-benchmarks/target/benchmarks.jar ProtoStuffSerializerBenchmark \ + -rf json -rff seatunnel-benchmarks/target/protostuff.json +``` + +默认使用 4 个线程,测量 `IMapFileData` 的内存序列化和反序列化吞吐(`ops/ms`)。 +两个方法均覆盖共享 Schema 缓存的查询路径,不包含文件 I/O 和 Hazelcast 调度。 + ### 运行 Checkpoint 基准测试 ```bash diff --git a/seatunnel-benchmarks/README.md b/seatunnel-benchmarks/README.md index 10b95b7e90..b673cb9ec1 100644 --- a/seatunnel-benchmarks/README.md +++ b/seatunnel-benchmarks/README.md @@ -41,6 +41,12 @@ Run only the `SeaTunnelRow` benchmarks: java -jar seatunnel-benchmarks/target/benchmarks.jar SeaTunnelRowBenchmark ``` +Run only the `ProtoStuffSerializer` benchmarks: + +```bash +java -jar seatunnel-benchmarks/target/benchmarks.jar ProtoStuffSerializerBenchmark +``` + Write JSON results: ```bash diff --git a/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmark.java b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmark.java new file mode 100644 index 0000000000..8cbfd99b5c --- /dev/null +++ b/seatunnel-benchmarks/src/main/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmark.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.benchmark; + +import org.apache.seatunnel.engine.imap.storage.file.bean.IMapFileData; +import org.apache.seatunnel.engine.serializer.protobuf.ProtoStuffSerializer; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.Threads; + +import java.util.concurrent.TimeUnit; + +/** + * Pure in-memory microbenchmarks for the steady-state ProtoStuff WAL record codec. + * + * <p>One operation encodes or decodes one IMapFileData envelope, not its nested key/value objects. + * No filesystem, Hazelcast, or Zeta runtime is started. Thread-local fixtures share the production + * serializer's static schema cache without sharing mutable input objects. Four threads are used by + * default to exercise concurrent lookup on the standard four-vCPU runner. IMapFileData is not a + * serializer wrapper type: both measured methods call getSchema on every invocation, even after + * setup initializes the schema. + */ +@Threads(4) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork( + value = 3, + jvmArgsAppend = { + "-Xms4g", + "-Xmx4g", + "-XX:+UseG1GC", + "-XX:+AlwaysPreTouch", + "-XX:+DisableExplicitGC", + "-XX:ActiveProcessorCount=4", + "-Djava.net.preferIPv4Stack=true" + }) +public class ProtoStuffSerializerBenchmark extends BenchmarkBase { + + private ProtoStuffSerializer serializer; + private IMapFileData record; + private byte[] serializedRecord; + + /** Prepare a fixed WAL record and initialize its schema outside the measured methods. */ + @Setup + public void setUp() { + serializer = new ProtoStuffSerializer(); + char[] payload = new char[1024]; + for (int index = 0; index < payload.length; index++) { + payload[index] = (char) ('a' + index % 26); + } + record = + IMapFileData.builder() + .deleted(false) + .key(serializer.serialize(1001L)) + .keyClassName(Long.class.getName()) + .value(serializer.serialize(new String(payload))) + .valueClassName(String.class.getName()) + .timestamp(1_700_000_000_000L) + .build(); + serializedRecord = serializer.serialize(record); + if (!record.equals(serializer.deserialize(serializedRecord, IMapFileData.class))) { + throw new IllegalStateException("ProtoStuff WAL record fixture failed to round-trip"); + } + } + + /** Encode one WAL envelope, including the serializer's normal buffer/output allocations. */ + @Benchmark + public byte[] serializeWalRecord() { + return serializer.serialize(record); + } + + /** Decode one prepared WAL envelope into a newly allocated object. */ + @Benchmark + public IMapFileData deserializeWalRecord() { + return serializer.deserialize(serializedRecord, IMapFileData.class); + } +} diff --git a/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmarkTest.java b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmarkTest.java new file mode 100644 index 0000000000..a11d4d4d30 --- /dev/null +++ b/seatunnel-benchmarks/src/test/java/org/apache/seatunnel/benchmark/ProtoStuffSerializerBenchmarkTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.seatunnel.benchmark; + +import org.apache.seatunnel.engine.imap.storage.file.bean.IMapFileData; +import org.apache.seatunnel.engine.serializer.protobuf.ProtoStuffSerializer; + +import org.junit.jupiter.api.Test; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Threads; + +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +class ProtoStuffSerializerBenchmarkTest { + + @Test + void shouldExerciseConcurrentSchemaLookupByDefault() { + assertEquals(4, ProtoStuffSerializerBenchmark.class.getAnnotation(Threads.class).value()); + assertArrayEquals( + new Mode[] {Mode.Throughput}, + ProtoStuffSerializerBenchmark.class.getAnnotation(BenchmarkMode.class).value()); + assertEquals( + TimeUnit.MILLISECONDS, + ProtoStuffSerializerBenchmark.class.getAnnotation(OutputTimeUnit.class).value()); + Fork fork = ProtoStuffSerializerBenchmark.class.getAnnotation(Fork.class); + assertEquals(3, fork.value()); + assertArrayEquals( + SeaTunnelPipelineBenchmark.class.getAnnotation(Fork.class).jvmArgsAppend(), + fork.jvmArgsAppend()); + } + + @Test + void shouldRoundTripTheWalEnvelopeAndItsPreparedPayload() { + ProtoStuffSerializerBenchmark benchmark = new ProtoStuffSerializerBenchmark(); + benchmark.setUp(); + ProtoStuffSerializer serializer = new ProtoStuffSerializer(); + + IMapFileData record = benchmark.deserializeWalRecord(); + assertFalse(record.isDeleted()); + assertEquals(Long.class.getName(), record.getKeyClassName()); + assertEquals(String.class.getName(), record.getValueClassName()); + assertEquals(1_700_000_000_000L, record.getTimestamp()); + assertEquals(Long.valueOf(1001L), serializer.deserialize(record.getKey(), Long.class)); + String payload = serializer.deserialize(record.getValue(), String.class); + assertEquals(1024, payload.length()); + for (int index = 0; index < payload.length(); index++) { + assertEquals((char) ('a' + index % 26), payload.charAt(index)); + } + for (int iteration = 0; iteration < 10; iteration++) { + IMapFileData decoded = + serializer.deserialize(benchmark.serializeWalRecord(), IMapFileData.class); + assertEquals(record, decoded); + assertEquals(record, benchmark.deserializeWalRecord()); + } + } + + @Test + void shouldReturnFreshResultsWithoutMutatingTheReusableInputs() { + ProtoStuffSerializerBenchmark benchmark = new ProtoStuffSerializerBenchmark(); + benchmark.setUp(); + + byte[] expectedBytes = benchmark.serializeWalRecord(); + byte[] output = benchmark.serializeWalRecord(); + assertNotSame(expectedBytes, output); + output[0] ^= 1; + assertArrayEquals(expectedBytes, benchmark.serializeWalRecord()); + + IMapFileData expected = benchmark.deserializeWalRecord(); + IMapFileData decoded = benchmark.deserializeWalRecord(); + assertNotSame(expected, decoded); + decoded.getKey()[0] ^= 1; + decoded.getValue()[0] ^= 1; + decoded.setTimestamp(0); + assertEquals(expected, benchmark.deserializeWalRecord()); + assertArrayEquals(expectedBytes, benchmark.serializeWalRecord()); + } +}
