Skip to content

Commit

Permalink
dyk-counts: redis caching for web endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthvp committed Mar 24, 2024
1 parent 9701f78 commit 50db94c
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 17 deletions.
17 changes: 15 additions & 2 deletions eventstream-router/routes/dyk-counts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { bot } from "../../botbase";
import { Route } from "../app";
import { createLocalSSHTunnel } from "../../utils";
import { ENWIKI_DB_HOST, enwikidb } from "../../db";
import {Redis, getRedisInstance} from "../../redis";
import {RecentChangeStreamEvent} from "../RecentChangeStreamEvent";

export default class DykCountsTask extends Route {
name = 'dyk-counts';

db: enwikidb;
redis: Redis;

counts: Record<string, number> = {};
unflushedChanges: Record<string, string[]> = {};
Expand All @@ -24,6 +27,7 @@ export default class DykCountsTask extends Route {

await createLocalSSHTunnel(ENWIKI_DB_HOST);
this.db = new enwikidb();
this.redis = getRedisInstance();

bot.setOptions({ maxRetries: 0, defaultParams: { maxlag: undefined } });
await bot.getTokensAndSiteInfo();
Expand All @@ -50,6 +54,10 @@ export default class DykCountsTask extends Route {
`);
this.counts = Object.fromEntries(queryResult.map(e => [e.username, parseInt(e.noms as string)]));
await this.saveCounts('Refreshing counts from database');

const keyValues = queryResult.flatMap(e => [e.username, e.noms]) as string[];
await this.redis.del('dyk-counts').catch(e => this.redisError(e));
await this.redis.hmset.apply(null, ['dyk-counts'].concat(keyValues)).catch(e => this.redisError(e));
} catch (e) {
this.log(`[E] Error while running db refresh`, e);
}
Expand Down Expand Up @@ -81,15 +89,16 @@ export default class DykCountsTask extends Route {
}
}

filter(data): boolean {
filter(data: RecentChangeStreamEvent): boolean {
return data.wiki === 'enwiki' &&
data.type === 'new' &&
data.title.startsWith('Template:Did you know nominations/');
}

async worker(data) {
async worker(data: RecentChangeStreamEvent) {
let {user, title} = data;
this.counts[user] = (this.counts[user] || 0) + 1;
this.redis.hincrby('dyk-counts', user, 1).catch(e => this.redisError(e));
this.unflushedChanges[user] = (this.unflushedChanges[user] || []).concat(title);
this.log(`[i] Crediting "${user}" for [[${data.title}]]`);

Expand All @@ -103,4 +112,8 @@ export default class DykCountsTask extends Route {
}
}
}

redisError(err: Error) {
this.log(`[E] Redis error: `, err)
}
}
4 changes: 2 additions & 2 deletions redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { onToolforge, readFile } from "./utils";
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Omitted = Omit<redis.RedisClient, keyof redis.Commands<boolean>>;

interface Redis<T = redis.RedisClient> extends Omitted, redis.Commands<Promise<boolean>> {}
export interface Redis<T = redis.RedisClient> extends Omitted, redis.Commands<Promise<boolean>> {}

export const REDIS_HOST = 'tools-redis';

Expand Down Expand Up @@ -47,7 +47,7 @@ export function getRedisConfig(config: redis.ClientOpts = {}): redis.ClientOpts

/**
* For typical usage with the default options.
* Note: this can trigger a network request even though it doesn't take a callback or return a
* Note: this triggers a network request even though it doesn't take a callback or return a
* promise.
*/
export function getRedisInstance(): Redis {
Expand Down
32 changes: 19 additions & 13 deletions webservice/routes/dyk.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as express from "express";
import { enwikidb } from "../../../SDZeroBot/db";
import {getRedisInstance} from "../../redis";

const router = express.Router();
const db = new enwikidb();

// TODO: Fix up redis caching, last present in f541de61
const redis = getRedisInstance()

router.get('/credits/:user', async (req, res, next) => {
const user = req.params.user.replace(/ /g, '_');
Expand All @@ -22,18 +23,23 @@ router.get('/credits/:user', async (req, res, next) => {

router.get('/noms/:user', async (req, res, next) => {
const user = req.params.user.replace(/ /g, '_');
const result = await db.query(`
SELECT COUNT(*) AS count FROM revision_userindex
JOIN page ON rev_page = page_id
JOIN actor_revision ON rev_actor = actor_id
WHERE page_namespace = 10
AND page_is_redirect = 0
AND rev_parent_id = 0
AND page_title LIKE 'Did_you_know_nominations/%'
AND actor_name = ?
`, [user]);
const count = result[0].count;
res.end(String(count));

let count = await redis.hget('dyk-counts', user) as unknown as string;
if (!count) {
const result = await db.query(`
SELECT COUNT(*) AS count FROM revision_userindex
JOIN page ON rev_page = page_id
JOIN actor_revision ON rev_actor = actor_id
WHERE page_namespace = 10
AND page_is_redirect = 0
AND rev_parent_id = 0
AND page_title LIKE 'Did_you_know_nominations/%'
AND actor_name = ?
`, [user]);
count = String(result[0].count);
redis.hset('dyk-counts', user, count);
}
res.end(count);
});

export default router;

0 comments on commit 50db94c

Please sign in to comment.