Add redis caching for 404s

This commit is contained in:
Ajay Ramachandran
2021-02-20 21:13:46 -05:00
parent 597dff7ac3
commit 01d318d902
6 changed files with 73 additions and 13 deletions

View File

@@ -0,0 +1,5 @@
import { Category, VideoID } from "../types/segments.model";
export function skipSegmentsKey(videoID: VideoID): string {
return "segments-" + videoID;
}

View File

@@ -1,11 +1,14 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { RedisClient } from 'redis';
import { config } from '../config'; import { config } from '../config';
import { db, privateDB } from '../databases/databases'; import { db, privateDB } from '../databases/databases';
import { skipSegmentsKey } from '../middleware/redisKeys';
import { SBRecord } from '../types/lib.model'; import { SBRecord } from '../types/lib.model';
import { Category, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model"; import { Category, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model";
import { getHash } from '../utils/getHash'; import { getHash } from '../utils/getHash';
import { getIP } from '../utils/getIP'; import { getIP } from '../utils/getIP';
import { Logger } from '../utils/logger'; import { Logger } from '../utils/logger';
import redis from '../utils/redis';
function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Segment[] { function prepareCategorySegments(req: Request, videoID: VideoID, category: Category, segments: DBSegment[], cache: SegmentCache = {shadowHiddenSegmentIPs: {}}): Segment[] {
@@ -216,16 +219,29 @@ function chooseSegments(segments: DBSegment[]): DBSegment[] {
* *
* @returns * @returns
*/ */
function handleGetSegments(req: Request, res: Response) { async function handleGetSegments(req: Request, res: Response): Promise<Segment[] | false> {
const videoID = req.query.videoID as string; const videoID = req.query.videoID as VideoID;
// Default to sponsor // Default to sponsor
// If using params instead of JSON, only one category can be pulled // If using params instead of JSON, only one category can be pulled
console.log(req.query.categories)
const categories = req.query.categories const categories = req.query.categories
? JSON.parse(req.query.categories as string) ? JSON.parse(req.query.categories as string)
: req.query.category : req.query.category
? [req.query.category] ? [req.query.category]
: ['sponsor']; : ['sponsor'];
// Only 404s are cached at the moment
const redisResult = await redis.getAsync(skipSegmentsKey(videoID));
if (redisResult.reply) {
const redisSegments = JSON.parse(redisResult.reply);
if (redisSegments?.length === 0) {
res.sendStatus(404);
Logger.debug("Using segments from cache for " + videoID);
return false;
}
}
const segments = getSegmentsByVideoID(req, videoID, categories); const segments = getSegmentsByVideoID(req, videoID, categories);
if (segments === null || segments === undefined) { if (segments === null || segments === undefined) {
@@ -235,18 +251,27 @@ function handleGetSegments(req: Request, res: Response) {
if (segments.length === 0) { if (segments.length === 0) {
res.sendStatus(404); res.sendStatus(404);
// Save in cache
redis.setAsync(skipSegmentsKey(videoID), JSON.stringify(segments));
return false; return false;
} }
return segments; return segments;
} }
function endpoint(req: Request, res: Response): void { async function endpoint(req: Request, res: Response): Promise<void> {
let segments = handleGetSegments(req, res); try {
const segments = await handleGetSegments(req, res);
if (segments) { // If false, res.send has already been called
//send result if (segments) {
res.send(segments); //send result
res.send(segments);
}
} catch (err) {
res.status(500).send();
} }
} }

View File

@@ -1,8 +1,8 @@
import {handleGetSegments} from './getSkipSegments'; import {handleGetSegments} from './getSkipSegments';
import {Request, Response} from 'express'; import {Request, Response} from 'express';
export function oldGetVideoSponsorTimes(req: Request, res: Response) { export async function oldGetVideoSponsorTimes(req: Request, res: Response): Promise<void> {
let segments = handleGetSegments(req, res); let segments = await handleGetSegments(req, res);
if (segments) { if (segments) {
// Convert to old outputs // Convert to old outputs

View File

@@ -11,6 +11,8 @@ import {getFormattedTime} from '../utils/getFormattedTime';
import {isUserTrustworthy} from '../utils/isUserTrustworthy'; import {isUserTrustworthy} from '../utils/isUserTrustworthy';
import {dispatchEvent} from '../utils/webhookUtils'; import {dispatchEvent} from '../utils/webhookUtils';
import {Request, Response} from 'express'; import {Request, Response} from 'express';
import { skipSegmentsKey } from '../middleware/redisKeys';
import redis from '../utils/redis';
function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) {
@@ -496,6 +498,9 @@ export async function postSkipSegments(req: Request, res: Response) {
//add to private db as well //add to private db as well
privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]); privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]);
// Clear redis cache for this video
redis.delAsync(skipSegmentsKey(videoID));
} catch (err) { } catch (err) {
//a DB change probably occurred //a DB change probably occurred
res.sendStatus(502); res.sendStatus(502);

View File

@@ -11,6 +11,9 @@ import {getIP} from '../utils/getIP';
import {getHash} from '../utils/getHash'; import {getHash} from '../utils/getHash';
import {config} from '../config'; import {config} from '../config';
import { UserID } from '../types/user.model'; import { UserID } from '../types/user.model';
import redis from '../utils/redis';
import { skipSegmentsKey } from '../middleware/redisKeys';
import { VideoID } from '../types/segments.model';
const voteTypes = { const voteTypes = {
normal: 0, normal: 0,
@@ -335,7 +338,8 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
} }
//check if the increment amount should be multiplied (downvotes have more power if there have been many views) //check if the increment amount should be multiplied (downvotes have more power if there have been many views)
const row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]); const row = db.prepare('get', "SELECT videoID, votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]) as
{videoID: VideoID, votes: number, views: number};
if (voteTypeEnum === voteTypes.normal) { if (voteTypeEnum === voteTypes.normal) {
if ((isVIP || isOwnSubmission) && incrementAmount < 0) { if ((isVIP || isOwnSubmission) && incrementAmount < 0) {
@@ -383,6 +387,9 @@ export async function voteOnSponsorTime(req: Request, res: Response) {
db.prepare('run', "UPDATE sponsorTimes SET locked = 0 WHERE UUID = ?", [UUID]); db.prepare('run', "UPDATE sponsorTimes SET locked = 0 WHERE UUID = ?", [UUID]);
} }
// Clear redis cache for this video
redis.delAsync(skipSegmentsKey(row.videoID));
//for each positive vote, see if a hidden submission can be shown again //for each positive vote, see if a hidden submission can be shown again
if (incrementAmount > 0 && voteTypeEnum === voteTypes.normal) { if (incrementAmount > 0 && voteTypeEnum === voteTypes.normal) {
//find the UUID that submitted the submission that was voted on //find the UUID that submitted the submission that was voted on

View File

@@ -2,15 +2,33 @@ import {config} from '../config';
import {Logger} from './logger'; import {Logger} from './logger';
import redis, {Callback} from 'redis'; import redis, {Callback} from 'redis';
let exportObject = { interface RedisSB {
get: (key: string, callback?: Callback<string | null>) => callback(null, undefined), get(key: string, callback?: Callback<string | null>): void;
set: (key: string, value: string, callback?: Callback<string | null>) => callback(null, undefined) getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>;
set(key: string, value: string, callback?: Callback<string | null>): void;
setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>;
delAsync?(...keys: [string]): Promise<Error | null>;
}
let exportObject: RedisSB = {
get: (key, callback?) => callback(null, undefined),
getAsync: (key) =>
new Promise((resolve) => resolve({err: null, reply: undefined})),
set: (key, value, callback) => callback(null, undefined),
setAsync: (key, value) =>
new Promise((resolve) => resolve({err: null, reply: undefined})),
delAsync: (...keys) =>
new Promise((resolve) => resolve(null)),
}; };
if (config.redis) { if (config.redis) {
Logger.info('Connected to redis'); Logger.info('Connected to redis');
const client = redis.createClient(config.redis); const client = redis.createClient(config.redis);
exportObject = client; exportObject = client;
exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({err, reply})));
exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({err, reply})));
exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err)));
} }
export default exportObject; export default exportObject;