cache etags of chunks to avoid useless redis calls

This commit is contained in:
HF 2022-04-04 22:12:12 +02:00
parent 6d4f878798
commit c38637e788
2 changed files with 33 additions and 15 deletions

View File

@ -26,10 +26,16 @@ const chunks: Set<string> = new Set();
class RedisCanvas {
// callback that gets informed about chunk changes
static registerChunkChange = () => undefined;
// array of callback functions that gets informed about chunk changes
static registerChunkChange = [];
static setChunkChangeCallback(cb) {
RedisCanvas.registerChunkChange = cb;
RedisCanvas.registerChunkChange.push(cb);
}
static execChunkChangeCallback(canvasId, cell) {
for (let i = 0; i < RedisCanvas.registerChunkChange.length; i += 1) {
RedisCanvas.registerChunkChange[i](canvasId, cell);
}
}
static getChunk(
@ -53,7 +59,7 @@ class RedisCanvas {
}
const key = `ch:${canvasId}:${i}:${j}`;
await redis.setAsync(key, Buffer.from(chunk.buffer));
RedisCanvas.registerChunkChange(canvasId, [i, j]);
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
return true;
}
@ -61,7 +67,7 @@ class RedisCanvas {
const key = `ch:${canvasId}:${i}:${j}`;
await redis.delAsync(key);
chunks.delete(key);
RedisCanvas.registerChunkChange(canvasId, [i, j]);
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
return true;
}
@ -98,7 +104,7 @@ class RedisCanvas {
const args = [key, 'SET', UINT_SIZE, `#${offset}`, color];
await redis.sendCommandAsync('bitfield', args);
RedisCanvas.registerChunkChange(canvasId, [i, j]);
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
}
static async getPixelIfExists(

View File

@ -15,6 +15,13 @@ import {
} from '../core/constants';
import logger from '../core/logger';
const chunkEtags = new Map();
RedisCanvas.setChunkChangeCallback((canvasId, cell) => {
const [x, y] = cell;
const ret = chunkEtags.delete(`${canvasId}:${x}:${y}`);
logger.info(`etag delete ${ret}`);
});
/*
* Send binary chunk to the client
*/
@ -23,12 +30,10 @@ export default async (req: Request, res: Response, next) => {
c: paramC,
x: paramX,
y: paramY,
z: paramZ,
} = req.params;
const c = parseInt(paramC, 10);
const x = parseInt(paramX, 10);
const y = parseInt(paramY, 10);
const z = (paramZ) ? parseInt(paramZ, 10) : null;
try {
// botters where using cachebreakers to update via chunk API
// lets not allow that for now
@ -37,17 +42,24 @@ export default async (req: Request, res: Response, next) => {
return;
}
const etagKey = `${c}:${x}:${y}`;
let curEtag = chunkEtags.get(etagKey);
const preEtag = req.headers['if-none-match'];
if (curEtag && preEtag === curEtag) {
logger.info(`etag ${curEtag} still the same`);
res.status(304).end();
return;
}
let chunk;
// z is in preeration for 3d chunks that are also
// divided in height, which is not used yet
// - this is not used and probably won't ever be used
try {
const stime = Date.now();
chunk = await RedisCanvas.getChunk(c, x, y, z);
chunk = await RedisCanvas.getChunk(c, x, y);
const dur = Date.now() - stime;
if (dur > 1000) {
// eslint-disable-next-line max-len
logger.warn(`Long redis response times of ${dur}ms for chunk ${c}:${x},${y},${z}`);
logger.warn(`Long redis response times of ${dur}ms for chunk ${c}:${x},${y}`);
}
} catch {
res.status(503).end();
@ -71,11 +83,11 @@ export default async (req: Request, res: Response, next) => {
logger.error(`Chunk ${x},${y}/${c} has invalid length ${chunk.length}!`);
}
const curEtag = etag(chunk, { weak: true });
curEtag = etag(chunk, { weak: true });
res.set({
ETag: curEtag,
});
const preEtag = req.headers['if-none-match'];
chunkEtags.set(etagKey, curEtag);
if (preEtag === curEtag) {
res.status(304).end();
return;