From 8a77bed2a4e292cb7ac419a8dbf8d3732b3bccdb Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Tue, 28 Jul 2026 11:52:48 -0400 Subject: [PATCH] Refactor LoadTestAction for usability Calculates the delay seconds automatically. This value helps ensure that all EPP requests are enqueued before the scheduled test start time. Since queue insertion is much slower than dispatch, this is essential to maintain a stable QPS rate. Also parallelizes queue insertion using a thread pool. This reduces the delay for enqueuing the requests. BUG=http://b/533414332 --- .../registry/loadtest/LoadTestAction.java | 58 ++++++++++++++----- .../registry/loadtest/LoadTestModule.java | 6 -- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/google/registry/loadtest/LoadTestAction.java b/core/src/main/java/google/registry/loadtest/LoadTestAction.java index 109aadf02b5..be7e12fa5af 100644 --- a/core/src/main/java/google/registry/loadtest/LoadTestAction.java +++ b/core/src/main/java/google/registry/loadtest/LoadTestAction.java @@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Lists.partition; import static google.registry.util.ResourceUtils.readResourceUtf8; +import static java.util.concurrent.Executors.newFixedThreadPool; import com.google.cloud.tasks.v2.Task; import com.google.common.collect.ImmutableList; @@ -39,11 +40,15 @@ import java.util.Iterator; import java.util.List; import java.util.Random; +import java.util.concurrent.ExecutorService; import java.util.function.Function; /** * Simple load test action that can generate configurable QPSes of various EPP actions. * + *

This is not an end-to-end test. It exercises the Nomulus EPP service and the database, but + * does not cover the proxy. + * *

All aspects of the load test are configured via URL parameters that are specified when the * loadtest URL is being POSTed to. The {@code clientId} and {@code tld} parameters are required. * All of the other parameters are optional, but if none are specified then no actual load testing @@ -60,7 +65,7 @@ public class LoadTestAction implements Runnable { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final int NUM_QUEUES = 10; + private static final int NUM_QUEUES = 20; private static final int MAX_TASKS_PER_LOAD = 100; private static final int ARBITRARY_VALID_HOST_LENGTH = 40; private static final int MAX_DOMAIN_LABEL_LENGTH = 63; @@ -72,20 +77,17 @@ public class LoadTestAction implements Runnable { public static final String PATH = "/_dr/loadtest"; + // Average task insertion rate with a dedicated thread enqueuing to one queue. This is used to + // calculate the EPP request dispatch time. This value is based on observation and needs not to + // be accurate. However, it should be low enough so that all EPP tasks are enqueued before the + // first task is dispatched. + private static final int TASK_INSERTIONS_PER_QUEUE_PER_MINUTE = 1000; + /** The ID of the registrar to use for load testing. */ @Inject @Parameter("loadtestClientId") String registrarId; - /** - * The number of seconds to delay the execution of the first load testing tasks by. Preparatory - * work of creating independent hosts that will be used for later domain creation testing occurs - * during this period, so make sure that it is long enough. - */ - @Inject - @Parameter("delaySeconds") - int delaySeconds; - /** * The number of seconds that tasks will be enqueued for. Note that if system QPS cannot handle * the given load then it will take longer than this number of seconds for the test to complete. @@ -157,9 +159,24 @@ public class LoadTestAction implements Runnable { xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST); } + private int eppTaskCount() { + return successfulDomainCreatesPerSecond + + runSeconds + * (successfulHostCreatesPerSecond + + failedHostCreatesPerSecond + + domainInfosPerSecond + + domainChecksPerSecond + + hostInfosPerSecond + + successfulDomainCreatesPerSecond + + failedDomainCreatesPerSecond); + } + @Override public void run() { - validateAndLogRequest(); + // Delay the EPP request dispatch time to account for queue-insertion time. + int delaySeconds = + Math.ceilDiv(eppTaskCount(), TASK_INSERTIONS_PER_QUEUE_PER_MINUTE * NUM_QUEUES) * 60; + validateAndLogRequest(delaySeconds); Instant initialStartSecond = clock.now().plus(Duration.ofSeconds(delaySeconds)); ImmutableList.Builder preTaskXmls = new ImmutableList.Builder<>(); ImmutableList.Builder hostPrefixesBuilder = new ImmutableList.Builder<>(); @@ -209,7 +226,7 @@ public void run() { logger.atInfo().log("Added %d total load test tasks.", taskOptions.size()); } - private void validateAndLogRequest() { + private void validateAndLogRequest(int delaySeconds) { checkArgument( RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION, "DO NOT RUN LOADTESTS IN PROD!"); @@ -297,9 +314,20 @@ private ImmutableList createTasks(ImmutableList xmls, Instant star private void enqueue(ImmutableList tasks) { List> chunks = partition(tasks, MAX_TASKS_PER_LOAD); - // Farm out tasks to multiple queues to work around queue qps quotas. - for (int i = 0; i < chunks.size(); i++) { - cloudTasksUtils.enqueue("load" + (i % NUM_QUEUES), chunks.get(i)); + // Farm out tasks to multiple queues to work around queue qps quotas. Use multiple threads to + // speed up the enqueuing. + try (ExecutorService executorService = newFixedThreadPool(NUM_QUEUES)) { + for (int i = 0; i < chunks.size(); i++) { + final int index = i; + // lgtm[java/local-variable-is-never-read] Suppress Github CodeQL's outdated warning + var _ = + executorService.submit( + () -> cloudTasksUtils.enqueue(getQueueName(index % NUM_QUEUES), chunks.get(index))); + } } } + + private static String getQueueName(int queueId) { + return String.format("load%d", queueId); + } } diff --git a/core/src/main/java/google/registry/loadtest/LoadTestModule.java b/core/src/main/java/google/registry/loadtest/LoadTestModule.java index 4601a2aed7c..976d0d4e8ce 100644 --- a/core/src/main/java/google/registry/loadtest/LoadTestModule.java +++ b/core/src/main/java/google/registry/loadtest/LoadTestModule.java @@ -38,12 +38,6 @@ static String provideClientId(HttpServletRequest req) { return extractRequiredParameter(req, "clientId"); } - @Provides - @Parameter("delaySeconds") - static int provideDelaySeconds(HttpServletRequest req) { - return extractOptionalIntParameter(req, "delaySeconds").orElse(60); - } - @Provides @Parameter("runSeconds") static int provideRunSeconds(HttpServletRequest req) {