perf: alternate worker selection between start and end of worker nodes
authorJérôme Benoit <jerome.benoit@sap.com>
Tue, 18 Apr 2023 18:07:08 +0000 (20:07 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Tue, 18 Apr 2023 18:07:08 +0000 (20:07 +0200)
in some worker choice strategies

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
CHANGELOG.md
package.json
pnpm-lock.yaml
src/pools/abstract-pool.ts
src/pools/pool.ts
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/less-busy-worker-choice-strategy.ts
src/pools/selection-strategies/less-used-worker-choice-strategy.ts

index 06ee9476e5b9c2c6853ab87ca0fbc935e6f606f3..c09124a9798897b22334b5c0f1e533016465ccfa 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Changed
+
+- Optimize free worker finding in worker choice strategies.
+
 ## [2.4.10] - 2023-04-15
 
 ### Fixed
index 3b20497143d66add03f6f36faf2f64d0e7014691..6b773ef299efc8a1bbfef8c1c53b53abb305bc36 100644 (file)
     "eslint-define-config": "^1.18.0",
     "eslint-import-resolver-typescript": "^3.5.5",
     "eslint-plugin-import": "^2.27.5",
-    "eslint-plugin-jsdoc": "^41.1.2",
+    "eslint-plugin-jsdoc": "^43.0.3",
     "eslint-plugin-n": "^15.7.0",
     "eslint-plugin-promise": "^6.1.1",
     "eslint-plugin-spellcheck": "^0.0.20",
     "prettier": "^2.8.7",
     "prettier-plugin-organize-imports": "^3.2.2",
     "release-it": "^15.10.1",
-    "rollup": "^3.20.4",
+    "rollup": "^3.20.6",
     "rollup-plugin-analyzer": "^4.0.0",
     "rollup-plugin-command": "^1.1.3",
     "rollup-plugin-delete": "^2.0.0",
index eec360d8574a341b93ee8adb8732e0a7a981fbfa..67ffcb3f66914ad21bae3c00f50c9ccc9b683ca6 100644 (file)
@@ -15,10 +15,10 @@ devDependencies:
     version: 3.1.0(release-it@15.10.1)
   '@rollup/plugin-terser':
     specifier: ^0.4.1
-    version: 0.4.1(rollup@3.20.4)
+    version: 0.4.1(rollup@3.20.6)
   '@rollup/plugin-typescript':
     specifier: ^11.1.0
-    version: 11.1.0(rollup@3.20.4)(typescript@5.0.4)
+    version: 11.1.0(rollup@3.20.6)(typescript@5.0.4)
   '@types/node':
     specifier: ^18.15.11
     version: 18.15.11
@@ -53,8 +53,8 @@ devDependencies:
     specifier: ^2.27.5
     version: 2.27.5(@typescript-eslint/parser@5.59.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.38.0)
   eslint-plugin-jsdoc:
-    specifier: ^41.1.2
-    version: 41.1.2(eslint@8.38.0)
+    specifier: ^43.0.3
+    version: 43.0.3(eslint@8.38.0)
   eslint-plugin-n:
     specifier: ^15.7.0
     version: 15.7.0(eslint@8.38.0)
@@ -95,8 +95,8 @@ devDependencies:
     specifier: ^15.10.1
     version: 15.10.1
   rollup:
-    specifier: ^3.20.4
-    version: 3.20.4
+    specifier: ^3.20.6
+    version: 3.20.6
   rollup-plugin-analyzer:
     specifier: ^4.0.0
     version: 4.0.0
@@ -719,7 +719,7 @@ packages:
       string-template: 1.0.0
     dev: true
 
-  /@rollup/plugin-terser@0.4.1(rollup@3.20.4):
+  /@rollup/plugin-terser@0.4.1(rollup@3.20.6):
     resolution: {integrity: sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -728,13 +728,13 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 3.20.4
+      rollup: 3.20.6
       serialize-javascript: 6.0.1
       smob: 0.0.6
-      terser: 5.16.9
+      terser: 5.17.0
     dev: true
 
-  /@rollup/plugin-typescript@11.1.0(rollup@3.20.4)(typescript@5.0.4):
+  /@rollup/plugin-typescript@11.1.0(rollup@3.20.6)(typescript@5.0.4):
     resolution: {integrity: sha512-86flrfE+bSHB69znnTV6kVjkncs2LBMhcTCyxWgRxLyfXfQrxg4UwlAqENnjrrxnSNS/XKCDJCl8EkdFJVHOxw==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -747,13 +747,13 @@ packages:
       tslib:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.20.4)
+      '@rollup/pluginutils': 5.0.2(rollup@3.20.6)
       resolve: 1.22.2
-      rollup: 3.20.4
+      rollup: 3.20.6
       typescript: 5.0.4
     dev: true
 
-  /@rollup/pluginutils@5.0.2(rollup@3.20.4):
+  /@rollup/pluginutils@5.0.2(rollup@3.20.6):
     resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -765,7 +765,7 @@ packages:
       '@types/estree': 1.0.0
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 3.20.4
+      rollup: 3.20.6
     dev: true
 
   /@sinclair/typebox@0.25.24:
@@ -2316,8 +2316,8 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-jsdoc@41.1.2(eslint@8.38.0):
-    resolution: {integrity: sha512-MePJXdGiPW7AG06CU5GbKzYtKpoHwTq1lKijjq+RwL/cQkZtBZ59Zbv5Ep0RVxSMnq6242249/n+w4XrTZ1Afg==}
+  /eslint-plugin-jsdoc@43.0.3(eslint@8.38.0):
+    resolution: {integrity: sha512-tHlpaUqB8ih2IhQw7Es/R3Z3anQZVfPUb33nUAVOgIcMugVYyD1ZE/KXjjN8HxykZsV1IXqrKZkKpUBrEi3G9Q==}
     engines: {node: ^14 || ^16 || ^17 || ^18 || ^19}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -5154,8 +5154,8 @@ packages:
       del: 5.1.0
     dev: true
 
-  /rollup@3.20.4:
-    resolution: {integrity: sha512-n7J4tuctZXUErM9Uc916httwqmTc63zzCr2+TLCiSCpfO/Xuk3g/marGN1IlRJZi+QF3XMYx75PxXRfZDVgaRw==}
+  /rollup@3.20.6:
+    resolution: {integrity: sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -5615,8 +5615,8 @@ packages:
     resolution: {integrity: sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==}
     dev: true
 
-  /terser@5.16.9:
-    resolution: {integrity: sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==}
+  /terser@5.17.0:
+    resolution: {integrity: sha512-3die3+pYW4mta4xF6K8Wtf7id8+oYyfqtAhjwzqY01+CfDSDMx/VA1Sp8sXWs5AVNIoAKoUfmp/gnPqRjBxuDA==}
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
index 4c78c28f1479f7988e10a9096710e1ec2e94c7cb..67a22ffe04898f590ab70669a9d49934d6f90ad6 100644 (file)
@@ -306,6 +306,20 @@ export abstract class AbstractPool<
     })
   }
 
+  /** @inheritDoc */
+  public findLastFreeWorkerNodeKey (): number {
+    // It requires node >= 18.0.0
+    // return this.workerNodes.findLastIndex(workerNode => {
+    //   return workerNode.tasksUsage?.running === 0
+    // })
+    for (let i = this.workerNodes.length - 1; i >= 0; i--) {
+      if (this.workerNodes[i].tasksUsage?.running === 0) {
+        return i
+      }
+    }
+    return -1
+  }
+
   /** @inheritDoc */
   public async execute (data?: Data): Promise<Response> {
     const [workerNodeKey, workerNode] = this.chooseWorkerNode()
index d540bbd267256677ba872fc6b08f7690f155dd5e..bf2ac2c251d3052169727dfd6c1bab1d031e7066 100644 (file)
@@ -141,7 +141,7 @@ export interface IPool<
    */
   readonly emitter?: PoolEmitter
   /**
-   * Finds a free worker node key based on the number of tasks the worker has applied.
+   * Finds the first free worker node key based on the number of tasks the worker has applied.
    *
    * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
    *
@@ -150,6 +150,16 @@ export interface IPool<
    * @returns A worker node key if there is one, `-1` otherwise.
    */
   findFreeWorkerNodeKey: () => number
+  /**
+   * Finds the last free worker node key based on the number of tasks the worker has applied.
+   *
+   * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
+   *
+   * If no free worker is found, `-1` is returned.
+   *
+   * @returns A worker node key if there is one, `-1` otherwise.
+   */
+  findLastFreeWorkerNodeKey: () => number
   /**
    * Executes the function specified in the worker constructor with the task data input parameter.
    *
index 2f1905782f7c4a6c3d6d4c98053b3965eaa8021d..bddb8b76566fd62545e1552618712dc39d98e7b9 100644 (file)
@@ -19,6 +19,10 @@ export abstract class AbstractWorkerChoiceStrategy<
   Data = unknown,
   Response = unknown
 > implements IWorkerChoiceStrategy {
+  /**
+   * Toggles finding the last free worker node key.
+   */
+  private toggleFindLastFreeWorkerNodeKey: boolean = false
   /** @inheritDoc */
   protected readonly isDynamicPool: boolean
   /** @inheritDoc */
@@ -68,4 +72,18 @@ export abstract class AbstractWorkerChoiceStrategy<
     this.checkOptions(opts)
     this.opts = opts
   }
+
+  /**
+   * Finds a free worker node key.
+   *
+   * @returns The free worker node key or `-1` if there is no free worker node.
+   */
+  protected findFreeWorkerNodeKey (): number {
+    if (this.toggleFindLastFreeWorkerNodeKey) {
+      this.toggleFindLastFreeWorkerNodeKey = false
+      return this.pool.findLastFreeWorkerNodeKey()
+    }
+    this.toggleFindLastFreeWorkerNodeKey = true
+    return this.pool.findFreeWorkerNodeKey()
+  }
 }
index d862405c2e07b224602e21093f235675ee8c750c..10fc3bf761f4908d8b1dd402f2ee2161310665f7 100644 (file)
@@ -45,7 +45,7 @@ export class LessBusyWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public choose (): number {
-    const freeWorkerNodeKey = this.pool.findFreeWorkerNodeKey()
+    const freeWorkerNodeKey = this.findFreeWorkerNodeKey()
     if (freeWorkerNodeKey !== -1) {
       return freeWorkerNodeKey
     }
index 51039c95173f8dda07116c0876b5f26ef93dbd5b..e3c395f477e4adb1d8a042ca71317c84111aae60 100644 (file)
@@ -37,7 +37,7 @@ export class LessUsedWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public choose (): number {
-    const freeWorkerNodeKey = this.pool.findFreeWorkerNodeKey()
+    const freeWorkerNodeKey = this.findFreeWorkerNodeKey()
     if (freeWorkerNodeKey !== -1) {
       return freeWorkerNodeKey
     }