perf: optimize worker choice strategies
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Jun 2023 19:27:58 +0000 (21:27 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Jun 2023 19:27:58 +0000 (21:27 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
CHANGELOG.md
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/fair-share-worker-choice-strategy.ts
src/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/least-busy-worker-choice-strategy.ts
src/pools/selection-strategies/least-elu-worker-choice-strategy.ts
src/pools/selection-strategies/least-used-worker-choice-strategy.ts
src/pools/selection-strategies/round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.ts
tests/pools/selection-strategies/selection-strategies.test.js
tests/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.test.js

index 9058bed670386b13d551213ac93b50187b5597be..40a1eac2e3a5bbf876a31dab7c1fb765c87f42a0 100644 (file)
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - Optimize O(1) queue implementation.
+- Optimize worker choice strategies: pre-choose the worker node key by executing the choice algorithm after tasks submission.
 
 ## [2.6.2] - 2023-06-12
 
index a060cea3a5ba4486059fec1f6b45dac515f155e3..feb7d95763e562fa59e49f2da619d5662ec744c3 100644 (file)
@@ -26,6 +26,11 @@ export abstract class AbstractWorkerChoiceStrategy<
    */
   private toggleFindLastFreeWorkerNodeKey: boolean = false
 
+  /**
+   * Id of the next worker node.
+   */
+  protected nextWorkerNodeId: number = 0
+
   /** @inheritDoc */
   public readonly strategyPolicy: StrategyPolicy = {
     useDynamicWorker: false
index ff9eaf36a93791b2d4e0002c2decd22402e92607..39da59a0f2c2d0a16c58e9f4f8b9ce91cc915b92 100644 (file)
@@ -66,13 +66,7 @@ export class FairShareWorkerChoiceStrategy<
   /** @inheritDoc */
   public update (workerNodeKey: number): boolean {
     this.computeWorkerVirtualTaskEndTimestamp(workerNodeKey)
-    return true
-  }
-
-  /** @inheritDoc */
-  public choose (): number {
     let minWorkerVirtualTaskEndTimestamp = Infinity
-    let chosenWorkerNodeKey!: number
     for (const [workerNodeKey] of this.pool.workerNodes.entries()) {
       if (this.workersVirtualTaskEndTimestamp[workerNodeKey] == null) {
         this.computeWorkerVirtualTaskEndTimestamp(workerNodeKey)
@@ -81,10 +75,15 @@ export class FairShareWorkerChoiceStrategy<
         this.workersVirtualTaskEndTimestamp[workerNodeKey]
       if (workerVirtualTaskEndTimestamp < minWorkerVirtualTaskEndTimestamp) {
         minWorkerVirtualTaskEndTimestamp = workerVirtualTaskEndTimestamp
-        chosenWorkerNodeKey = workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
       }
     }
-    return chosenWorkerNodeKey
+    return true
+  }
+
+  /** @inheritDoc */
+  public choose (): number {
+    return this.nextWorkerNodeId
   }
 
   /** @inheritDoc */
index deaa48815e97f0b0d9fc8e42ffc845c6fa118982..85f3148374e5b0c5cf932425772779839f217ca0 100644 (file)
@@ -28,14 +28,10 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
   }
 
   /**
-   * Worker node id where the current task will be submitted.
-   */
-  private currentWorkerNodeId: number = 0
-  /**
-   * Current round id.
+   * Round id.
    * This is used to determine the current round weight.
    */
-  private currentRoundId: number = 0
+  private roundId: number = 0
   /**
    * Round weights.
    */
@@ -58,8 +54,8 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public reset (): boolean {
-    this.currentWorkerNodeId = 0
-    this.currentRoundId = 0
+    this.nextWorkerNodeId = 0
+    this.roundId = 0
     return true
   }
 
@@ -73,12 +69,12 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     let roundId: number | undefined
     let workerNodeId: number | undefined
     for (
-      let roundIndex = this.currentRoundId;
+      let roundIndex = this.roundId;
       roundIndex < this.roundWeights.length;
       roundIndex++
     ) {
       for (
-        let workerNodeKey = this.currentWorkerNodeId;
+        let workerNodeKey = this.nextWorkerNodeId;
         workerNodeKey < this.pool.workerNodes.length;
         workerNodeKey++
       ) {
@@ -91,32 +87,28 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
         }
       }
     }
-    this.currentRoundId = roundId ?? 0
-    this.currentWorkerNodeId = workerNodeId ?? 0
-    const chosenWorkerNodeKey = this.currentWorkerNodeId
-    if (this.currentWorkerNodeId === this.pool.workerNodes.length - 1) {
-      this.currentWorkerNodeId = 0
-      this.currentRoundId =
-        this.currentRoundId === this.roundWeights.length - 1
-          ? 0
-          : this.currentRoundId + 1
+    this.roundId = roundId ?? 0
+    this.nextWorkerNodeId = workerNodeId ?? 0
+    const chosenWorkerNodeKey = this.nextWorkerNodeId
+    if (this.nextWorkerNodeId === this.pool.workerNodes.length - 1) {
+      this.nextWorkerNodeId = 0
+      this.roundId =
+        this.roundId === this.roundWeights.length - 1 ? 0 : this.roundId + 1
     } else {
-      this.currentWorkerNodeId = this.currentWorkerNodeId + 1
+      this.nextWorkerNodeId = this.nextWorkerNodeId + 1
     }
     return chosenWorkerNodeKey
   }
 
   /** @inheritDoc */
   public remove (workerNodeKey: number): boolean {
-    if (this.currentWorkerNodeId === workerNodeKey) {
+    if (this.nextWorkerNodeId === workerNodeKey) {
       if (this.pool.workerNodes.length === 0) {
-        this.currentWorkerNodeId = 0
-      } else if (this.currentWorkerNodeId > this.pool.workerNodes.length - 1) {
-        this.currentWorkerNodeId = this.pool.workerNodes.length - 1
-        this.currentRoundId =
-          this.currentRoundId === this.roundWeights.length - 1
-            ? 0
-            : this.currentRoundId + 1
+        this.nextWorkerNodeId = 0
+      } else if (this.nextWorkerNodeId > this.pool.workerNodes.length - 1) {
+        this.nextWorkerNodeId = this.pool.workerNodes.length - 1
+        this.roundId =
+          this.roundId === this.roundWeights.length - 1 ? 0 : this.roundId + 1
       }
     }
     return true
index 58fbb51ecfd5012db79b6bc3cad5a714ba93d733..dc304c89fd1e5abecb680aa5d519d9a59f6f18ac 100644 (file)
@@ -57,25 +57,25 @@ export class LeastBusyWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public update (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public choose (): number {
     let minTime = Infinity
-    let leastBusyWorkerNodeKey!: number
     for (const [workerNodeKey, workerNode] of this.pool.workerNodes.entries()) {
       const workerTime =
         workerNode.workerUsage.runTime.aggregate +
         workerNode.workerUsage.waitTime.aggregate
       if (workerTime === 0) {
-        return workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
+        return true
       } else if (workerTime < minTime) {
         minTime = workerTime
-        leastBusyWorkerNodeKey = workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
       }
     }
-    return leastBusyWorkerNodeKey
+    return true
+  }
+
+  /** @inheritDoc */
+  public choose (): number {
+    return this.nextWorkerNodeId
   }
 
   /** @inheritDoc */
index 35ab69d0ca38595c2f81be566a0b7352bf900e4e..eca5a1cf541eca5d25102cacf821a08b556ad894 100644 (file)
@@ -57,24 +57,24 @@ export class LeastEluWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public update (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public choose (): number {
     let minWorkerElu = Infinity
-    let leastEluWorkerNodeKey!: number
     for (const [workerNodeKey, workerNode] of this.pool.workerNodes.entries()) {
       const workerUsage = workerNode.workerUsage
       const workerElu = workerUsage.elu?.active.aggregate ?? 0
       if (workerElu === 0) {
-        return workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
+        return true
       } else if (workerElu < minWorkerElu) {
         minWorkerElu = workerElu
-        leastEluWorkerNodeKey = workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
       }
     }
-    return leastEluWorkerNodeKey
+    return true
+  }
+
+  /** @inheritDoc */
+  public choose (): number {
+    return this.nextWorkerNodeId
   }
 
   /** @inheritDoc */
index 4161a1d266634518c5800d57a421acb46b690fe6..a505e6270aea7f19c458d1872aa2f69fe6b190cb 100644 (file)
@@ -37,17 +37,7 @@ export class LeastUsedWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public update (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public choose (): number {
-    const freeWorkerNodeKey = this.findFreeWorkerNodeKey()
-    if (freeWorkerNodeKey !== -1) {
-      return freeWorkerNodeKey
-    }
     let minNumberOfTasks = Infinity
-    let leastUsedWorkerNodeKey!: number
     for (const [workerNodeKey, workerNode] of this.pool.workerNodes.entries()) {
       const workerTaskStatistics = workerNode.workerUsage.tasks
       const workerTasks =
@@ -55,13 +45,19 @@ export class LeastUsedWorkerChoiceStrategy<
         workerTaskStatistics.executing +
         workerTaskStatistics.queued
       if (workerTasks === 0) {
-        return workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
+        return true
       } else if (workerTasks < minNumberOfTasks) {
         minNumberOfTasks = workerTasks
-        leastUsedWorkerNodeKey = workerNodeKey
+        this.nextWorkerNodeId = workerNodeKey
       }
     }
-    return leastUsedWorkerNodeKey
+    return true
+  }
+
+  /** @inheritDoc */
+  public choose (): number {
+    return this.nextWorkerNodeId
   }
 
   /** @inheritDoc */
index 936925313f99e9bafa6c3bb8c0a71f7ad0c94fad..0995b3506b102931ab59956a927a51f0aa5fbbbc 100644 (file)
@@ -27,11 +27,6 @@ export class RoundRobinWorkerChoiceStrategy<
     useDynamicWorker: true
   }
 
-  /**
-   * Id of the next worker node.
-   */
-  private nextWorkerNodeId: number = 0
-
   /** @inheritDoc */
   public constructor (
     pool: IPool<Worker, Data, Response>,
index 618b5065e01bd33a91fc9a170388ee063f2e8fcb..fc6f48cf2632a89e0fa1de1afe198ff37eb2cdb7 100644 (file)
@@ -48,10 +48,6 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
     }
   }
 
-  /**
-   * Worker node id where the current task will be submitted.
-   */
-  private currentWorkerNodeId: number = 0
   /**
    * Default worker weight.
    */
@@ -73,7 +69,7 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public reset (): boolean {
-    this.currentWorkerNodeId = 0
+    this.nextWorkerNodeId = 0
     this.workerVirtualTaskRunTime = 0
     return true
   }
@@ -85,7 +81,7 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public choose (): number {
-    const chosenWorkerNodeKey = this.currentWorkerNodeId
+    const chosenWorkerNodeKey = this.nextWorkerNodeId
     const workerVirtualTaskRunTime = this.workerVirtualTaskRunTime
     const workerWeight =
       this.opts.weights?.[chosenWorkerNodeKey] ?? this.defaultWorkerWeight
@@ -94,10 +90,10 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
         workerVirtualTaskRunTime +
         this.getWorkerTaskRunTime(chosenWorkerNodeKey)
     } else {
-      this.currentWorkerNodeId =
-        this.currentWorkerNodeId === this.pool.workerNodes.length - 1
+      this.nextWorkerNodeId =
+        this.nextWorkerNodeId === this.pool.workerNodes.length - 1
           ? 0
-          : this.currentWorkerNodeId + 1
+          : this.nextWorkerNodeId + 1
       this.workerVirtualTaskRunTime = 0
     }
     return chosenWorkerNodeKey
@@ -105,11 +101,11 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public remove (workerNodeKey: number): boolean {
-    if (this.currentWorkerNodeId === workerNodeKey) {
+    if (this.nextWorkerNodeId === workerNodeKey) {
       if (this.pool.workerNodes.length === 0) {
-        this.currentWorkerNodeId = 0
-      } else if (this.currentWorkerNodeId > this.pool.workerNodes.length - 1) {
-        this.currentWorkerNodeId = this.pool.workerNodes.length - 1
+        this.nextWorkerNodeId = 0
+      } else if (this.nextWorkerNodeId > this.pool.workerNodes.length - 1) {
+        this.nextWorkerNodeId = this.pool.workerNodes.length - 1
       }
       this.workerVirtualTaskRunTime = 0
     }
index 0df558deb71865434c57b2d3029f21222aa28b26..d7567114ed67d35f9288bd1399c327ac6d9911f4 100644 (file)
@@ -98,7 +98,7 @@ describe('Selection strategies test suite', () => {
         expect(
           pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
             workerChoiceStrategy
-          ).currentWorkerNodeId
+          ).nextWorkerNodeId
         ).toBe(0)
         expect(
           pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1069,7 +1069,7 @@ describe('Selection strategies test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.workerUsage).toStrictEqual({
         tasks: {
-          executed: maxMultiplier,
+          executed: expect.any(Number),
           executing: 0,
           queued: 0,
           failed: 0
@@ -1102,6 +1102,10 @@ describe('Selection strategies test suite', () => {
           utilization: expect.any(Number)
         }
       })
+      expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
+      expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
+        max * maxMultiplier
+      )
       expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
       expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0)
       expect(workerNode.workerUsage.elu.utilization).toBeGreaterThanOrEqual(0)
@@ -1166,12 +1170,12 @@ describe('Selection strategies test suite', () => {
           utilization: expect.any(Number)
         }
       })
-      expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
+      expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
         max * maxMultiplier
       )
-      expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
-      expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0)
+      expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThanOrEqual(0)
+      expect(workerNode.workerUsage.runTime.average).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.elu.utilization).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.elu.utilization).toBeLessThanOrEqual(1)
     }
@@ -1239,12 +1243,12 @@ describe('Selection strategies test suite', () => {
           utilization: expect.any(Number)
         }
       })
-      expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
+      expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
         max * maxMultiplier
       )
-      expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
-      expect(workerNode.workerUsage.runTime.median).toBeGreaterThan(0)
+      expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThanOrEqual(0)
+      expect(workerNode.workerUsage.runTime.median).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.elu.utilization).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.elu.utilization).toBeLessThanOrEqual(1)
     }
@@ -1637,7 +1641,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1653,7 +1657,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1674,7 +1678,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1690,7 +1694,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1849,12 +1853,12 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentRoundId
+      ).roundId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1931,12 +1935,12 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentRoundId
+      ).roundId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1961,12 +1965,12 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentRoundId
+      ).roundId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -1982,12 +1986,12 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentRoundId
+      ).roundId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -2012,12 +2016,12 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentRoundId
+      ).roundId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBeDefined()
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
@@ -2033,7 +2037,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategyContext.workerChoiceStrategy
-      ).currentWorkerNodeId
+      ).nextWorkerNodeId
     ).toBe(0)
     expect(
       pool.workerChoiceStrategyContext.workerChoiceStrategies.get(
index 3f94411c98c104b5ee82697e134c31f557886ccf..b21c41ff954f72aa3c1dee9d9381c985b2413e01 100644 (file)
@@ -35,7 +35,7 @@ describe('Weighted round robin strategy worker choice strategy test suite', () =
     )
     const resetResult = strategy.reset()
     expect(resetResult).toBe(true)
-    expect(strategy.currentWorkerNodeId).toBe(0)
+    expect(strategy.nextWorkerNodeId).toBe(0)
     expect(strategy.workerVirtualTaskRunTime).toBe(0)
   })
 })