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 21, 2020
1 parent 4bf74cd commit aca8a18
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 9 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
4 changes: 3 additions & 1 deletion 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 Down Expand Up @@ -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
4 changes: 3 additions & 1 deletion 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 Down Expand Up @@ -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
116 changes: 116 additions & 0 deletions src/statistic/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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 is ${logger.y(
sSuffix("item", cacheSize)
)}, so the cache is turned off for ${logger.y(idKey)}`
);
} else {
logger.info(
`Cache size is ${logger.y(
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 ${logger.y(
sSuffix("item", this.cacheSize)
)} and have a size of ${logger.y(
sSuffix("item", 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
);
}
}
7 changes: 7 additions & 0 deletions src/statistic/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LanguageCode } from "../recognition/types";

export enum NodeStatKey {
Active = "active",
SelfUrl = "selfUrl",
Expand All @@ -11,3 +13,8 @@ export enum UsageStatKey {
UserName = "user",
CreatedAt = "createdAt",
}

export interface UsageStatCache {
[UsageStatKey.ChatId]: number;
[UsageStatKey.LangId]: LanguageCode;
}
36 changes: 31 additions & 5 deletions src/statistic/usage.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import Parse from "parse/node";
import { LanguageCode } from "../recognition/types";
import { Logger } from "../logger";
import { UsageStatKey } from "./types";
import { UsageStatCache, UsageStatKey } from "./types";
import { CacheProvider } from "./cache";

const logger = new Logger("db");

export class UsageStatisticApi {
private readonly dbClass = "BotStat";
private cache: CacheProvider<UsageStatCache, UsageStatKey.ChatId>;

constructor(
statUrl: string,
appId: string,
appKey: string,
masterKey: string
masterKey: string,
cacheSize: number
) {
Parse.serverURL = statUrl;
Parse.initialize(appId, appKey, masterKey);
this.cache = new CacheProvider<UsageStatCache, UsageStatKey.ChatId>(
cacheSize,
UsageStatKey.ChatId
);
}

public updateUsageCount(chatId: number, username: string): Promise<void> {
Expand All @@ -25,15 +32,34 @@ export class UsageStatisticApi {
}

public updateLanguage(chatId: number, lang: LanguageCode): Promise<void> {
this.cache.removeItem(chatId);

return this.getStatIfNotExists(chatId).then((stat) =>
this.updateStatLanguage(stat, lang)
);
}

public getLanguage(chatId: number, username: string): Promise<LanguageCode> {
return this.getStatIfNotExists(chatId, username).then((stat) =>
stat.get(UsageStatKey.LangId)
);
const cachedItem = this.cache.getItem(chatId);
if (cachedItem) {
return Promise.resolve(cachedItem[UsageStatKey.LangId]);
}

return this.getStatIfNotExists(chatId, username).then((stat) => {
this.addCacheItem(stat);
return stat.get(UsageStatKey.LangId);
});
}

private addCacheItem(instance: Parse.Object): void {
const langId = instance.get(UsageStatKey.LangId);
const chatId = instance.get(UsageStatKey.ChatId);
const cachedItem: UsageStatCache = {
[UsageStatKey.LangId]: langId,
[UsageStatKey.ChatId]: chatId,
};

this.cache.addItem(cachedItem);
}

private getStatIfNotExists(
Expand Down

0 comments on commit aca8a18

Please sign in to comment.