UNPKG

@cocalc/database

Version:

CoCalc: code for working with our PostgreSQL database

133 lines 5.15 kB
"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