Merge pull request #156 from MRuy/147-add-cache-for-gettopusers

Add cache for getTopUsers
This commit is contained in:
Ajay Ramachandran
2020-10-14 18:48:47 -04:00
committed by GitHub
3 changed files with 116 additions and 60 deletions

View File

@@ -23,6 +23,7 @@
"readOnly": false,
"webhooks": [],
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], // List of supported categories any other category will be rejected
"getTopUsersCacheTimeMinutes": 5, // cacheTime for getTopUsers result in minutes
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
"hoursAfterWarningExpire": 24,
"rateLimit": {

View File

@@ -1,34 +1,18 @@
var db = require('../databases/databases.js').db;
const logger = require('../utils/logger.js');
const createMemoryCache = require('../utils/createMemoryCache.js');
const config = require('../config.js');
module.exports = function getTopUsers (req, res) {
let sortType = req.query.sortType;
let categoryStatsEnabled = req.query.categoryStats;
const MILLISECONDS_IN_MINUTE = 60000;
const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE);
if (sortType == undefined) {
//invalid request
res.sendStatus(400);
return;
}
//setup which sort type to use
let sortBy = "";
if (sortType == 0) {
sortBy = "minutesSaved";
} else if (sortType == 1) {
sortBy = "viewCount";
} else if (sortType == 2) {
sortBy = "totalSubmissions";
} else {
//invalid request
res.sendStatus(400);
return;
}
let userNames = [];
let viewCounts = [];
let totalSubmissions = [];
let minutesSaved = [];
let categoryStats = categoryStatsEnabled ? [] : undefined;
function generateTopUsersStats(sortBy, categoryStatsEnabled = false) {
return new Promise((resolve, reject) => {
const userNames = [];
const viewCounts = [];
const totalSubmissions = [];
const minutesSaved = [];
const categoryStats = categoryStatsEnabled ? [] : undefined;
let additionalFields = '';
if (categoryStatsEnabled) {
@@ -40,7 +24,7 @@ module.exports = function getTopUsers (req, res) {
"SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as categoryMusicOfftopic, ";
}
let rows = db.prepare('all', "SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
const rows = db.prepare('all', "SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," +
"SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " +
"SUM(votes) as userVotes, " +
additionalFields +
@@ -68,12 +52,41 @@ module.exports = function getTopUsers (req, res) {
}
}
//send this result
res.send({
resolve({
userNames,
viewCounts,
totalSubmissions,
minutesSaved,
categoryStats
});
});
}
module.exports = async function getTopUsers (req, res) {
let sortType = req.query.sortType;
let categoryStatsEnabled = req.query.categoryStats;
if (sortType == undefined) {
//invalid request
res.sendStatus(400);
return;
}
//setup which sort type to use
let sortBy = '';
if (sortType == 0) {
sortBy = 'minutesSaved';
} else if (sortType == 1) {
sortBy = 'viewCount';
} else if (sortType == 2) {
sortBy = 'totalSubmissions';
} else {
//invalid request
return res.sendStatus(400);
}
const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled);
//send this result
res.send(stats);
}

View File

@@ -0,0 +1,42 @@
module.exports = function createMemoryCache(memoryFn, cacheTimeMs) {
// holds the promise results
const cache = new Map();
// holds the promises that are not fulfilled
const promiseMemory = new Map();
return (...args) => {
// create cacheKey by joining arguments as string
const cacheKey = args.join('.');
// check if promising is already running
if (promiseMemory.has(cacheKey)) {
return promiseMemory.get(cacheKey);
}
else {
// check if result is in cache
if (cache.has(cacheKey)) {
const cacheItem = cache.get(cacheKey);
const now = Date.now();
// check if cache is valid
if (!(cacheItem.cacheTime + cacheTimeMs < now)) {
return Promise.resolve(cacheItem.result);
}
}
// create new promise
const promise = new Promise(async (resolve, reject) => {
resolve((await memoryFn(...args)));
});
// store promise reference until fulfilled
promiseMemory.set(cacheKey, promise);
return promise.then(result => {
// store promise result in cache
cache.set(cacheKey, {
result,
cacheTime: Date.now(),
});
// remove fulfilled promise from memory
promiseMemory.delete(cacheKey);
// return promise result
return result;
});
}
};
};