cache etags of chunks to avoid useless redis calls
This commit is contained in:
parent
6d4f878798
commit
c38637e788
|
@ -26,10 +26,16 @@ const chunks: Set<string> = new Set();
|
||||||
|
|
||||||
|
|
||||||
class RedisCanvas {
|
class RedisCanvas {
|
||||||
// callback that gets informed about chunk changes
|
// array of callback functions that gets informed about chunk changes
|
||||||
static registerChunkChange = () => undefined;
|
static registerChunkChange = [];
|
||||||
static setChunkChangeCallback(cb) {
|
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(
|
static getChunk(
|
||||||
|
@ -53,7 +59,7 @@ class RedisCanvas {
|
||||||
}
|
}
|
||||||
const key = `ch:${canvasId}:${i}:${j}`;
|
const key = `ch:${canvasId}:${i}:${j}`;
|
||||||
await redis.setAsync(key, Buffer.from(chunk.buffer));
|
await redis.setAsync(key, Buffer.from(chunk.buffer));
|
||||||
RedisCanvas.registerChunkChange(canvasId, [i, j]);
|
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +67,7 @@ class RedisCanvas {
|
||||||
const key = `ch:${canvasId}:${i}:${j}`;
|
const key = `ch:${canvasId}:${i}:${j}`;
|
||||||
await redis.delAsync(key);
|
await redis.delAsync(key);
|
||||||
chunks.delete(key);
|
chunks.delete(key);
|
||||||
RedisCanvas.registerChunkChange(canvasId, [i, j]);
|
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +104,7 @@ class RedisCanvas {
|
||||||
|
|
||||||
const args = [key, 'SET', UINT_SIZE, `#${offset}`, color];
|
const args = [key, 'SET', UINT_SIZE, `#${offset}`, color];
|
||||||
await redis.sendCommandAsync('bitfield', args);
|
await redis.sendCommandAsync('bitfield', args);
|
||||||
RedisCanvas.registerChunkChange(canvasId, [i, j]);
|
RedisCanvas.execChunkChangeCallback(canvasId, [i, j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getPixelIfExists(
|
static async getPixelIfExists(
|
||||||
|
|
|
@ -15,6 +15,13 @@ import {
|
||||||
} from '../core/constants';
|
} from '../core/constants';
|
||||||
import logger from '../core/logger';
|
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
|
* Send binary chunk to the client
|
||||||
*/
|
*/
|
||||||
|
@ -23,12 +30,10 @@ export default async (req: Request, res: Response, next) => {
|
||||||
c: paramC,
|
c: paramC,
|
||||||
x: paramX,
|
x: paramX,
|
||||||
y: paramY,
|
y: paramY,
|
||||||
z: paramZ,
|
|
||||||
} = req.params;
|
} = req.params;
|
||||||
const c = parseInt(paramC, 10);
|
const c = parseInt(paramC, 10);
|
||||||
const x = parseInt(paramX, 10);
|
const x = parseInt(paramX, 10);
|
||||||
const y = parseInt(paramY, 10);
|
const y = parseInt(paramY, 10);
|
||||||
const z = (paramZ) ? parseInt(paramZ, 10) : null;
|
|
||||||
try {
|
try {
|
||||||
// botters where using cachebreakers to update via chunk API
|
// botters where using cachebreakers to update via chunk API
|
||||||
// lets not allow that for now
|
// lets not allow that for now
|
||||||
|
@ -37,17 +42,24 @@ export default async (req: Request, res: Response, next) => {
|
||||||
return;
|
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;
|
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 {
|
try {
|
||||||
const stime = Date.now();
|
const stime = Date.now();
|
||||||
chunk = await RedisCanvas.getChunk(c, x, y, z);
|
chunk = await RedisCanvas.getChunk(c, x, y);
|
||||||
const dur = Date.now() - stime;
|
const dur = Date.now() - stime;
|
||||||
if (dur > 1000) {
|
if (dur > 1000) {
|
||||||
// eslint-disable-next-line max-len
|
// 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 {
|
} catch {
|
||||||
res.status(503).end();
|
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}!`);
|
logger.error(`Chunk ${x},${y}/${c} has invalid length ${chunk.length}!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const curEtag = etag(chunk, { weak: true });
|
curEtag = etag(chunk, { weak: true });
|
||||||
res.set({
|
res.set({
|
||||||
ETag: curEtag,
|
ETag: curEtag,
|
||||||
});
|
});
|
||||||
const preEtag = req.headers['if-none-match'];
|
chunkEtags.set(etagKey, curEtag);
|
||||||
if (preEtag === curEtag) {
|
if (preEtag === curEtag) {
|
||||||
res.status(304).end();
|
res.status(304).end();
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user