piscina

repository·current·Indexed 26 days ago

https://github.com/piscinajs/piscina

A fast, efficient Node.js Worker Thread Pool implementation designed to handle fixed and variable task scenarios. It features flexible pool sizing, efficient thread communication, and support for ECMAScript Modules (ESM). Key capabilities include task cancellation via AbortController, backpressure management with maxQueue, custom task queues like FixedQueue, and detailed performance monitoring metrics such as utilization and execution time histograms.

Tokens
16.3K
Snippets
41
Records
97
Agent score
89%

What's inside piscina

  1. Introduction to Piscina.js

    current
    Piscina.js is a Node.js worker pool library designed to run CPU-intensive tasks in parallel using worker threads. It provides an API to offload computationally expensive tasks to a managed pool of workers, improving the performance and scalability of Node.js applications by preventing the overhead and performance issues associated with manually managing thousands of concurrent worker threads.
  2. Key features of Piscina.js

    current

    Piscina provides several advanced capabilities for managing worker threads:

    • Performance: Fast communication between threads and support for flexible pool sizes.
    • Task Management: Covers both fixed-task and variable-task scenarios, including custom task queues and cancellation support.
    • Observability: Proper async tracking integration and tracking statistics for run and wait times.
    • Resource Control: Supports enforcing memory resource limits and optional CPU scheduling priorities on Linux.
    • Compatibility: Supports CommonJS, ESM, and TypeScript.
  3. Understand Piscina's default load balancing algorithm

    current
    By default, Piscina uses a Dynamic load balancing algorithm based on a Resource Based (least-busy) approach. This algorithm distributes tasks to the worker that is currently the least busy, aiming to adapt to the environment and minimize the time required to finish individual workloads by making better use of available resources.
  4. Install Piscina via npm, yarn, pnpm, or Bun

    current

    To install the latest version of Piscina.js and its dependencies, use your preferred package manager. Note that you must have Node.js version 22 or higher installed on your system.

    :::note While Bun can be used to install Piscina, its behavior while running within Bun is not assured. :::

    npm install piscina
    # or
    yarn add piscina
    # or
    pnpm add piscina
    # or
    bun add piscina
  5. Set worker thread priority on Linux

    current

    On Linux systems, you can set the CPU scheduling priority of workers using the niceIncrement option. This requires the @napi-rs/nice native addon.

    1. Install the dependency: npm i @napi-rs/nice
    2. Pass niceIncrement to the Piscina constructor.

    A higher niceIncrement results in lower CPU priority for workers, which helps prevent them from starving the main Node.js event loop thread.

    const Piscina = require("piscina");
    const pool = new Piscina({
      worker: "/absolute/path/to/worker.js",
      niceIncrement: 20,
    });
  6. Delay worker availability during initialization

    current

    By default, Piscina makes a worker available as soon as it loads the exported handler. If your worker requires asynchronous initialization (e.g., connecting to a database), you can export a Promise that resolves to the handler function. Piscina will await this Promise before marking the worker as ready to process tasks.

    async function initialize() {
      await someAsyncInitializationActivity();
      return ({ a, b }) => a + b;
    }
    
    module.exports = initialize();
  7. Support ECMAScript Modules (ESM)

    current

    To use ESM with Piscina, ensure the filename provided to the Piscina constructor is a valid file:// URL. In your worker module, use export default or named exports.

    import { Piscina } from "piscina";
    
    const piscina = new Piscina({
      // The URL must be a file:// URL
      filename: new URL("./worker.mjs", import.meta.url).href,
    });
    
    const result = await piscina.run({ a: 4, b: 6 });
  8. Avoid worker thread thrashing

    current

    If the rate of incoming tasks is inconsistent, Piscina may experience 'thrashing'—the excessive creation and termination of workers. To prevent this, use one or a combination of the following strategies:

    1. Maintain Queue Pressure: Ensure the rate of new tasks is sufficient to keep workers busy.
    2. Increase idleTimeout: Increase the time a worker stays alive while idle before being terminated. This prevents immediate shutdown when a brief lull in tasks occurs.
    3. Increase minThreads: Increase the minimum number of threads that Piscina always maintains. This ensures a baseline of workers is always available, even during low pressure.
  9. Use Inline Worker Code with TypeScript

    current

    You can combine your main application and worker code into a single file by using the isMainThread flag from worker_threads. If isMainThread is true, you initialize Piscina using __filename. Otherwise, you export the worker function as the default export.

    import Piscina from 'piscina';
    import { isMainThread } from 'worker_threads';
    
    interface Inputs {
      a: number;
      b: number;
    }
    
    if (isMainThread) {
      const piscina = new Piscina({ filename: __filename });
    
      (async () => {
        const task: Inputs = { a: 1, b: 2 };
        console.log(await piscina.run(task));
      })();
    } else {
      export default ({ a, b }: Inputs): number => {
        return a + b;
      };
    }
  10. Manage task overload with backpressure using `maxQueue` and `drain`

    current

    When using the maxQueue option, you can prevent memory exhaustion by implementing backpressure. When pool.queueSize reaches pool.options.maxQueue, you should pause your data source (e.g., a Node.js stream). Listen for the 'drain' event on the Piscina pool instance to know when the queue has been cleared and it is safe to resume the data source.

    const Pool = require("piscina");
    const { resolve } = require("path");
    
    const pool = new Pool({
      filename: resolve(__dirname, "worker.js"),
      maxQueue: "auto",
    });
    
    const stream = getStreamSomehow();
    
    // Resume stream when queue is empty
    pool.on("drain", () => {
      if (stream.isPaused()) {
        stream.resume();
      }
    });
    
    stream
      .on("data", (data) => {
        pool.run(data);
        // Pause stream if queue is full
        if (pool.queueSize === pool.options.maxQueue) {
          stream.pause();
        }
      })
      .on("error", console.error)
      .on("end", () => {
        console.log("done");
      });
  11. Set up Piscina in JavaScript

    current

    To use Piscina in a JavaScript project, instantiate Piscina by providing the absolute path to your worker file via the filename option. You can then execute tasks using the .run() method, which returns a Promise that resolves to the worker's result.

    const path = require("path");
    const Piscina = require("piscina");
    
    // Create a new Piscina instance pointing to your worker file
    const piscina = new Piscina({
      filename: path.resolve(__dirname, "worker.js"),
    });
    
    // Run a task using Piscina
    (async () => {
      const result = await piscina.run({ a: 4, b: 6 });
      console.log(result); // prints 10
    })();