Skip to content

Commit ed4912d

Browse files
bajtosjuliangruber
andauthored
feat: add jitter to delay between tasks (#125)
feat: add jitter to delay between tasks Introduce random jitter in the delay between subsequent tasks (retrieval checks). The goal is to better spread out the requests from the network in the time, to avoid too many nodes hitting services like cid.contact at the same time. Signed-off-by: Miroslav Bajtoš <[email protected]> Co-authored-by: Julian Gruber <[email protected]>
1 parent 0a5fd14 commit ed4912d

File tree

3 files changed

+69
-3
lines changed

3 files changed

+69
-3
lines changed

lib/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const SPARK_VERSION = '1.18.1'
22
export const MAX_CAR_SIZE = 200 * 1024 * 1024 // 200 MB
33
export const APPROX_ROUND_LENGTH_IN_MS = 20 * 60_000 // 20 minutes
4+
export const MAX_JITTER_BETWEEN_TASKS_IN_MS = 10_000 // 10 seconds
45
export const RPC_URL = 'https://api.node.glif.io/'
56
export const RPC_AUTH = 'KZLIUb9ejreYOm-mZFM3UNADE0ux6CrHjxnS2D2Qgb8='
67
export const MINER_TO_PEERID_CONTRACT_ADDRESS = '0x14183aD016Ddc83D638425D6328009aa390339Ce' // Contract address on the Filecoin EVM

lib/spark.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* global Zinnia */
22

33
import { ActivityState } from './activity-state.js'
4-
import { SPARK_VERSION, MAX_CAR_SIZE, APPROX_ROUND_LENGTH_IN_MS } from './constants.js'
4+
import { SPARK_VERSION, MAX_CAR_SIZE, APPROX_ROUND_LENGTH_IN_MS, MAX_JITTER_BETWEEN_TASKS_IN_MS } from './constants.js'
55
import { queryTheIndex } from './ipni-client.js'
66
import { assertOkResponse } from './http-assertions.js'
77
import { getIndexProviderPeerId as defaultGetIndexProvider } from './miner-info.js'
@@ -235,6 +235,7 @@ export default class Spark {
235235
const duration = Date.now() - started
236236
const delay = calculateDelayBeforeNextTask({
237237
roundLengthInMs: APPROX_ROUND_LENGTH_IN_MS,
238+
maxJitterInMs: MAX_JITTER_BETWEEN_TASKS_IN_MS,
238239
maxTasksPerRound: this.#tasker.maxTasksPerRound,
239240
lastTaskDurationInMs: duration
240241
})
@@ -263,17 +264,24 @@ export default class Spark {
263264
/**
264265
* @param {object} args
265266
* @param {number} args.roundLengthInMs
267+
* @param {number} [args.maxJitterInMs=0]
266268
* @param {number} args.maxTasksPerRound
267269
* @param {number} args.lastTaskDurationInMs
268270
*/
269271
export function calculateDelayBeforeNextTask ({
270272
roundLengthInMs,
273+
maxJitterInMs = 0,
271274
maxTasksPerRound,
272275
lastTaskDurationInMs
273276
}) {
274277
const baseDelay = roundLengthInMs / maxTasksPerRound
275278
const delay = baseDelay - lastTaskDurationInMs
276-
return Math.min(delay, 60_000)
279+
const base = Math.min(delay, 60_000)
280+
281+
// Introduce some jitter to avoid all clients querying cid.contact at the same time
282+
const jitter = Math.round(Math.random() * maxJitterInMs)
283+
284+
return base + jitter
277285
}
278286

279287
export function newStats () {

test/spark.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import Spark, { calculateDelayBeforeNextTask, newStats } from '../lib/spark.js'
44
import { test } from 'zinnia:test'
5-
import { assertInstanceOf, assertEquals, assertArrayIncludes } from 'zinnia:assert'
5+
import { assertInstanceOf, assertEquals, assertArrayIncludes, assertNotEquals, assertLessOrEqual, assertGreaterOrEqual } from 'zinnia:assert'
66
import { SPARK_VERSION } from '../lib/constants.js'
77

88
const KNOWN_CID = 'bafkreih25dih6ug3xtj73vswccw423b56ilrwmnos4cbwhrceudopdp5sq'
@@ -363,3 +363,60 @@ test('calculateDelayBeforeNextTask() handles one task per round', () => {
363363
})
364364
assertEquals(delay, 60_000)
365365
})
366+
367+
test('calculateDelayBeforeNextTask() introduces random jitter', () => {
368+
const getDelay = () => calculateDelayBeforeNextTask({
369+
lastTaskDurationInMs: 3_000,
370+
371+
// one task every 10 seconds (on average)
372+
roundLengthInMs: 60_000,
373+
maxTasksPerRound: 6,
374+
375+
// jitter up to 1 second
376+
maxJitterInMs: 1_000
377+
})
378+
379+
const delay1 = getDelay()
380+
const delay2 = getDelay()
381+
382+
assertGreaterOrEqual(delay1, 7_000)
383+
assertLessOrEqual(delay1, 7_000 + 1_000)
384+
385+
assertNotEquals(
386+
delay1,
387+
delay2,
388+
`Expected delay values to be different because of jitter. Actual value: ${delay1}`
389+
)
390+
assertLessOrEqual(
391+
Math.abs(delay1 - delay2),
392+
1_000,
393+
`expected delay values to be within 1 second of each other. Actual values: ${delay1} <> ${delay2}`
394+
)
395+
})
396+
397+
test('calculateDelayBeforeNextTask() introduces random jitter for zero tasks in round', () => {
398+
const getDelay = () => calculateDelayBeforeNextTask({
399+
maxTasksPerRound: 0,
400+
401+
// jitter up to 1 second
402+
maxJitterInMs: 1_000,
403+
404+
// the values below are not important
405+
roundLengthInMs: 12345,
406+
lastTaskDurationInMs: 12
407+
})
408+
409+
const delay1 = getDelay()
410+
const delay2 = getDelay()
411+
412+
assertNotEquals(
413+
delay1,
414+
delay2,
415+
`Expected delay values to be different because of jitter. Actual value: ${delay1}`
416+
)
417+
assertLessOrEqual(
418+
Math.abs(delay1 - delay2),
419+
1_000,
420+
`expected delay values to be within 1 second of each other. Actual values: ${delay1} <> ${delay2}`
421+
)
422+
})

0 commit comments

Comments
 (0)