diff --git a/src/config.ts b/src/config.ts index e2d56d2..54b7d74 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ import fs from "fs"; import { SBSConfig } from "./types/config.model"; import packageJson from "../package.json"; -import { isBoolean, isNumber } from "lodash"; +import { isNumber } from "lodash"; const isTestMode = process.env.npm_lifecycle_script === packageJson.scripts.test; const configFile = process.env.TEST_POSTGRES ? "ci.json" @@ -81,6 +81,17 @@ addDefaults(config, { rejectUnauthorized: false } }, + postgresReadOnly: { + enabled: false, + weight: 1, + user: "", + host: "", + password: "", + port: 5432, + ssl: { + rejectUnauthorized: false + } + }, dumpDatabase: { enabled: false, minTimeBetweenMs: 180000, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index d34da78..7fca688 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -3,6 +3,7 @@ import { IDatabase, QueryType } from "./IDatabase"; import { Client, Pool, PoolClient, types } from "pg"; import fs from "fs"; +import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; // return numeric (pg_type oid=1700) as float types.setTypeParser(1700, function(val) { @@ -14,16 +15,33 @@ types.setTypeParser(20, function(val) { return parseInt(val, 10); }); +export interface DatabaseConfig { + dbSchemaFileName: string, + dbSchemaFolder: string, + fileNamePrefix: string, + readOnly: boolean, + createDbIfNotExists: boolean, + postgres: CustomPostgresConfig, + postgresReadOnly: CustomPostgresReadOnlyConfig +} + export class Postgres implements IDatabase { private pool: Pool; + private poolRead: Pool; - constructor(private config: Record) {} + constructor(private config: DatabaseConfig) {} async init(): Promise { this.pool = new Pool(this.config.postgres); - this.pool.on("error", (err) => { + const errorHandler = (err: Error) => { Logger.error(err.stack); - }); + }; + this.pool.on("error", errorHandler); + + if (this.config.postgresReadOnly) { + this.poolRead = new Pool(this.config.postgresReadOnly); + this.poolRead.on("error", errorHandler); + } if (!this.config.readOnly) { if (this.config.createDbIfNotExists) { @@ -60,7 +78,7 @@ export class Postgres implements IDatabase { let client: PoolClient; try { - client = await this.pool.connect(); + client = await this.getClient(type); const queryResult = await client.query({ text: query, values: params }); switch (type) { @@ -85,6 +103,15 @@ export class Postgres implements IDatabase { } } + private getClient(type: string): Promise { + if (this.poolRead && (type === "get" || type === "all") + && Math.random() > 1 / (this.config.postgresReadOnly.weight + 1)) { + return this.poolRead.connect(); + } else { + return this.pool.connect(); + } + } + private async createDB() { const client = new Client({ ...this.config.postgres, diff --git a/src/databases/databases.ts b/src/databases/databases.ts index 4bea8c7..e2c225b 100644 --- a/src/databases/databases.ts +++ b/src/databases/databases.ts @@ -19,7 +19,11 @@ if (config.mysql) { postgres: { ...config.postgres, database: "sponsorTimes", - } + }, + postgresReadOnly: config.postgresReadOnly ? { + ...config.postgresReadOnly, + database: "sponsorTimes" + } : null }); privateDB = new Postgres({ @@ -31,7 +35,11 @@ if (config.mysql) { postgres: { ...config.postgres, database: "privateDB" - } + }, + postgresReadOnly: config.postgresReadOnly ? { + ...config.postgresReadOnly, + database: "privateDB" + } : null }); } else { db = new Sqlite({ diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 8258005..774bd01 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -5,10 +5,14 @@ interface RedisConfig extends redis.RedisClientOptions { enabled: boolean; } -interface CustomPostgresConfig extends PoolConfig { +export interface CustomPostgresConfig extends PoolConfig { enabled: boolean; } +export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { + weight: number; +} + export interface SBSConfig { [index: string]: any port: number; @@ -51,6 +55,7 @@ export interface SBSConfig { redis?: RedisConfig; maxRewardTimePerSegmentInSeconds?: number; postgres?: CustomPostgresConfig; + postgresReadOnly?: CustomPostgresReadOnlyConfig; dumpDatabase?: DumpDatabase; diskCacheURL: string; crons: CronJobOptions;