@cocalc/database
Version:
CoCalc: code for working with our PostgreSQL database
133 lines • 5.15 kB
JavaScript
"use strict";
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.query = void 0;
/*
Does the queries to update changefeeds, deduplicating across
both all changefeeds and a small interval of time.
*/
// set to false to completely disable for debugging/testing
const THROTTLE = true;
// 10ms when running unit tests, still throttle, but make it quick.
// Otherwise, we default to 250ms, which is enough to be massively
// useful, but also not noticed by user.
let THROTTLE_MS = process.env.SMC_TEST ? 10 : 500;
// THROTTLE_MS can be overridden the POSTGRES_THROTTLE_CHANGEFEED_MS
// environment variable.
if (process.env.POSTGRES_THROTTLE_CHANGEFEED_MS != null) {
THROTTLE_MS = parseInt(process.env.POSTGRES_THROTTLE_CHANGEFEED_MS);
}
const events_1 = require("events");
const awaiting_1 = require("awaiting");
const async_utils_1 = require("@cocalc/util/async-utils");
const misc_1 = require("@cocalc/util/misc");
const { one_result, all_results } = require("../postgres-base");
function key(obj) {
return `query-${JSON.stringify(obj)}`;
}
class ThrottledTableQueue extends events_1.EventEmitter {
constructor(db, table, interval_ms) {
super();
this.queue = {};
this.state = "ready";
this.db = db;
this.table = table;
this.interval_ms = interval_ms;
// client listens for results of query -- if queries pile up, and
// there are many tables, the default of 10 can easily be exceeded.
this.setMaxListeners(100);
}
dbg(f) {
return this.db._dbg(`ThrottledTableQueue('${this.table}').${f}`);
}
close() {
if (this.state == "closed") {
return;
}
if (this.process_timer != null) {
clearTimeout(this.process_timer);
}
for (const k in this.queue) {
this.emit(k, "closed");
}
this.emit("closed");
this.removeAllListeners();
(0, misc_1.close)(this);
this.state = "closed";
}
enqueue(query) {
if (this.state == "closed") {
throw Error("trying to enqueue after close");
}
const k = key(query);
this.queue[k] = query;
if (this.process_timer == null) {
this.dbg("enqueue")(`will process queue in ${this.interval_ms}ms...`);
this.process_timer = setTimeout(this.process_queue.bind(this), this.interval_ms);
}
return k;
}
async process_queue() {
const dbg = this.dbg("process_queue");
delete this.process_timer; // it just fired
// First time we just doing them one at a time.
// FURTHER WORK: we could instead do ALL queries simultaneously as
// a single query, or at least do them in parallel...
// Make a copy of whatever is queued up at this moment in time,
// then clear the queue. We are responsible for handling all
// queries in the queue now, but nothing further. If queries are
// slow, it can easily be the case that process_queue is
// called several times at once. However, that's fine, since
// each call is responsible for only the part of the queue that
// existed when it was called.
const queue = (0, misc_1.copy)(this.queue);
this.queue = {};
for (const k in queue) {
dbg(k);
const { select, where } = queue[k];
try {
const result = await (0, awaiting_1.callback)(one_query, this.db, select, this.table, where);
if (this.state == "closed")
return;
dbg("success", k);
this.emit(k, undefined, result);
}
catch (err) {
if (this.state == "closed")
return;
dbg("fail", k);
this.emit(k, err, undefined);
}
}
}
}
const throttled_table_queues = {};
function throttled_table_queue(db, table, interval_ms) {
if (throttled_table_queues[table] != null) {
return throttled_table_queues[table];
}
return (throttled_table_queues[table] = new ThrottledTableQueue(db, table, interval_ms));
}
async function query(opts) {
if (THROTTLE && opts.one) {
const Table = throttled_table_queue(opts.db, opts.table, THROTTLE_MS);
const k = Table.enqueue({ select: opts.select, where: opts.where });
const [err, result] = await (0, async_utils_1.once)(Table, k);
if (err != null) {
throw err;
}
return result;
}
return await (0, awaiting_1.callback)(opts.one ? one_query : all_query, opts.db, opts.select, opts.table, opts.where);
}
exports.query = query;
function all_query(db, select, table, where, cb) {
db._query({ select, table, where, cb: all_results(cb) });
}
function one_query(db, select, table, where, cb) {
db._query({ select, table, where, cb: one_result(cb) });
}
//# sourceMappingURL=changefeed-query.js.map