better-sqlite3/docs/threads.md
2021-03-30 14:06:16 -05:00

98 lines
2.5 KiB
Markdown

# Worker threads
For most applications, `better-sqlite3` is fast enough to use in the main thread without blocking for a noticeable amount of time. However, if you need to perform very slow queries, you have the option of using [worker threads](https://nodejs.org/api/worker_threads.html) to keep things running smoothly. Below is an example of using a thread pool to perform queries in the background.
### worker.js
The worker logic is very simple in our case. It accepts messages from the master thread, executes each message's SQL (with any given parameters), and sends back the query results.
```js
const { parentPort } = require('worker_threads');
const db = require('better-sqlite3')('foobar.db');
parentPort.on('message', ({ sql, parameters }) => {
const result = db.prepare(sql).all(...parameters);
parentPort.postMessage(result);
});
```
### master.js
The master thread is responsible for spawning workers, respawning threads that crash, and accepting query jobs.
```js
const { Worker } = require('worker_threads');
const os = require('os');
/*
Export a function that queues pending work.
*/
const queue = [];
exports.asyncQuery = (sql, ...parameters) => {
return new Promise((resolve, reject) => {
queue.push({
resolve,
reject,
message: { sql, parameters },
});
drainQueue();
});
};
/*
Instruct workers to drain the queue.
*/
let workers = [];
function drainQueue() {
for (const worker of workers) {
worker.takeWork();
}
}
/*
Spawn workers that try to drain the queue.
*/
os.cpus().forEach(function spawn() {
const worker = new Worker('./worker.js');
let job = null; // Current item from the queue
let error = null; // Error that caused the worker to crash
function takeWork() {
if (!job && queue.length) {
// If there's a job in the queue, send it to the worker
job = queue.shift();
worker.postMessage(job.message);
}
}
worker
.on('online', () => {
workers.push({ takeWork });
takeWork();
})
.on('message', (result) => {
job.resolve(result);
job = null;
takeWork(); // Check if there's more work to do
})
.on('error', (err) => {
console.error(err);
error = err;
})
.on('exit', (code) => {
workers = workers.filter(w => w.takeWork !== takeWork);
if (job) {
job.reject(error || new Error('worker died'));
}
if (code !== 0) {
console.error(`worker exited with code ${code}`);
spawn(); // Worker died, so spawn a new one
}
});
});
```