Parallel vacuum v2 at vacuum_cost_delay = 100ms: 1 s cap and WaitLatch loop Linux x86_64, master 5ffb8c74093, no assertions, -O2. Builds: v2 master + v2-0001 1 s cap v2 + "msec = Min(msec, 1000)" after the v2 cap in compute_parallel_delay() (Bharath's option 2) loop v2 with pg_usleep(msec * 1000) replaced by Chao's loop as posted: CHECK_FOR_INTERRUPTS(), WaitLatch() for the whole milliseconds, pg_usleep() for the rest, remaining time recomputed from a start timestamp. make check passes. resume diagnostic only: v2 with pg_usleep() called again for the remaining time when it returns early. Table: 10000 rows, 21 int columns, 20 btree indexes, 10% of rows deleted. Default page costs. Cost spent = hits + 2 * misses + 20 * dirtied from the VACUUM VERBOSE buffer usage (includes the workers), divided by elapsed time, as a multiple of the budget (vacuum_cost_limit / vacuum_cost_delay). N = 3, rounds alternate the builds, all values listed. 1. vacuum_cost_limit = 200 (budget 2000 units/s) PARALLEL master v2 1 s cap 4 2.72 2.83 2.72 1.12 1.11 1.10 1.10 1.10 1.10 8 3.44 3.63 3.04 1.11 1.15 1.13 1.08 1.11 1.14 16 5.07 3.81 3.64 1.13 1.17 1.10 1.09 1.18 1.10 PARALLEL loop 4 1.03 1.03 1.03 8 1.02 1.02 1.02 16 1.04 1.04 1.03 The 1 s cap does not change anything measurable here. 2. vacuum_cost_limit = 20 (budget 200 units/s) PARALLEL master v2 1 s cap loop 4 2.75 2.75 2.76 1.05 1.05 1.06 1.05 1.05 1.05 1.03 1.03 1.03 16 3.52 3.54 3.54 1.07 1.07 1.07 1.54 1.54 1.55 1.03 1.03 1.03 Elapsed at PARALLEL 16: v2 74.7-74.9 s, 1 s cap 51.8-52.0 s, loop 77.8-78.0 s. 3. Why v2 is above the budget at 100ms Same setup as 1, PARALLEL 4 and 16, rounds alternating v2 / resume / loop: v2 1.10 1.18 | 1.11 1.19 | 1.10 1.13 resume 1.03 1.03 | 1.02 1.03 | 1.03 1.03 loop 1.03 1.04 | 1.03 1.04 | 1.03 1.04 SIGUSR1 received by the leader during one VACUUM (PARALLEL n, VERBOSE) (gdb catch signal on the leader): v2 25 at PARALLEL 4, 47 at 16; loop 25 and 50. No SIGURG. pg_usleep() on a Unix backend is nanosleep() (src/port/pgsleep.c:50, under #if defined(FRONTEND) || !defined(WIN32)), which returns early when a signal handler runs. Workers send PROCSIG_PARALLEL_MESSAGE to the leader for each message (src/backend/libpq/pqmq.c:187), for example the index progress update (src/backend/commands/vacuumparallel.c:1217). Resuming the sleep is enough to bring v2 to the loop's numbers. For reference, on 2026-10-22 with vacuum_cost_delay = 10ms and 200000 rows, v2 was at 1.00x at PARALLEL 4, 8 and 16. 4. Interrupt latency VACUUM (PARALLEL 16), vacuum_cost_delay = 100ms, vacuum_cost_limit = 1, page_hit = page_miss = 0, page_dirty = 20: every sleep is at the cap (6.8 s with v2 and the loop, 1 s with the 1 s cap). Time from sending the signal to the client getting the error, psql startup included. pg_cancel_backend, v2 and loop alternated, N = 10 each: v2 5.0 7.7 8.4 9.0 10.4 11.5 12.7 13.0 14.9 16.1 ms loop 8.5 9.3 9.3 9.4 10.1 10.2 10.3 11.1 14.2 14.9 ms Forced race: gdb stops the leader right before the sleep and sets QueryCancelPending and InterruptPending as the handler would; for the loop it stops at WaitLatch() entry (VacuumDelay only) and also calls SetLatch(MyLatch), which the handler does too. v2 6767 6780 6786 ms (N = 3) 1 s cap 960 969 980 986 986 ms (N = 5) loop 0.4 0.8 0.9 0.9 0.9 1.0 1.1 1.1 2.0 3.2 ms (N = 10) 5. Fractional vacuum_cost_delay (the reason 4753ef37 was reverted) Table of 200000 rows (otherwise the same), budget 40000 units/s in both cases, N = 3, rounds alternate the builds: 2.5ms, limit 100, PARALLEL 4: v2 0.96 0.96 0.96, loop 0.96 0.96 0.96 2.5ms, limit 100, PARALLEL 16: v2 0.97 0.97 0.97, loop 0.96 0.97 0.97 0.5ms, limit 20, PARALLEL 4: v2 0.92 0.92 0.92, loop 0.92 0.92 0.93 0.5ms, limit 20, PARALLEL 16: v2 0.95 0.94 0.94, loop 0.95 0.94 0.94 Caveats: one machine (Ryzen AI 9 HX 370, Linux 7.2), data on tmpfs, fsync = off. The lane monitor flagged CPU contention from other jobs during most runs. The ratios of v2, 1 s cap, loop and resume repeat within 0.08 across rounds (master varies more: 3.64-5.07 at PARALLEL 16, limit 200); the millisecond latencies are more sensitive to it. The race is emulated with gdb, which models the outcome, not how often it happens. Windows not measured.