Operations 5 min read

Scaling Fixed‑QPS Load Tests with a Multithreaded Task Generator

The article explains why a single‑threaded request generator fails at high QPS, introduces a multithreaded generator with a configurable per‑thread QPS limit, provides the full Java implementation, and shows that it reliably sustains 10‑20k QPS in practice.

FunTester
FunTester
FunTester
Scaling Fixed‑QPS Load Tests with a Multithreaded Task Generator

In earlier posts the author used a sleep() ‑based task generator to maintain a fixed QPS during load testing, but this single‑threaded approach showed significant error when the target QPS grew large because the generator could not keep up.

To solve this, a multithreaded task generator is added. After the performance‑test soft‑start completes, the system allocates several threads according to the configured QPS, each handling a portion of the request generation.

A constant defines the maximum request rate a single thread should handle:

/**
 * Maximum QPS rate per thread
 */
public static int QPS_PER_THREAD = 250;

The start() method is updated to compute the number of threads, the interval between requests, and the incremental timing adjustments needed to keep the overall QPS stable:

public PerformanceResultBean start() {
    boolean isTimesMode = baseThread.isTimesMode;
    int limit = baseThread.limit;
    int qps = baseThread.qps;
    executeThread = qps / Constant.QPS_PER_THREAD + 1;
    interval = 1_000_000_000 / qps; // 1s = 1,000,000,000 ns
    int runupTotal = qps * PREFIX_RUN; // total request count
    double diffTime = 2 * (Constant.RUNUP_TIME / PREFIX_RUN * interval - interval);
    double piece = diffTime / runupTotal; // per‑request time increment
    for (int i = runupTotal; i > 0; i--) {
        executorService.execute(threads.get(limit-- % queueLength).clone());
        sleep((long) (interval + i * piece));
    }
    // ... start progress, aid thread, latch, and worker threads ...
    CountDownLatch countDownLatch = new CountDownLatch(executeThread);
    for (int i = 0; i < executeThread; i++) {
        new FunTester(countDownLatch).start();
    }
    // ... wait for completion, shutdown, log results ...
    return over();
}

The for loop above launches multiple FunTester threads, each executing the test workload concurrently.

The inner FunTester class extends Thread and repeatedly pulls tasks from the shared pool until the configured limit or timeout is reached:

private class FunTester extends Thread {
    FunTester(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    CountDownLatch countDownLatch;
    boolean isTimesMode = baseThread.isTimesMode;
    int limit = baseThread.limit / executeThread;
    long nanosec = interval * executeThread;
    @Override
    public void run() {
        try {
            while (true) {
                executorService.execute(threads.get(limit-- % queueLength).clone());
                if (needAbord || (isTimesMode ? limit < 1 : Time.getTimeStamp() - startTime > limit))
                    break;
                SourceCode.sleep(nanosec);
            }
        } catch (Exception e) {
            logger.warn("Task generator error!", e);
        } finally {
            countDownLatch.countDown();
        }
    }
}

Testing in scenarios of 10,000–20,000 QPS shows satisfactory accuracy, and the author plans to refine the implementation further as new issues arise.

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.

JavaperformanceLoad TestingmultithreadingQPStask generator
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.