Skip to content

Commit

Permalink
feat(db): Implement db cache (Closes #72)
Browse files Browse the repository at this point in the history
For most frequent db request we want to implement cache
which will decrease db usage
  • Loading branch information
n0th1ng-else committed Jun 17, 2020
1 parent cfe21e5 commit bf1f040
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 48 deletions.
2 changes: 2 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const ngRokToken: string = process.env.NGROK_TOKEN || "";
export const authorTelegramAccount: string =
process.env.AUTHOR_TELEGRAM_ACCOUNT || "";

export const cacheSize: number = Number(process.env.CACHE_SIZE) || 50;

export const googleApi = {
projectId: process.env.GOOGLE_PROJECT_ID || "",
clientEmail: process.env.GOOGLE_CLIENT_EMAIL || "",
Expand Down
7 changes: 4 additions & 3 deletions src/scripts/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { resolve as resolvePath } from "path";
import { Logger } from "../logger";
import { appPort, enableSSL } from "../env";
import { httpsCert, httpsKey } from "../../certs";
import { sSuffix } from "../text";

export function run(): void {
const logger = new Logger("chart");
const logger = new Logger("chart-script");

export function run(): void {
const chartHtml = resolvePath(__dirname, "../chart/index.html");

const app = express();
Expand All @@ -18,7 +19,7 @@ export function run(): void {
res.status(200).sendFile(chartHtml);
});

logger.info(`Starting ${logger.y(`http${enableSSL ? "s" : ""}`)} server`);
logger.info(`Starting ${logger.y(sSuffix("http", enableSSL))} server`);

const httpsOptions = {
cert: httpsCert,
Expand Down
6 changes: 4 additions & 2 deletions src/scripts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ngRokToken,
authorTelegramAccount,
appVersion,
cacheSize,
} from "../env";
import { Logger } from "../logger";
import { VoiceConverterOptions } from "../recognition/types";
Expand All @@ -21,7 +22,7 @@ import { StatisticApi } from "../statistic";
import { TelegramBotModel } from "../telegram/bot";
import { ExpressServer } from "../server/express";

const logger = new Logger("run handler");
const logger = new Logger("dev-script");

export function run(): void {
const server = new ExpressServer(appPort, enableSSL, appVersion);
Expand All @@ -39,7 +40,8 @@ export function run(): void {
dbStat.statUrl,
dbStat.appId,
dbStat.appKey,
dbStat.masterKey
dbStat.masterKey,
cacheSize
);
const bot = new TelegramBotModel(telegramBotApi, converter, stat).setAuthor(
authorTelegramAccount
Expand Down
6 changes: 4 additions & 2 deletions src/scripts/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
authorTelegramAccount,
appVersion,
ngRokToken,
cacheSize,
} from "../env";
import {
getVoiceConverterInstance,
Expand All @@ -23,7 +24,7 @@ import { TelegramBotModel } from "../telegram/bot";
import { ExpressServer } from "../server/express";
import { getHostName } from "../server/tunnel";

const logger = new Logger("run handler");
const logger = new Logger("start-script");

export function run(): void {
const server = new ExpressServer(appPort, enableSSL, appVersion);
Expand All @@ -42,7 +43,8 @@ export function run(): void {
dbStat.statUrl,
dbStat.appId,
dbStat.appKey,
dbStat.masterKey
dbStat.masterKey,
cacheSize
);
const bot = new TelegramBotModel(telegramBotApi, converter, stat).setAuthor(
authorTelegramAccount
Expand Down
18 changes: 9 additions & 9 deletions src/server/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HealthDto, HealthSsl, HealthStatus } from "./types";
import { runGetDto } from "./request";
import { StatisticApi } from "../statistic";
import { sleepForRandom } from "../common/timer";
import { sSuffix } from "../text";

const logger = new Logger("server");

Expand Down Expand Up @@ -84,9 +85,10 @@ export class ExpressServer {
public setBots(bots: TelegramBotModel[] = []): this {
this.bots = bots;
logger.info(
`Requested ${logger.y(bots.length)} bot${
bots.length === 1 ? "" : "s"
} to set up`
`Requested ${logger.y(bots.length)} ${sSuffix(
"bot",
bots.length
)} to set up`
);

bots.forEach((bot) => {
Expand Down Expand Up @@ -119,9 +121,7 @@ export class ExpressServer {
}

public start(): Promise<() => Promise<void>> {
logger.info(
`Starting ${logger.y(`http${this.isHttps ? "s" : ""}`)} server`
);
logger.info(`Starting ${logger.y(sSuffix("http", this.isHttps))} server`);

const server = this.isHttps
? createHttps(this.httpsOptions, this.app)
Expand Down Expand Up @@ -172,9 +172,9 @@ export class ExpressServer {
this.nextReplicaUrl = nextReplicaUrl;
this.daysRunning = [];
logger.info(
`Lifecycle interval is set to ${logger.y(this.lifecycleInterval)} day${
this.lifecycleInterval === 1 ? "" : "s"
}`
`Lifecycle interval is set to ${logger.y(
this.lifecycleInterval
)} ${sSuffix("day", this.lifecycleInterval)}`
);

sleepForRandom()
Expand Down
118 changes: 118 additions & 0 deletions src/statistic/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Logger } from "../logger";
import { sSuffix } from "../text";

const logger = new Logger("cache");

export class CacheProvider<Data, UniqId extends keyof Data> {
private cache: Data[] = [];

constructor(
private readonly cacheSize: number,
private readonly idKey: UniqId
) {
if (!this.hasCacheEnabled()) {
logger.warn(
`Cache size cacheSize=${cacheSize}, so the cache is turned off for ${logger.y(
idKey
)}`
);
} else {
logger.info(
`Cache size is cacheSize=${logger.y(cacheSize)} ${sSuffix(
"item",
cacheSize
)} initialized for ${logger.y(idKey)}`
);
}
}

public addItem(item: Data): void {
if (!this.hasCacheEnabled()) {
return;
}

if (!item[this.idKey]) {
logger.error(
`The item with ${this.idKey}=${
item[this.idKey]
} can not have empty index value. Caching skipped`,
new Error("Cache item can not have empty index value")
);
return;
}

const existingItem = this.cache.find(
(cItem) => cItem[this.idKey] === item[this.idKey]
);
if (existingItem) {
logger.warn(
`The item with ${this.idKey}=${
item[this.idKey]
} is already exists. Removing old data from the cache`
);

this.removeItem(item[this.idKey]);
}

const newCacheData = [...this.cache, item];

if (newCacheData.length > this.cacheSize) {
logger.warn(
`Cache storage exceeds the limit of ${this.cacheSize} ${sSuffix(
"item",
this.cacheSize
)} and have a size of ${
newCacheData.length
}. Old records will be removed to keep storage under the limit`
);
}

this.cache = newCacheData.slice(
Math.max(newCacheData.length - this.cacheSize, 0)
);

logger.info(
`Added cache item with ${this.idKey}=${item[this.idKey]}. Cache size=${
this.cache.length
}`
);
}

public getItem(idValue: Data[UniqId]): Data | null {
if (!this.hasCacheEnabled()) {
return null;
}

const cachedItem = this.cache.find(
(cItem) => cItem[this.idKey] === idValue
);

if (!cachedItem) {
logger.info(
`Did not find the item with ${this.idKey}=${idValue} in cache`
);
return null;
}

logger.info(
`Found the item with ${this.idKey}=${idValue} in cache. Skipping DB request`
);
return cachedItem;
}

public removeItem(idValue: Data[UniqId]): void {
if (!this.hasCacheEnabled()) {
return;
}

this.cache = this.cache.filter((cItem) => cItem[this.idKey] !== idValue);

logger.info(
`Removed cache item for ${this.idKey}=${idValue}. Cache size=${this.cache.length}`
);
}

private hasCacheEnabled(): boolean {
return this.cacheSize > 0;
}
}
11 changes: 9 additions & 2 deletions src/statistic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ export class StatisticApi {
statUrl: string,
appId: string,
appKey: string,
masterKey: string
masterKey: string,
cacheSize: number
) {
this.node = new NodeStatisticApi(statUrl, appId, appKey, masterKey);
this.usage = new UsageStatisticApi(statUrl, appId, appKey, masterKey);
this.usage = new UsageStatisticApi(
statUrl,
appId,
appKey,
masterKey,
cacheSize
);
}
}
20 changes: 8 additions & 12 deletions src/statistic/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import Parse from "parse/node";
import { Logger } from "../logger";
import { NodeStatKey } from "./types";

const logger = new Logger("db");

enum StatKey {
Active = "active",
SelfUrl = "selfUrl",
Version = "version",
}

export class NodeStatisticApi {
private readonly dbClass = "NodeStat";

Expand Down Expand Up @@ -40,8 +35,8 @@ export class NodeStatisticApi {
): Promise<void> {
logger.info(`Updating active state for ${logger.y(instance.id)}`);

instance.set(StatKey.Active, active);
instance.set(StatKey.Version, version);
instance.set(NodeStatKey.Active, active);
instance.set(NodeStatKey.Version, version);
return instance.save().then(() => {
// Empty promise result
});
Expand All @@ -56,9 +51,9 @@ export class NodeStatisticApi {

const NodeStatClass = Parse.Object.extend(this.dbClass);
const instance = new NodeStatClass();
instance.set(StatKey.SelfUrl, selfUrl);
instance.set(StatKey.Active, active);
instance.set(StatKey.Version, version);
instance.set(NodeStatKey.SelfUrl, selfUrl);
instance.set(NodeStatKey.Active, active);
instance.set(NodeStatKey.Version, version);
return instance.save().then((stat: Parse.Object) => stat.id);
}

Expand All @@ -75,7 +70,8 @@ export class NodeStatisticApi {

const NodeStatClass = Parse.Object.extend(this.dbClass);
const query = new Parse.Query(NodeStatClass);
query.equalTo(StatKey.SelfUrl, selfUrl);
query.equalTo(NodeStatKey.SelfUrl, selfUrl);

return query.find().then((results) => {
if (!results.length) {
return Promise.reject(new Error(`Record ${selfUrl} not found`));
Expand Down
19 changes: 19 additions & 0 deletions src/statistic/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LanguageCode } from "../recognition/types";

export enum NodeStatKey {
Active = "active",
SelfUrl = "selfUrl",
Version = "version",
}

export enum UsageStatKey {
UsageCount = "usageCount",
LangId = "langId",
ChatId = "chatId",
UserName = "user",
}

export interface UsageStatCache {
[UsageStatKey.ChatId]: number;
[UsageStatKey.LangId]: LanguageCode;
}
Loading

0 comments on commit bf1f040

Please sign in to comment.