How to Stress-Test Netty WebSocket for 10,000 Subscriptions and Keepalive

This article presents advanced WebSocket testing techniques—including asynchronous connection creation, result statistics, price verification, latency measurement, TPS modeling—and demonstrates a Netty‑WebSocket subscription test for 10,000 users plus Java‑ and Netty‑based keepalive implementations for high‑concurrency scenarios.

FunTester
FunTester
FunTester
How to Stress-Test Netty WebSocket for 10,000 Subscriptions and Keepalive

5.4.4 Unfinished WebSocket Tasks

Additional improvements that can be added to the existing WebSocket test suite:

Asynchronous connection creation : use a thread pool to open many WebSocket connections in parallel. Each thread creates a client, calls connect(), and stores the resulting ChannelPromise (or Future) in a list. After all threads are started, a CountDownLatch (or waiting on each promise) synchronizes the main thread until every handshake is complete.

Result statistics : maintain atomic counters for successful and failed validations. After the test run, output a summary report showing total connections, success rate, and failure reasons.

Price verification : after receiving a message, parse the JSON payload, extract the price field and compare it with the expected value for the user role (VIP vs regular). Use assertions to fail the test when the difference is incorrect.

Latency measurement : record System.nanoTime() (or Instant.now()) immediately before sending a request and again when the corresponding response arrives. Store the delta in a list and compute average, min, max latency to evaluate asynchronous response time.

Event‑driven TPS model : replace the simple thread‑per‑connection approach with a transaction‑per‑second (TPS) engine that schedules connection creation and message sending based on an event loop (e.g., Netty’s EventLoopGroup). This reduces thread overhead and improves scalability under high concurrency.

5.4.5 Netty‑WebSocket Subscription Test

To verify the push capability of a server that must handle >10 000 concurrent subscribers, the following Java program creates 10 000 Netty‑based WebSocket clients, completes the handshake, and then broadcasts a subscription request to all channels.

package org.funtester.performance.books.chapter05.section4;

import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.funtester.performance.books.chapter05.section3.NettyWebSocketClient;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

public class SubscribeTest {
    public static void main(String[] args) throws URISyntaxException, InterruptedException {
        String url = "ws://localhost:12345/websocket/FunTester";
        URI uri = new URI(url);
        List<ChannelPromise> promises = new ArrayList<>();

        // Create 10 000 clients in parallel
        for (int i = 0; i < 10000; i++) {
            NettyWebSocketClient client = new NettyWebSocketClient(uri);
            ChannelPromise connect = client.connect();   // asynchronous handshake
            promises.add(connect);
        }

        // Wait for all handshakes to finish
        for (ChannelPromise p : promises) {
            p.get();   // blocks until the handshake succeeds or fails
        }

        // Send a single subscription frame to all channels
        NettyWebSocketClient.channels.writeAndFlush(
            new TextWebSocketFrame("Subscribe: vegetable and fruit price & quantity")
        ).sync();

        System.out.println("Subscription completed");
    }
}

Analysis

Netty‑WebSocket uses an event‑driven architecture with thread reuse. The test demonstrates that a single JVM can establish and maintain 10 000 connections, then push a message to all of them with a single write operation. By varying the size of the payload or the frequency of subscription messages, testers can generate additional load to observe CPU, memory, and network consumption on the server.

5.4.6 Connection Keepalive

In large‑scale subscription scenarios, idle connections are often closed by the server. A periodic heartbeat (ping) keeps the TCP session alive.

Java‑WebSocket keepalive

Each client runs a background thread that sleeps for a configurable interval (e.g., 10 seconds) and then sends a ping frame if the connection state is OPEN. The client also implements a simple reconnection strategy when the state becomes CLOSED.

package org.funtester.performance.books.chapter05.section4;

import org.funtester.performance.books.chapter05.section2.JavaWebSocketClient;
import org.java_websocket.enums.ReadyState;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

public class JavaWebSocketKeepAlive {
    public static void main(String[] args) throws URISyntaxException {
        String url = "ws://localhost:12345/websocket/FunTester";
        URI uri = new URI(url);
        List<JavaWebSocketClient> clients = new ArrayList<>();

        // Heartbeat thread
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(10_000); // 10 s interval
                    for (JavaWebSocketClient client : clients) {
                        if (client.getReadyState() == ReadyState.OPEN) {
                            client.sendPing();
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();

        // Create 1 000 clients
        for (int i = 0; i < 1000; i++) {
            JavaWebSocketClient client = new JavaWebSocketClient(uri) {
                @Override
                public void onMessage(String s) {
                    // No processing needed for keepalive
                }
            };
            client.connect();
            clients.add(client);
        }
    }
}

Netty‑WebSocket keepalive

Netty provides a ChannelGroup that holds all active channels. A scheduled task can iterate over the group and write a PingWebSocketFrame to every channel, eliminating the need for per‑client threads.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaconcurrencyPerformance TestingNettyWebSocketKeepalive
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.