Можно ли использовать рабочие потоки Node для вставки в базу данных?

Недавно я прочитал о модуле Node worker_threads, который позволяет параллельное выполнение кода Javascript в нескольких потоках, что полезно для операций с интенсивным использованием ЦП. (ПРИМЕЧАНИЕ: это не веб-работники, созданные Chrome в браузере)

Я создаю функцию, в которой мне нужно делать огромное количество Postgres INSERT, не блокируя браузер.

Проблема в следующем: в моих файлах Javascript, где я создаю экземпляр рабочего, мне не разрешено импортировать что-либо, включая собственные модули Node или библиотеки NPM, такие как Knex.js, которые необходимы для выполнения запросов к базе данных. Я получаю сообщение об ошибке: Невозможно использовать оператор импорта вне модуля, как только файл будет выполнен.

Я пробовал поместить рабочий код в другой файл с оператором импорта вверху (та же ошибка). Я пробовал передать объект 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/


person Bruce Wang    schedule 15.07.2020    source источник
comment
Да, вы можете использовать для этого workerThreads, но это, вероятно, не требуется и не полезно. База данных сама по себе представляет собой отдельный процесс. Итак, все, что делает процесс nodejs, - это отправляет ему сетевые запросы неблокирующим асинхронным способом. nodejs вряд ли будет делать что-то интенсивное для ЦП, которое выиграет от workerThreads.   -  person jfriend00    schedule 16.07.2020
comment
К вашему сведению, при написании кода workerThread я считаю его на 200% чище, яснее и легче отлаживать, если вы поместите код workerThread в его собственный файл и не смешиваете код основного потока с кодом workerThread в том же файле. При этом вам не понадобится if (isMainThread) {}. Кроме того, полное интеллектуальное понимание того, какой код и где выполняется, будет намного проще, если вы разделите их на два файла.   -  person jfriend00    schedule 16.07.2020
comment
Спасибо за это объяснение! Для меня это имеет смысл! @ jfriend00   -  person Bruce Wang    schedule 16.07.2020
comment
@ jfriend00 Кстати, если у вас есть другое понимание - какие еще инструменты есть в моем распоряжении при попытке выполнить вставку большого объема с использованием Node (например, до 1 миллиона строк на прием)?   -  person Bruce Wang    schedule 16.07.2020
comment
nodejs - не ваша проблема. Задача состоит в том, чтобы сделать 1 миллион вставок в базу данных. Узким местом здесь будет база данных, а не nodejs. Для получения дополнительной помощи вы, вероятно, захотите задать новый вопрос о конкретной базе данных, которую вы используете, и о том, что именно вы пытаетесь вставить, а затем, возможно, кто-то, кто действительно знает эту базу данных, может поговорить о наиболее эффективных способах получения все эти данные в базу данных. Опять же, это не проблема nodejs, а вопрос о том, как оптимально использовать базу данных.   -  person jfriend00    schedule 17.07.2020


Ответы (1)


Это, конечно, возможно, но это очень плохая идея.

Драйверы базы данных уже асинхронны и неблокируют поток JavaScript. Перемещение ваших вызовов вставки в отдельный поток, как вы предлагаете, не только не приведет к увеличению производительности, но и фактически снизит общую производительность из-за накладных расходов, связанных с межпотоковой связью:

  • Синхронизация и передача сообщений платные
  • JavaScript использует структурированное клонирование при перемещении данных между потоками. Это означает, что все ваши rowsToInsert должны быть скопированы, что (относительно) дорого.

Как правило, единственный случай, когда действительно целесообразно использовать потоки JS, - это когда ваш код JavaScript выполняет работу, требующую интенсивного использования ЦП. Об этом же написано прямо вверху:

Рабочие (потоки) полезны для выполнения операций JavaScript с интенсивным использованием ЦП. Они не сильно помогут при работе с интенсивным вводом-выводом. Встроенные в Node.js операции асинхронного ввода-вывода более эффективны, чем могут быть Workers.

Это означает, что если вы много занимаетесь синтаксическим анализом, математикой или чем-то подобным, может быть целесообразно выполнить работу в потоке. Однако простой перенос данных из одного места в другое (т. Е. Ввод-вывод) не является хорошим кандидатом для потока - в конце концов, конструкция узла настроена так, чтобы быть эффективной для такого рода работы.

Вы не говорите, откуда взялся ваш rowsToInsert, но если он поступает из HTTP-запроса (ов), поток - это неправильная вещь для использования. Однако, если вы анализируете, например, файл CSV или JSON на сервере, может быть полезным сделать это в потоке, но важно, чтобы поток выполнял всю работу (поэтому память не требуется. перемещаться между потоками). Сообщение, которое вы отправляете рабочему, должно просто обрабатывать файл, расположенный в /foo/bar.csv, а затем рабочий поток сделает все остальное.


Вы получаете ту же ошибку, что и без рабочих потоков: вы пытаетесь использовать import в обычный немодульный JS-файл. Либо переименуйте рабочий файл в * .mjs, либо используйте вместо него require('knex').

документация по модулю ES узла подробно описывает, чем import отличается от require.

person josh3736    schedule 16.07.2020
comment
Спасибо за этот замечательный ответ! Это очень понятно, и ваш совет по использованию require сработал. Итак, какие инструменты доступны, если я хотел сделать вставку большого объема? Стоит ли использовать кластеры узлов? Я ищу в Google большие объемы вставок node или node cluster postgres Insert, но не получаю четкого ответа. - person Bruce Wang; 16.07.2020
comment
Это зависит от того, откуда поступают ваши данные. Откуда это взялось? - person josh3736; 16.07.2020
comment
Данные поступают в результате анализа нескольких файлов CSV в Node. Я перебираю данные и создаю массив объектов JS для вставки. В настоящее время я разбиваю вставки по 10К строк в каждом. И я запускаю Promises, чтобы вставлять их в Postgres один за другим (в отличие от одновременно с Promise.all) - person Bruce Wang; 22.07.2020
comment
@BruceWang: если ваши входные данные уже правильно сформированы, лучшим вариантом может быть COPY, который изначально импортирует CSV в таблицу. Поможет pg-copy-streams. Однако, если вы выполняете много синтаксического анализа и преобразования в узле, тогда может иметь смысл выполнять эту работу в рабочем потоке или процессе, но я бы профилировал, чтобы найти настоящее узкое место ( с) первый. Также ознакомьтесь с подготовленными операторами. - person josh3736; 22.07.2020
comment
… И вообще прочтите советы по производительности pg docs. - person josh3736; 22.07.2020