是否可以使用 Node 工作线程来执行数据库插入?

Is it possible to use Node worker threads to perform database inserts?

我最近读到了 Node 的“worker_threads”模块,它允许在多个线程中并行执行 Javascript 代码,这对于 CPU 密集型操作很有用。 (注意:这些不是 Chrome 在浏览器中制作的网络工作者)

我正在构建一项功能,我需要在不阻塞浏览器的情况下执行大量 Postgres INSERT。

问题是: 在我实例化 worker 的 Javascript 文件中,我不允许导入任何东西,包括本机 Node 模块或 NPM 库,例如Knex.js 这是进行数据库查询所必需的。我收到一条错误消息:无法在模块外使用 import 语句 一旦文件被执行。

我试过将工作代码放在另一个文件中,顶部有一个导入语句(同样的错误)。我尝试将 Knex 对象提供给 workerData,但它无法克隆非本机 JS 对象。

我没主意了 - 如果我们不能导入任何 NPM 库,有人知道如何在工作线程中与数据库交互吗?!?!

// mainThread.js

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

import knex from 'knex'; // --> *** UNCAUGHT EXCEPTION: Cannot use import statement outside a module ***

if (isMainThread) {
  module.exports = async function runWorker (rowsToInsert = []) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, { workerData: { rowsToInsert } });

      worker.on('message', (returningRows) => resolve(returningRows));
      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
      });
    });
  };
} else {
  const { rowsToInsert } = workerData;

  return knex('table').insert(rowsToInsert)
    .then((returningRows) => {
      parentPort.postMessage({ data: returningRows });
    });
}

我正在关注此网页中的教程:https://blog.logrocket.com/use-cases-for-node-workers/

这当然是可能的,但这是一个非常糟糕的主意。

数据库驱动程序已经是 JavaScript 线程的异步和非阻塞。按照您的建议将插入调用移动到单独的线程不仅不会提高性能,而且实际上会 降低 整体性能,因为涉及线程间通信的开销:

  • 同步和消息传递不是免费的
  • JavaScript 在线程间移动数据时使用 structured cloning。这意味着您的所有 rowsToInsert 都必须被复制,这是(相对)昂贵的。

一般来说,只有在您的 JavaScript 代码 执行 CPU 密集型工作时才真正适合使用 JS 线程。 node docs say as much right at the top:

Workers (threads) are useful for performing CPU-intensive JavaScript operations. They will not help much with I/O-intensive work. Node.js’s built-in asynchronous I/O operations are more efficient than Workers can be.

这意味着如果您要进行大量解析、数学运算或类似操作,则在线程中进行工作可能比较合适。然而,简单地将数据从一个地方铲到另一个地方(即,I/O)并不是线程的理想选择——毕竟,节点的设计被调整为在这种工作中高效。

你没有说你的 rowsToInsert 来自哪里,但如果它来自 HTTP 请求,那么使用线程是错误的。但是,如果您正在解析服务器上的 CSV 或 JSON 文件,可能 值得在线程中执行此操作,但重要的是线程完成所有工作工作(因此内存不需要在线程之间移动)。您 post 给工作人员的消息应该只是“处理位于 /foo/bar.csv 的文件”,然后工作线程完成其余的工作。


您遇到的错误是 :您正在尝试在常规的非模块 JS 文件中使用 import。将工作文件重命名为 *.mjs 或改用 require('knex')

节点的 ES module documentation 详细介绍了 importrequire 的区别。

Cannot use import statement outside a module

这只是抱怨您的代码使用的是导入的“导入”样式,但是"import" is only supported inside ES modules并且您的代码是在 CommonJS 上下文中。在 CommonJS 代码中,您需要使用 require() 代替:

const knex = require('knex');