perf(run-engine): cut two Redis calls from the ck vtime registration path
The idle lookup only matters on the call that actually registers a variant. ZADD NX is a no-op on one that is already registered, and its tag is already correct, so the ZSCORE preceding it was wasted on every enqueue after the first. Doing the ZADD first and the ZSCORE only when it reports an insert takes the common path from two ops to one. The per-registration EXPIRE of ckVtimeIdle went too: the park sites are the only writers that put anything in that key and they set its TTL themselves, so refreshing it on a call that may never write there was pure cost. Applies to all four registration sites: enqueue, enqueue-with-ttl, nack, and the gated-variant branch in the vtime dequeue. Six redis.call per registration becomes four in the common case, five or six on the rarer call that registers. Measured on a saturated benchmark (generator co-located with Redis, 1M invocations per arm, 3 interleaved cycles, Redis CPU/wall 0.97 on every arm, two independent cost measures agreeing to 0.04 usec). Against the flag-off enqueue script at 8.603 usec, the vtime path was 10.903 usec (+26.7%) and is now 10.177 usec (+18.3%), so this removes about 30% of the virtual-time enqueue overhead. That +26.7% independently reproduces the 26/23/24% total-CPU overhead the cardinality benchmark measured by a different method. A probe isolating the block gives the model behind it: roughly 1.38 usec fixed per EVALSHA plus 0.33 usec per redis.call, linear in call count for O(1) commands on small keys. Behaviour is unchanged. Final ckVtime tags are identical across already registered, unregistered, idle above floor, idle below floor and missing floor key, and the starvation suite that asserts exact tags still passes.
This commit is contained in:
@@ -4363,16 +4363,20 @@ end
|
||||
-- never rewound. The start is max(floor, remembered idle tag): a variant that drained and
|
||||
-- came back would otherwise be handed full credit at the floor on every re-enqueue, which
|
||||
-- starves any variant carrying a persistent backlog.
|
||||
-- The idle lookup only matters when this call is what registers the variant: ZADD NX is
|
||||
-- a no-op on an already-registered one, and its tag is already correct. Doing the ZADD
|
||||
-- first and the ZSCORE only on the registering call takes the common path from two ops to
|
||||
-- one. Measured saturated: 0.33 usec of Redis CPU per redis.call removed, and the pair of
|
||||
-- reductions here is ~30% of the vtime enqueue overhead.
|
||||
local vfloor = redis.call('GET', ckVtimeFloorKey) or '0'
|
||||
local vstart = tonumber(vfloor)
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, queueName)
|
||||
if vidle and tonumber(vidle) > vstart then
|
||||
vstart = tonumber(vidle)
|
||||
if redis.call('ZADD', ckVtimeKey, 'NX', vfloor, queueName) == 1 then
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, queueName)
|
||||
if vidle and tonumber(vidle) > tonumber(vfloor) then
|
||||
redis.call('ZADD', ckVtimeKey, 'XX', vidle, queueName)
|
||||
end
|
||||
end
|
||||
redis.call('ZADD', ckVtimeKey, 'NX', tostring(vstart), queueName)
|
||||
redis.call('EXPIRE', ckVtimeKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeFloorKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeIdleKey, stateTtl)
|
||||
|
||||
-- Rebalance master queue with ck:* member
|
||||
local earliestIdx = redis.call('ZRANGE', ckIndexKey, 0, 0, 'WITHSCORES')
|
||||
@@ -4510,16 +4514,20 @@ end
|
||||
-- never rewound. The start is max(floor, remembered idle tag): a variant that drained and
|
||||
-- came back would otherwise be handed full credit at the floor on every re-enqueue, which
|
||||
-- starves any variant carrying a persistent backlog.
|
||||
-- The idle lookup only matters when this call is what registers the variant: ZADD NX is
|
||||
-- a no-op on an already-registered one, and its tag is already correct. Doing the ZADD
|
||||
-- first and the ZSCORE only on the registering call takes the common path from two ops to
|
||||
-- one. Measured saturated: 0.33 usec of Redis CPU per redis.call removed, and the pair of
|
||||
-- reductions here is ~30% of the vtime enqueue overhead.
|
||||
local vfloor = redis.call('GET', ckVtimeFloorKey) or '0'
|
||||
local vstart = tonumber(vfloor)
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, queueName)
|
||||
if vidle and tonumber(vidle) > vstart then
|
||||
vstart = tonumber(vidle)
|
||||
if redis.call('ZADD', ckVtimeKey, 'NX', vfloor, queueName) == 1 then
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, queueName)
|
||||
if vidle and tonumber(vidle) > tonumber(vfloor) then
|
||||
redis.call('ZADD', ckVtimeKey, 'XX', vidle, queueName)
|
||||
end
|
||||
end
|
||||
redis.call('ZADD', ckVtimeKey, 'NX', tostring(vstart), queueName)
|
||||
redis.call('EXPIRE', ckVtimeKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeFloorKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeIdleKey, stateTtl)
|
||||
|
||||
-- Rebalance master queue with ck:* member
|
||||
local earliestIdx = redis.call('ZRANGE', ckIndexKey, 0, 0, 'WITHSCORES')
|
||||
@@ -5547,13 +5555,13 @@ local function tryServe(ckQueueName, mayRaiseFloor)
|
||||
-- The TTL only needs setting when this actually registered something, since that is the
|
||||
-- path that can recreate a ckVtime key which expired out from under a live ckIndex.
|
||||
-- NEW: registers at max(floor, remembered idle tag), same rule as the enqueue path, so
|
||||
-- a variant that drained under the gate does not come back with full credit.
|
||||
local gateStart = floor
|
||||
local gateIdle = redis.call('ZSCORE', ckVtimeIdleKey, ckQueueName)
|
||||
if gateIdle and tonumber(gateIdle) > gateStart then
|
||||
gateStart = tonumber(gateIdle)
|
||||
end
|
||||
if redis.call('ZADD', ckVtimeKey, 'NX', tostring(gateStart), ckQueueName) == 1 then
|
||||
-- a variant that drained under the gate does not come back with full credit. Ordered
|
||||
-- the same way too: the ZADD NX decides whether the idle lookup is worth doing at all.
|
||||
if redis.call('ZADD', ckVtimeKey, 'NX', tostring(floor), ckQueueName) == 1 then
|
||||
local gateIdle = redis.call('ZSCORE', ckVtimeIdleKey, ckQueueName)
|
||||
if gateIdle and tonumber(gateIdle) > floor then
|
||||
redis.call('ZADD', ckVtimeKey, 'XX', gateIdle, ckQueueName)
|
||||
end
|
||||
redis.call('EXPIRE', ckVtimeKey, stateTtl)
|
||||
end
|
||||
end
|
||||
@@ -6461,16 +6469,20 @@ end
|
||||
-- never rewound. The start is max(floor, remembered idle tag): a nack after the variant
|
||||
-- drained would otherwise hand back full credit at the floor, which is the same starvation
|
||||
-- the enqueue path guards against.
|
||||
-- The idle lookup only matters when this call is what registers the variant: ZADD NX is
|
||||
-- a no-op on an already-registered one, and its tag is already correct. Doing the ZADD
|
||||
-- first and the ZSCORE only on the registering call takes the common path from two ops to
|
||||
-- one. Measured saturated: 0.33 usec of Redis CPU per redis.call removed, and the pair of
|
||||
-- reductions here is ~30% of the vtime enqueue overhead.
|
||||
local vfloor = redis.call('GET', ckVtimeFloorKey) or '0'
|
||||
local vstart = tonumber(vfloor)
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, messageQueueName)
|
||||
if vidle and tonumber(vidle) > vstart then
|
||||
vstart = tonumber(vidle)
|
||||
if redis.call('ZADD', ckVtimeKey, 'NX', vfloor, messageQueueName) == 1 then
|
||||
local vidle = redis.call('ZSCORE', ckVtimeIdleKey, messageQueueName)
|
||||
if vidle and tonumber(vidle) > tonumber(vfloor) then
|
||||
redis.call('ZADD', ckVtimeKey, 'XX', vidle, messageQueueName)
|
||||
end
|
||||
end
|
||||
redis.call('ZADD', ckVtimeKey, 'NX', tostring(vstart), messageQueueName)
|
||||
redis.call('EXPIRE', ckVtimeKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeFloorKey, stateTtl)
|
||||
redis.call('EXPIRE', ckVtimeIdleKey, stateTtl)
|
||||
|
||||
-- Rebalance master queue with ck:* member
|
||||
local earliestIdx = redis.call('ZRANGE', ckIndexKey, 0, 0, 'WITHSCORES')
|
||||
|
||||
@@ -61,7 +61,9 @@ function createQueue(
|
||||
masterQueueConsumersDisabled: true,
|
||||
workerOptions: { disabled: true },
|
||||
...(vtimeEnabled
|
||||
? { ckVirtualTimeScheduling: { enabled: true, ...(idleMaxEntries ? { idleMaxEntries } : {}) } }
|
||||
? {
|
||||
ckVirtualTimeScheduling: { enabled: true, ...(idleMaxEntries ? { idleMaxEntries } : {}) },
|
||||
}
|
||||
: {}),
|
||||
queueSelectionStrategy: new FairQueueSelectionStrategy({
|
||||
redis: { keyPrefix, host: redisContainer.getHost(), port: redisContainer.getPort() },
|
||||
|
||||
Reference in New Issue
Block a user