rename allowPlace to cooldown and batch incrementing pixelcounts into a

single transaction
This commit is contained in:
HF 2022-09-07 13:16:05 +02:00
parent 100bdb17b5
commit 1fe9b70b9b
5 changed files with 141 additions and 84 deletions

View File

@ -7,7 +7,7 @@ import {
} from './utils';
import logger, { pixelLogger } from './logger';
import RedisCanvas from '../data/redis/RedisCanvas';
import allowPlace from '../data/redis/allowPlace';
import allowPlace from '../data/redis/cooldown';
import {
setPixelByOffset,
setPixelByCoords,
@ -70,7 +70,7 @@ export async function drawByOffsets(
let pxlCnt = 0;
let rankedPxlCnt = 0;
let needProxycheck = 0;
const { ip } = user;
const { ipSub: ip } = user;
try {
const startTime = Date.now();
@ -220,7 +220,7 @@ export async function drawByOffsets(
}
if (rankedPxlCnt) {
await user.incrementPixelcount(rankedPxlCnt);
user.incrementPixelcount(rankedPxlCnt);
}
if (retCode !== 13) {

View File

@ -7,11 +7,11 @@
* */
import { QueryTypes, Utils } from 'sequelize';
import redis from './redis/client';
import logger from '../core/logger';
import sequelize from './sql/sequelize';
import { RegUser, Channel, UserBlock } from './sql';
import { incrementPixelcount } from './sql/RegUser';
import { setCoolDown, getCoolDown } from './redis/cooldown';
import { getIPv6Subnet } from '../utils/ip';
import { ADMIN_IDS } from '../core/config';
@ -171,45 +171,16 @@ class User {
return (this.regUser) ? this.regUser.name : null;
}
async setWait(wait, canvasId) {
// PX is milliseconds expire
await redis.set(`cd:${canvasId}:ip:${this.ipSub}`, '', {
PX: wait,
});
if (this.id) {
await redis.set(`cd:${canvasId}:id:${this.id}`, '', {
PX: wait,
});
}
return true;
setWait(wait, canvasId) {
return setCoolDown(this.ipSub, this.id, canvasId, wait);
}
async getWait(canvasId) {
let ttl = await redis.pTTL(`cd:${canvasId}:ip:${this.ipSub}`);
if (this.id) {
const ttlid = await redis.pTTL(
`cd:${canvasId}:id:${this.id}`,
);
ttl = Math.max(ttl, ttlid);
}
logger.debug('ererer', ttl, typeof ttl);
const wait = ttl < 0 ? 0 : ttl;
return wait;
getWait(canvasId) {
return getCoolDown(this.ipSub, this.id, canvasId);
}
async incrementPixelcount(amount = 1) {
const { id } = this;
if (!id) return false;
try {
await this.regUser.increment(
['totalPixels', 'dailyTotalPixels'],
{ by: amount },
);
} catch (err) {
return false;
}
return true;
incrementPixelcount(amount = 1) {
incrementPixelcount(this.regUser, amount);
}
async getTotalPixels() {

View File

@ -1,43 +0,0 @@
/*
* redis script for user pixel placement
* this does not set any pixels itself, see lua/placePixel.lua
*/
import client from './client';
import { getIPv6Subnet } from '../../utils/ip';
import { PREFIX as CAPTCHA_PREFIX } from './captcha';
import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache';
import { CAPTCHA_TIME } from '../../core/config';
/*
* gets pixels and chunk coords and checks if
* and how many a user can set and sets the cooldown accordingly
* @param ip ip of request
* @param id userId
* @param clrIgnore, bcd, pcd, cds incormations about canvas
* @param i, j chunk coordinates
* @param pxls Array with offsets of pixels
* @return see lua/placePixel.lua
*/
export default function allowPlace(
ip,
id,
canvasId,
i, j,
clrIgnore,
bcd,
pcd,
cds,
pxls,
) {
const ipn = getIPv6Subnet(ip);
const isalKey = `${ALLOWED_PREFIX}:${ipn}`;
const captKey = (CAPTCHA_TIME >= 0) ? `${CAPTCHA_PREFIX}:${ipn}` : 'nope';
const ipCdKey = `cd:${canvasId}:ip:${ipn}`;
const idCdKey = (id) ? `cd:${canvasId}:id:${id}` : 'nope';
const chunkKey = `ch:${canvasId}:${i}:${j}`;
return client.placePxl(
isalKey, captKey, ipCdKey, idCdKey, chunkKey,
clrIgnore, bcd, pcd, cds,
...pxls,
);
}

View File

@ -0,0 +1,90 @@
/*
* redis script for cooldown calculation
* this does not set any pixels itself, see lua/placePixel.lua
*/
import client from './client';
import { PREFIX as CAPTCHA_PREFIX } from './captcha';
import { PREFIX as ALLOWED_PREFIX } from './isAllowedCache';
import { CAPTCHA_TIME } from '../../core/config';
const PREFIX = 'cd';
/*
* checks how many of the given pixels can be set,
* sets user cooldown and returns the number of pixels to set
* @param ip ip of request
* @param id userId
* @param clrIgnore, bcd, pcd, cds incormations about canvas
* @param i, j chunk coordinates
* @param pxls Array with offsets of pixels
* @return see lua/placePixel.lua
*/
export default function allowPlace(
ip,
id,
canvasId,
i, j,
clrIgnore,
bcd,
pcd,
cds,
pxls,
) {
const isalKey = `${ALLOWED_PREFIX}:${ip}`;
const captKey = (CAPTCHA_TIME >= 0) ? `${CAPTCHA_PREFIX}:${ip}` : 'nope';
const ipCdKey = `${PREFIX}:${canvasId}:ip:${ip}`;
const idCdKey = (id) ? `${PREFIX}:${canvasId}:id:${id}` : 'nope';
const chunkKey = `ch:${canvasId}:${i}:${j}`;
return client.placePxl(
isalKey, captKey, ipCdKey, idCdKey, chunkKey,
clrIgnore, bcd, pcd, cds,
...pxls,
);
}
/*
* get cooldown of specific user
* @param ip ip of request
* @param id userId
* @param canvasId
* @return cooldown
*/
export async function getCoolDown(
ip,
id,
canvasId,
) {
let ttl = await client.pTTL(`${PREFIX}:${canvasId}:ip:${ip}`);
if (id) {
const ttlid = await client.pTTL(`${PREFIX}:${canvasId}:id:${id}`);
ttl = Math.max(ttl, ttlid);
}
const cooldown = ttl < 0 ? 0 : ttl;
return cooldown;
}
/*
* set cooldown of specific user
* @param ip ip of request
* @param id userId
* @param canvasId
* @param cooldown (in ms)
* @return cooldown
*/
export async function setCoolDown(
ip,
id,
canvasId,
cooldown,
) {
// PX is milliseconds expire
await client.set(`${PREFIX}:${canvasId}:ip:${ip}`, '', {
PX: cooldown,
});
if (id) {
await client.set(`${PREFIX}:${canvasId}:id:${id}`, '', {
PX: cooldown,
});
}
return true;
}

View File

@ -6,8 +6,9 @@
*/
import { DataTypes, QueryTypes } from 'sequelize';
import sequelize from './sequelize';
import logger from '../../core/logger';
import sequelize from './sequelize';
import { generateHash } from '../../utils/hash';
@ -203,4 +204,42 @@ export async function getNamesToIds(ids) {
return idToNameMap;
}
/*
* increment user pixelcount in a batched transaction
*/
const incrementQueue = [];
let pushLoop = null;
const incrementLoop = async () => {
if (!incrementQueue.length) {
clearInterval(pushLoop);
pushLoop = null;
}
try {
sequelize.transaction(async (t) => {
while (incrementQueue.length) {
const [model, amount] = incrementQueue.pop();
// eslint-disable-next-line no-await-in-loop
await model.increment(
['totalPixels', 'dailyTotalPixels'],
{ by: amount, transaction: t },
);
}
return true;
});
} catch (err) {
logger.warn(`Error on batched incrementing pixelcounts: ${err.message}`);
}
};
// TODO remove this after testing
setInterval(() => {
// eslint-disable-next-line no-console
console.log('INCREMENTATION QUEUE SIZE', incrementQueue.length);
}, 300000);
export async function incrementPixelcount(model, amount) {
incrementQueue.push([model, amount]);
if (!pushLoop) {
pushLoop = setInterval(incrementLoop, 250);
}
}
export default RegUser;