From 1d0f7a738a03f3b68d62c96349ccbc437c5cb8ce Mon Sep 17 00:00:00 2001 From: mkaruza Date: Fri, 16 Oct 2026 14:20:33 +0200 Subject: [PATCH 1/4] test: add loadtest with normally distributed server delay Add a /delay?s= nginx endpoint, where can be fractional (e.g. /delay?s=0.087), and let wait_for_many_gets() send each request there with a delay drawn from a normal distribution, so the loadtest can simulate the latency of "normal" servers instead of an instant local reply: net-loadtest 10000 200 100 30 # mean 100ms, stddev 30ms The delay is generated by random_delay_ms() using the Box-Muller transform (written with AI assistance). Also remove the unused /slow-reply and /really-slow-reply locations, superseded by /delay. --- nix/loadtest.nix | 6 ++++++ nix/nginx/conf/custom.conf | 11 +++-------- test/utils/loadtest.sql | 22 ++++++++++++++++++++-- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/nix/loadtest.nix b/nix/loadtest.nix index 6270d4df..91204938 100644 --- a/nix/loadtest.nix +++ b/nix/loadtest.nix @@ -88,6 +88,12 @@ writeShellScriptBin "net-loadtest" '' batch_size_opt="-c pg_net.batch_size=$2" fi + # Optional server delay in ms following a normal distribution. + # Arguments: delay in ms ($3), delay standard deviation in ms ($4) + if [ -n "''${3:-}" ]; then + reqs="$reqs, delay_ms => ''${3}, delay_sd_ms => ''${4:-0}" + fi + net-with-nginx xpg --options "-c log_min_messages=WARNING $batch_size_opt" \ psql -c "call wait_for_many_gets($reqs)" -c "\pset format csv" -c "\o $query_csv" -c "select * from run" > /dev/null & diff --git a/nix/nginx/conf/custom.conf b/nix/nginx/conf/custom.conf index 894b2132..a03b3368 100644 --- a/nix/nginx/conf/custom.conf +++ b/nix/nginx/conf/custom.conf @@ -2,14 +2,9 @@ location / { echo 'Hello world'; } -location /slow-reply { - echo_sleep 2; - echo 'this text will come in response body with HTTP 200 after 2 seconds'; -} - -location /really-slow-reply { - echo_sleep 30; - echo 'this text will come in response body with HTTP 200 after 30 seconds'; +location /delay { + echo_sleep $arg_s; + echo 'ok'; } location /echo-method { diff --git a/test/utils/loadtest.sql b/test/utils/loadtest.sql index 8321a713..b2a38961 100644 --- a/test/utils/loadtest.sql +++ b/test/utils/loadtest.sql @@ -7,8 +7,22 @@ create table run ( last_failure_error text ); +-- random delay in ms from a normal distribution with the given mean and standard deviation. +create or replace function random_delay_ms(mean_ms int, sd_ms int) returns int as $$ + select greatest(0, round(mean_ms + sd_ms * sqrt(-2 * ln(1 - random())) * cos(2 * pi() * random())))::int +$$ language sql volatile; + -- loadtest using many gets, used to be called `repro_timeouts` -create or replace procedure wait_for_many_gets(number_of_requests int default 10000, url text default 'http://localhost:8080') as $$ +-- +-- when delay_ms is given, each request goes to the nginx /delay endpoint with a server delay drawn +-- from a normal distribution with mean delay_ms and standard deviation delay_sd_ms (clamped +-- at 0), to simulate the latency of real servers +create or replace procedure wait_for_many_gets( + number_of_requests int default 10000, + url text default 'http://localhost:8080', + delay_ms int default null, + delay_sd_ms int default 0 +) as $$ declare last_id bigint; first_time timestamptz; @@ -22,7 +36,11 @@ begin with do_requests as ( select - net.http_get(url) as id + net.http_get( + case when delay_ms is null then url + else url || '/delay?s=' || round(random_delay_ms(delay_ms, delay_sd_ms) / 1000.0, 3) + end + ) as id from generate_series (1, number_of_requests) x ) select id, clock_timestamp() into last_id, first_time from do_requests offset number_of_requests - 1; From 3c681122c091b3f937c7adf7de9f489b78a49be9 Mon Sep 17 00:00:00 2001 From: mkaruza Date: Mon, 19 Oct 2026 09:31:25 +0200 Subject: [PATCH 2/4] chore: add --help to net-loadtest --- CONTRIBUTING.md | 2 +- nix/loadtest.nix | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cdf5536..07423b62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -128,7 +128,7 @@ $ sudo net-with-gdb The `net-loadtest` launchs a temporary db and a nginx server, waiting until a number of requests are done, then reporting results (plus process monitoring) at the end. -It takes a two parameters: the number of requests (GETs) and the `pg_net.batch_size`. +It takes up to four optional parameters: the number of requests (GETs), the `pg_net.batch_size`, and a server delay in ms with its standard deviation in ms (normally distributed). Run `net-loadtest --help` for details. ```bash $ net-loadtest 1000 200 diff --git a/nix/loadtest.nix b/nix/loadtest.nix index 91204938..ea9e5f55 100644 --- a/nix/loadtest.nix +++ b/nix/loadtest.nix @@ -67,6 +67,31 @@ in writeShellScriptBin "net-loadtest" '' set -euo pipefail + usage() { + cat < Date: Mon, 19 Oct 2026 16:22:43 +0200 Subject: [PATCH 3/4] chore: Add explanation about `random_delay_ms` function --- test/utils/loadtest.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/utils/loadtest.sql b/test/utils/loadtest.sql index b2a38961..53e524d0 100644 --- a/test/utils/loadtest.sql +++ b/test/utils/loadtest.sql @@ -8,6 +8,11 @@ create table run ( ); -- random delay in ms from a normal distribution with the given mean and standard deviation. +-- +-- Uses the Box-Muller transform to turn two independent uniform samples u1, u2 into a standard normal +-- value z = sqrt(-2 * ln(u1)) * cos(2 * pi * u2), which is then scaled to mean_ms + sd_ms * z. +-- PostgreSQL random() returns values in [0, 1) and ln(0) is undefined, so u1 is taken as `1 - random()`, +-- which lies in (0, 1]. Negative results are clamped to 0 since a delay can't be negative. create or replace function random_delay_ms(mean_ms int, sd_ms int) returns int as $$ select greatest(0, round(mean_ms + sd_ms * sqrt(-2 * ln(1 - random())) * cos(2 * pi() * random())))::int $$ language sql volatile; From 027d34bd049f26444d74bd68ebc6144fb32a4fc0 Mon Sep 17 00:00:00 2001 From: mkaruza Date: Tue, 20 Oct 2026 09:04:32 +0200 Subject: [PATCH 4/4] chore: Support only pg16+ and use `random_normal` --- test/utils/loadtest.sql | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/utils/loadtest.sql b/test/utils/loadtest.sql index 53e524d0..fa839532 100644 --- a/test/utils/loadtest.sql +++ b/test/utils/loadtest.sql @@ -8,14 +8,16 @@ create table run ( ); -- random delay in ms from a normal distribution with the given mean and standard deviation. --- --- Uses the Box-Muller transform to turn two independent uniform samples u1, u2 into a standard normal --- value z = sqrt(-2 * ln(u1)) * cos(2 * pi * u2), which is then scaled to mean_ms + sd_ms * z. --- PostgreSQL random() returns values in [0, 1) and ln(0) is undefined, so u1 is taken as `1 - random()`, --- which lies in (0, 1]. Negative results are clamped to 0 since a delay can't be negative. +-- Uses random_normal(), which is only available on PostgreSQL 16+. +-- Negative results are clamped to 0 since a delay can't be negative. create or replace function random_delay_ms(mean_ms int, sd_ms int) returns int as $$ - select greatest(0, round(mean_ms + sd_ms * sqrt(-2 * ln(1 - random())) * cos(2 * pi() * random())))::int -$$ language sql volatile; +begin + if current_setting('server_version_num')::int < 160000 then + raise exception 'function `random_normal` requires PostgreSQL 16 or newer, running %', current_setting('server_version'); + end if; + return greatest(0, round(random_normal(mean_ms, sd_ms)))::int; +end; +$$ language plpgsql volatile; -- loadtest using many gets, used to be called `repro_timeouts` --