Add read only redis ability

This commit is contained in:
Ajay
2022-10-09 16:10:46 -04:00
parent b417241ca0
commit 9386f25f9f
4 changed files with 55 additions and 4 deletions

View File

@@ -139,6 +139,15 @@ addDefaults(config, {
expiryTime: 24 * 60 * 60,
getTimeout: 40
},
redisRead: {
enabled: false,
socket: {
host: "",
port: 0
},
disableOfflineQueue: true,
weight: 1
},
patreon: {
clientId: "",
clientSecret: "",

View File

@@ -178,11 +178,11 @@ export class Postgres implements IDatabase {
private getPool(type: string, options: QueryOption): Pool {
const readAvailable = this.poolRead && options.useReplica && this.isReadQuery(type);
const ignroreReadDueToFailure = this.config.postgresReadOnly.fallbackOnFail
const ignoreReadDueToFailure = this.config.postgresReadOnly.fallbackOnFail
&& this.lastPoolReadFail > Date.now() - 1000 * 30;
const readDueToFailure = this.config.postgresReadOnly.fallbackOnFail
&& this.lastPoolFail > Date.now() - 1000 * 30;
if (readAvailable && !ignroreReadDueToFailure && (options.forceReplica || readDueToFailure ||
if (readAvailable && !ignoreReadDueToFailure && (options.forceReplica || readDueToFailure ||
Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) {
return this.poolRead;
} else {

View File

@@ -7,6 +7,11 @@ interface RedisConfig extends redis.RedisClientOptions {
getTimeout: number;
}
interface RedisReadOnlyConfig extends redis.RedisClientOptions {
enabled: boolean;
weight: number;
}
export interface CustomPostgresConfig extends PoolConfig {
enabled: boolean;
maxTries: number;
@@ -61,6 +66,7 @@ export interface SBSConfig {
minimumPrefix?: string;
maximumPrefix?: string;
redis?: RedisConfig;
redisRead?: RedisReadOnlyConfig;
maxRewardTimePerSegmentInSeconds?: number;
postgres?: CustomPostgresConfig;
postgresReadOnly?: CustomPostgresReadOnlyConfig;

View File

@@ -25,19 +25,35 @@ let exportClient: RedisSB = {
quit: () => new Promise((resolve) => resolve(null)),
};
let lastClientFail = 0;
let lastReadFail = 0;
if (config.redis?.enabled) {
Logger.info("Connected to redis");
const client = createClient(config.redis);
const readClient = config.redisRead?.enabled ? createClient(config.redisRead) : null;
void client.connect(); // void as we don't care about the promise
void readClient?.connect();
exportClient = client as RedisSB;
const get = client.get.bind(client);
const getRead = readClient?.get?.bind(readClient);
exportClient.get = (key) => new Promise((resolve, reject) => {
const timeout = config.redis.getTimeout ? setTimeout(() => reject(), config.redis.getTimeout) : null;
get(key).then((reply) => {
const chosenGet = pickChoice(get, getRead);
chosenGet(key).then((reply) => {
if (timeout !== null) clearTimeout(timeout);
resolve(reply);
}).catch((err) => reject(err));
}).catch((err) => {
if (chosenGet === get) {
lastClientFail = Date.now();
} else {
lastReadFail = Date.now();
}
reject(err);
});
});
exportClient.increment = (key) => new Promise((resolve, reject) =>
void client.multi()
@@ -48,11 +64,31 @@ if (config.redis?.enabled) {
.catch((err) => reject(err))
);
client.on("error", function(error) {
lastClientFail = Date.now();
Logger.error(`Redis Error: ${error}`);
});
client.on("reconnect", () => {
Logger.info("Redis: trying to reconnect");
});
readClient?.on("error", function(error) {
lastReadFail = Date.now();
Logger.error(`Redis Read-Only Error: ${error}`);
});
readClient?.on("reconnect", () => {
Logger.info("Redis Read-Only: trying to reconnect");
});
}
function pickChoice<T>(client: T, readClient: T): T {
const readAvailable = !!readClient;
const ignoreReadDueToFailure = lastReadFail > Date.now() - 1000 * 30;
const readDueToFailure = lastClientFail > Date.now() - 1000 * 30;
if (readAvailable && !ignoreReadDueToFailure && (readDueToFailure ||
Math.random() > 1 / (config.redisRead?.weight + 1))) {
return readClient;
} else {
return client;
}
}
export default exportClient;