imago-sql-memory-cache
Version:
Caches entire SQL tables in the memory of the Node.js process.
127 lines (105 loc) • 4.58 kB
JavaScript
;
const Sql = require('imago-sql');
/**
* Caches the entire SQL table in memory, under global.tables, and periodically
* refreshes it. Use it for small, but frequently accessed tables to improve
* performance and reduce SQL server load.
*/
class ImagoSqlMemoryCache {
constructor() {
this.configs = {};
/** Processed copies of SQL data */
global.sqlCache = global.sqlCache || { };
/** Unprocessed copies of SQL data */
global.tables = global.tables || {};
/** SQL connections */
global.sql = global.sql || {};
/** Promises that help other code know that it needs to await before the
* SQL data is fetched. */
global.promises = global.promises || {};
/** Global counter of SQL connection errors. */
global.sqlErrors = global.sqlErrors || {};
}
/**
* Adds a new SQL table to the list of auto-refreshed tables.
* @param {object} options - The parameters of the table to be added to the autorefresh list.
* @param {string} options.connString - The SQL connection string in the mssql:// format.
* @param {string} options.table - The name of the SQL table.
* @param {number} options.refreshEvery - Refresh period in ms.
* @param {function} options.callback - The callback to run on the data.
*/
add(options) {
// Cache the settings in case someone wants to refresh them.
this.configs[options.table] = options;
this.refresh(options);
console.log(`We will be refreshing the table ${options.table} every ${Math.round(options.refreshEvery / 1000)} seconds.`);
}
/**
* Updates the cached data in an SQL table.
* @param {object} options
* @param {string} options.connString - Connection string in mssql:// format
* @param {string} options.table - Name of SQL table
* @param {number} options.refreshEvery - How often to refresh the data from
* @param {function} options.callback - Callback to run once data is refreshed.
* SQL, in ms.
* @param {boolean} once - Only refresh once. Useful after you've updated
* the tables and want to refresh it immediately.
*/
async refresh(options, once = false) {
let resolveFunction;
try {
// Initialize DB connection and cache it globally:
if (!global.sql[options.connString]) {
global.sql[options.connString] = new Sql(options.connString);
global.sqlErrors[options.connString] = 0;
console.log('Initialized connection to ', options.connString);
}
// If the app service is just starting, delay DB queries by creating
// a Promise to delay the processing of the first message until
// we finish creating the data. Other code needs to await the promise from
// global.promises before proceeding.
if (!global.tables[options.table]) {
global.promises[options.table] = new Promise(resolve => {
resolveFunction = resolve;
// Just in case we never resolve for some reason, use a timeout here:
setTimeout(resolve, 30 * 1000);
});
}
// Fetch data from SQL:
let data = await global.sql[options.connString].selectAll(options.table);
global.tables[options.table] = data;
// console.log(`Refreshed SQL table ${options.table}`);
if (typeof options.callback === 'function') {
await options.callback(options.table);
}
// Once data is preloaded, resolve the Promise holding the first
// request to the endpoint:
if (resolveFunction) {
resolveFunction();
}
} catch (error) {
console.log(`Error when refreshing SQL table ${options.table}: `, error);
// After a few errors, the connection will be reinitialized by another
// piece of code:
global.sqlErrors[options.connString]++;
if (resolveFunction) {
resolveFunction();
}
}
// No matter the outcome, requeue the same operation again,
// because we will keep refreshing/retrying forever - unless a flag is set
// that says otherwise.
if (!once) {
setTimeout(() => this.refresh(options), options.refreshEvery);
}
}
/**
* Refreshes the given table once - useful if we just updated it.
* @param {string} tableName - The SQL table name.
*/
async refreshOnce(tableName) {
console.log(`Refreshing SQL table once: ${tableName}.`);
await this.refresh(this.configs[tableName], true);
}
}
module.exports = ImagoSqlMemoryCache;