Migrate redis client and configuration client from class syntax (#2802)

This commit is contained in:
Bruce Berrios 2022-04-05 12:41:20 -04:00 committed by GitHub
parent 20ec6679a7
commit a3fc6e9997
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 115 deletions

View file

@ -38,73 +38,69 @@ function mergeConfigurations(
merge(baseConfiguration, liveConfiguration);
}
class ConfigurationClient {
static configuration: MonkeyTypes.Configuration = BASE_CONFIGURATION;
static lastFetchTime = 0;
static databaseConfigurationUpdated = false;
let configuration = BASE_CONFIGURATION;
let lastFetchTime = 0;
let serverConfigurationUpdated = false;
static async getCachedConfiguration(
attemptCacheUpdate = false
): Promise<MonkeyTypes.Configuration> {
if (
attemptCacheUpdate &&
this.lastFetchTime < Date.now() - CONFIG_UPDATE_INTERVAL
) {
Logger.info("Cached configuration is stale.");
return await this.getLiveConfiguration();
}
return this.configuration;
export async function getCachedConfiguration(
attemptCacheUpdate = false
): Promise<MonkeyTypes.Configuration> {
if (
attemptCacheUpdate &&
lastFetchTime < Date.now() - CONFIG_UPDATE_INTERVAL
) {
Logger.info("Cached configuration is stale.");
return await getLiveConfiguration();
}
static async getLiveConfiguration(): Promise<MonkeyTypes.Configuration> {
this.lastFetchTime = Date.now();
const configurationCollection = db.collection("configuration");
try {
const liveConfiguration = await configurationCollection.findOne();
if (liveConfiguration) {
const baseConfiguration = _.cloneDeep(BASE_CONFIGURATION);
const liveConfigurationWithoutId = _.omit(
liveConfiguration,
"_id"
) as MonkeyTypes.Configuration;
mergeConfigurations(baseConfiguration, liveConfigurationWithoutId);
this.pushConfiguration(baseConfiguration);
this.configuration = baseConfiguration;
} else {
await configurationCollection.insertOne(BASE_CONFIGURATION); // Seed the base configuration.
}
} catch (error) {
Logger.logToDb(
"fetch_configuration_failure",
`Could not fetch configuration: ${error.message}`
);
}
return this.configuration;
}
static async pushConfiguration(
configuration: MonkeyTypes.Configuration
): Promise<void> {
if (this.databaseConfigurationUpdated) {
return;
}
try {
await db.collection("configuration").replaceOne({}, configuration);
this.databaseConfigurationUpdated = true;
} catch (error) {
Logger.logToDb(
"push_configuration_failure",
`Could not push configuration: ${error.message}`
);
}
}
return configuration;
}
export default ConfigurationClient;
export async function getLiveConfiguration(): Promise<MonkeyTypes.Configuration> {
lastFetchTime = Date.now();
const configurationCollection = db.collection("configuration");
try {
const liveConfiguration = await configurationCollection.findOne();
if (liveConfiguration) {
const baseConfiguration = _.cloneDeep(BASE_CONFIGURATION);
const liveConfigurationWithoutId = _.omit(
liveConfiguration,
"_id"
) as MonkeyTypes.Configuration;
mergeConfigurations(baseConfiguration, liveConfigurationWithoutId);
pushConfiguration(baseConfiguration);
configuration = baseConfiguration;
} else {
await configurationCollection.insertOne(BASE_CONFIGURATION); // Seed the base configuration.
}
} catch (error) {
Logger.logToDb(
"fetch_configuration_failure",
`Could not fetch configuration: ${error.message}`
);
}
return configuration;
}
async function pushConfiguration(
configuration: MonkeyTypes.Configuration
): Promise<void> {
if (serverConfigurationUpdated) {
return;
}
try {
await db.collection("configuration").replaceOne({}, configuration);
serverConfigurationUpdated = true;
} catch (error) {
Logger.logToDb(
"push_configuration_failure",
`Could not push configuration: ${error.message}`
);
}
}

View file

@ -1,52 +1,46 @@
import IORedis from "ioredis";
import Logger from "../utils/logger";
class RedisClient {
static connection: IORedis.Redis;
static connected = false;
let connection: IORedis.Redis;
let connected = false;
static async connect(): Promise<void> {
if (this.connected) {
export async function connect(): Promise<void> {
if (connected) {
return;
}
const { REDIS_URI, MODE } = process.env;
if (!REDIS_URI && MODE !== "dev") {
if (MODE === "dev") {
Logger.warning("No redis configuration provided. Running without redis.");
return;
}
const { REDIS_URI } = process.env;
if (!REDIS_URI) {
if (process.env.MODE === "dev") {
Logger.warning(
"No redis configuration provided. Running without redis."
);
return;
}
throw new Error("No redis configuration provided");
}
this.connection = new IORedis(REDIS_URI, {
maxRetriesPerRequest: null, // These options are required for BullMQ
enableReadyCheck: false,
lazyConnect: true,
});
try {
await this.connection.connect();
this.connected = true;
} catch (error) {
Logger.error(error.message);
Logger.error(
"Failed to connect to redis. Exiting with exit status code 1."
);
process.exit(1);
}
throw new Error("No redis configuration provided");
}
static isConnected(): boolean {
return this.connected;
}
connection = new IORedis(REDIS_URI, {
maxRetriesPerRequest: null, // These options are required for BullMQ
enableReadyCheck: false,
lazyConnect: true,
});
static getConnection(): IORedis.Redis {
return this.connection;
try {
await connection.connect();
connected = true;
} catch (error) {
Logger.error(error.message);
Logger.error(
"Failed to connect to redis. Exiting with exit status code 1."
);
process.exit(1);
}
}
export default RedisClient;
export function isConnected(): boolean {
return connected;
}
export function getConnection(): IORedis.Redis | undefined {
return connection;
}

View file

@ -3,7 +3,7 @@ import BotDAO from "../dao/bot";
import George from "../tasks/george";
import { Document, WithId } from "mongodb";
import LeaderboardsDAO from "../dao/leaderboards";
import ConfigurationClient from "../init/configuration";
import { getCachedConfiguration } from "../init/configuration";
const CRON_SCHEDULE = "30 14/15 * * * *";
const RECENT_AGE_MINUTES = 10;
@ -50,7 +50,7 @@ async function updateLeaderboardAndNotifyChanges(
});
if (newRecords.length > 0) {
const cachedConfig = await ConfigurationClient.getCachedConfiguration();
const cachedConfig = await getCachedConfiguration();
const leaderboardId = `time ${leaderboardTime} english`;

View file

@ -1,4 +1,4 @@
import ConfigurationClient from "../init/configuration";
import { getCachedConfiguration } from "../init/configuration";
import { Response, NextFunction } from "express";
async function contextMiddleware(
@ -6,7 +6,7 @@ async function contextMiddleware(
_res: Response,
next: NextFunction
): Promise<void> {
const configuration = await ConfigurationClient.getCachedConfiguration(true);
const configuration = await getCachedConfiguration(true);
req.ctx = {
configuration,

View file

@ -4,12 +4,12 @@ import admin, { ServiceAccount } from "firebase-admin";
import serviceAccount from "./credentials/serviceAccountKey.json"; // eslint-disable-line require-path-exists/exists
import db from "./init/db";
import jobs from "./jobs";
import ConfigurationClient from "./init/configuration";
import { getLiveConfiguration } from "./init/configuration";
import app from "./app";
import { Server } from "http";
import { version } from "./version";
import { recordServerVersion } from "./utils/prometheus";
import RedisClient from "./init/redis";
import * as RedisClient from "./init/redis";
import George from "./tasks/george";
import Logger from "./utils/logger";
@ -28,7 +28,7 @@ async function bootServer(port: number): Promise<Server> {
Logger.success("Firebase app initialized");
Logger.info("Fetching live configuration...");
await ConfigurationClient.getLiveConfiguration();
await getLiveConfiguration();
Logger.success("Live configuration fetched");
Logger.info("Connecting to redis...");

View file

@ -1,7 +1,7 @@
import { lock } from "../utils/misc";
import type IORedis from "ioredis";
import { Queue, QueueScheduler } from "bullmq";
import RedisClient from "../init/redis";
import { isConnected } from "../init/redis";
const QUEUE_NAME = "george-tasks";
@ -21,7 +21,7 @@ class George {
static jobQueue: Queue;
static jobQueueScheduler: QueueScheduler;
static initJobQueue(redisConnection: IORedis.Redis): void {
static initJobQueue(redisConnection: IORedis.Redis | undefined): void {
this.jobQueue = new Queue(QUEUE_NAME, {
connection: redisConnection,
defaultJobOptions: {
@ -101,5 +101,5 @@ class George {
}
export default lock(George, () => {
return !RedisClient.isConnected();
return !isConnected();
});