You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2024/08/01 10:54:32 UTC

(ignite-3) branch main updated: IGNITE-22872 Add client heartbeat benchmarks (#4165)

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 1e8959c0a0 IGNITE-22872 Add client heartbeat benchmarks (#4165)
1e8959c0a0 is described below

commit 1e8959c0a000f0901085eb0b11b37db4299fa72a
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Thu Aug 1 13:54:27 2024 +0300

    IGNITE-22872 Add client heartbeat benchmarks (#4165)
    
    * Java client: add `ClientHeartbeatBenchmark`
      * ~17 us / op
      * Check profiler - no obvious issues
      * Remove unnecessary `channelReadComplete` override
    * .NET client: add `HeartbeatBenchmark` (uses real server via `PlatformBenchmarkNodeRunner`)
      * ~15 us / op
      * Check profiler - no obvious issues
      * Remove `HasFlag` usage to avoid enum boxing (minor allocation reduction)
---
 .../handler/ClientInboundMessageHandler.java       |  12 +-
 .../io/netty/NettyClientConnectionMultiplexer.java |   1 +
 .../benchmarks/ClientHeartbeatBenchmark.java       | 124 +++++++++++++++++++++
 .../{ => benchmarks}/ClientPutGetBenchmark.java    |   8 +-
 .../Apache.Ignite.Benchmarks/HeartbeatBenchmark.cs |  52 +++++++++
 .../dotnet/Apache.Ignite.Benchmarks/Program.cs     |   3 +-
 .../dotnet/Apache.Ignite/Internal/ClientSocket.cs  |   6 +-
 .../runner/app/PlatformBenchmarkNodeRunner.java    |   4 +
 8 files changed, 193 insertions(+), 17 deletions(-)

diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
index 2f3c0ad04f..8d1fcd6e76 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientInboundMessageHandler.java
@@ -306,7 +306,7 @@ public class ClientInboundMessageHandler extends ChannelInboundHandlerAdapter im
         ByteBuf byteBuf = (ByteBuf) msg;
 
         // Each inbound handler in a pipeline has to release the received messages.
-        var unpacker = getUnpacker(byteBuf);
+        var unpacker = new ClientMessageUnpacker(byteBuf);
         metrics.bytesReceivedAdd(byteBuf.readableBytes() + ClientMessageCommon.HEADER_SIZE);
 
         // Packer buffer is released by Netty on send, or by inner exception handlers below.
@@ -551,10 +551,6 @@ public class ClientInboundMessageHandler extends ChannelInboundHandlerAdapter im
         return new ClientMessagePacker(alloc.buffer());
     }
 
-    private static ClientMessageUnpacker getUnpacker(ByteBuf buf) {
-        return new ClientMessageUnpacker(buf);
-    }
-
     private void processOperation(ChannelHandlerContext ctx, ClientMessageUnpacker in, ClientMessagePacker out) {
         long requestId = -1;
         int opCode = -1;
@@ -826,12 +822,6 @@ public class ClientInboundMessageHandler extends ChannelInboundHandlerAdapter im
         }
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void channelReadComplete(ChannelHandlerContext ctx) {
-        ctx.flush();
-    }
-
     /** {@inheritDoc} */
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java b/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
index fa4264b08a..9adf9d1961 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/io/netty/NettyClientConnectionMultiplexer.java
@@ -78,6 +78,7 @@ public class NettyClientConnectionMultiplexer implements ClientConnectionMultipl
         try {
             bootstrap.group(workerGroup);
             bootstrap.channel(NioSocketChannel.class);
+            bootstrap.option(ChannelOption.TCP_NODELAY, true);
             bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
             bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) clientCfg.connectTimeout());
             bootstrap.handler(new ChannelInitializer<SocketChannel>() {
diff --git a/modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientHeartbeatBenchmark.java b/modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientHeartbeatBenchmark.java
new file mode 100644
index 0000000000..10ab5ddafa
--- /dev/null
+++ b/modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientHeartbeatBenchmark.java
@@ -0,0 +1,124 @@
+/*
+ * 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.ignite.client.benchmarks;
+
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.TestServer;
+import org.apache.ignite.client.fakes.FakeIgnite;
+import org.apache.ignite.internal.client.ClientChannel;
+import org.apache.ignite.internal.client.TcpIgniteClient;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+/**
+ * Heartbeat (ping) benchmark - measures the simplest client-server interaction.
+ *
+ * <p>Results on i9-12900H, openjdk 11.0.23, Ubuntu 22.04:
+ * Benchmark                                                Mode      Cnt     Score     Error   Units
+ * ClientHeartbeatBenchmark.heartbeat                     sample  1297810    17.333 ±   0.021   us/op
+ * ClientHeartbeatBenchmark.heartbeat:gc.alloc.rate       sample        3    27.942 ±  16.104  MB/sec
+ * ClientHeartbeatBenchmark.heartbeat:gc.alloc.rate.norm  sample        3   512.781 ± 242.667    B/op
+ * ClientHeartbeatBenchmark.heartbeat:gc.count            sample        3     6.000            counts
+ * ClientHeartbeatBenchmark.heartbeat:gc.time             sample        3    18.000                ms
+ * ClientHeartbeatBenchmark.heartbeat:jfr                 sample                NaN               ---
+ * ClientHeartbeatBenchmark.heartbeat:p0.00               sample             13.120             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.50               sample             16.480             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.90               sample             20.480             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.95               sample             21.408             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.99               sample             25.632             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.999              sample             94.336             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p0.9999             sample            160.880             us/op
+ * ClientHeartbeatBenchmark.heartbeat:p1.00               sample           3690.496             us/op
+ */
+@State(Scope.Benchmark)
+public class ClientHeartbeatBenchmark {
+    private TestServer testServer;
+
+    private TcpIgniteClient client;
+
+    private ClientChannel channel;
+
+    /**
+     * Init.
+     */
+    @Setup
+    public void init() {
+        testServer = new TestServer(1000, new FakeIgnite("server-1"));
+
+        client = (TcpIgniteClient) IgniteClient.builder()
+                .addresses("127.0.0.1:" + testServer.port())
+                .build();
+
+        channel = client.channel().channels().get(0);
+
+        ResourceLeakDetector.setLevel(Level.DISABLED);
+    }
+
+    /**
+     * Tear down.
+     */
+    @TearDown
+    public void tearDown() throws Exception {
+        client.close();
+        testServer.close();
+    }
+
+    /**
+     * Heartbeat benchmark.
+     */
+    @Benchmark
+    public void heartbeat() {
+        channel.heartbeatAsync(null).join();
+    }
+
+    /**
+     * Runner.
+     *
+     * @param args Arguments.
+     * @throws RunnerException Exception.
+     */
+    public static void main(String[] args) throws RunnerException {
+        Options opt = new OptionsBuilder()
+                .include(ClientHeartbeatBenchmark.class.getSimpleName())
+                .mode(Mode.SampleTime)
+                .timeUnit(TimeUnit.MICROSECONDS)
+                .addProfiler("gc")
+                .addProfiler("jfr")
+                .warmupIterations(3)
+                .warmupTime(TimeValue.seconds(3))
+                .measurementIterations(3)
+                .measurementTime(TimeValue.seconds(15))
+                .forks(1)
+                .threads(1)
+                .build();
+
+        new Runner(opt).run();
+    }
+}
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientPutGetBenchmark.java b/modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientPutGetBenchmark.java
similarity index 95%
rename from modules/client/src/test/java/org/apache/ignite/client/ClientPutGetBenchmark.java
rename to modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientPutGetBenchmark.java
index 2186566968..64656cf17b 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientPutGetBenchmark.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/benchmarks/ClientPutGetBenchmark.java
@@ -15,9 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.client;
+package org.apache.ignite.client.benchmarks;
 
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
 import org.apache.ignite.Ignite;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.TestServer;
 import org.apache.ignite.client.fakes.FakeIgnite;
 import org.apache.ignite.client.fakes.FakeIgniteTables;
 import org.apache.ignite.table.RecordView;
@@ -119,6 +123,8 @@ public class ClientPutGetBenchmark {
      * @throws RunnerException Exception.
      */
     public static void main(String[] args) throws RunnerException {
+        ResourceLeakDetector.setLevel(Level.DISABLED);
+
         Options opt = new OptionsBuilder()
                 .include(ClientPutGetBenchmark.class.getSimpleName())
                 .addProfiler("gc")
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/HeartbeatBenchmark.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/HeartbeatBenchmark.cs
new file mode 100644
index 0000000000..1f497581d4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/HeartbeatBenchmark.cs
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+namespace Apache.Ignite.Benchmarks;
+
+using System.Linq;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Internal;
+
+/// <summary>
+/// Results on i9-12900H, .NET SDK 6.0.3224, Ubuntu 22.04:
+/// | Method    | Mean     | Error    | StdDev   |
+/// |---------- |---------:|---------:|---------:|
+/// | Heartbeat | 15.62 us | 0.386 us | 1.121 us |.
+/// </summary>
+[SimpleJob]
+public class HeartbeatBenchmark
+{
+    private IgniteClientInternal? _client;
+    private ClientSocket? _socket;
+
+    [GlobalSetup]
+    public async Task GlobalSetup()
+    {
+        // Requires PlatformBenchmarkNodeRunner class to be started.
+        var cfg = new IgniteClientConfiguration("127.0.0.1:10420");
+
+        _client = (IgniteClientInternal)await IgniteClient.StartAsync(cfg);
+        _socket = _client.Socket.GetSockets().Single();
+    }
+
+    [GlobalCleanup]
+    public void GlobalCleanup() => _client?.Dispose();
+
+    [Benchmark]
+    public async Task Heartbeat() => await _socket!.HeartbeatAsync();
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
index 2bb6cd7c9d..51c99d06b3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
@@ -18,10 +18,9 @@
 namespace Apache.Ignite.Benchmarks;
 
 using BenchmarkDotNet.Running;
-using Table;
 
 internal static class Program
 {
     // IMPORTANT: Disable Netty leak detector when using a real Ignite server for benchmarks.
-    private static void Main() => BenchmarkRunner.Run<TupleGetBenchmarks>();
+    private static void Main() => BenchmarkRunner.Run<HeartbeatBenchmark>();
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
index dec8eb3b0c..b0cdc0ffbc 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
@@ -813,10 +813,10 @@ namespace Apache.Ignite.Internal
             HandlePartitionAssignmentChange(flags, ref reader);
             HandleObservableTimestamp(ref reader);
 
-            var exception = flags.HasFlag(ResponseFlags.Error) ? ReadError(ref reader) : null;
+            var exception = (flags & ResponseFlags.Error) != 0 ? ReadError(ref reader) : null;
             response.Position += reader.Consumed;
 
-            if (flags.HasFlag(ResponseFlags.Notification))
+            if ((flags & ResponseFlags.Notification) != 0)
             {
                 return HandleNotification(requestId, exception, response);
             }
@@ -882,7 +882,7 @@ namespace Apache.Ignite.Internal
 
         private void HandlePartitionAssignmentChange(ResponseFlags flags, ref MsgPackReader reader)
         {
-            if (flags.HasFlag(ResponseFlags.PartitionAssignmentChanged))
+            if ((flags & ResponseFlags.PartitionAssignmentChanged) != 0)
             {
                 long timestamp = reader.ReadInt64();
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformBenchmarkNodeRunner.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformBenchmarkNodeRunner.java
index 8df2e973dd..b0948be8e6 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformBenchmarkNodeRunner.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/PlatformBenchmarkNodeRunner.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.runner.app;
 
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
@@ -56,6 +58,8 @@ public class PlatformBenchmarkNodeRunner {
     public static void main(String[] args) throws Exception {
         System.out.println("Starting benchmark node runner...");
 
+        ResourceLeakDetector.setLevel(Level.DISABLED);
+
         List<IgniteServer> startedNodes = PlatformTestNodeRunner.startNodes(BASE_PATH, nodesBootstrapCfg);
 
         Ignite ignite = startedNodes.get(0).api();