From f3760476324c25ddab0e4b7924b2ed4118c3e629 Mon Sep 17 00:00:00 2001 From: jaison-x Date: Wed, 3 Jan 2024 18:42:54 -0300 Subject: [PATCH] perf(chatwoot): create cache for the most used/expensive functions in chatwoot --- .../controllers/instance.controller.ts | 2 + src/whatsapp/services/cache.service.ts | 39 ++++++++++++++ src/whatsapp/services/chatwoot.service.ts | 52 ++++++++++++++++++- src/whatsapp/services/monitor.service.ts | 1 + src/whatsapp/services/whatsapp.service.ts | 10 ++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/whatsapp/services/cache.service.ts diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 0860f972..3c125efc 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -548,6 +548,7 @@ export class InstanceController { switch (state) { case 'open': this.logger.verbose('logging out instance: ' + instanceName); + instance.clearCacheChatwoot(); await instance.reloadConnection(); await delay(2000); @@ -613,6 +614,7 @@ export class InstanceController { } try { this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues(); + this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot(); if (instance.state === 'connecting') { this.logger.verbose('logging out instance: ' + instanceName); diff --git a/src/whatsapp/services/cache.service.ts b/src/whatsapp/services/cache.service.ts new file mode 100644 index 00000000..8a77b79b --- /dev/null +++ b/src/whatsapp/services/cache.service.ts @@ -0,0 +1,39 @@ +import NodeCache from 'node-cache'; + +import { Logger } from '../../config/logger.config'; + +export class CacheService { + private readonly logger = new Logger(CacheService.name); + + constructor(private module: string) {} + + static localCache = new NodeCache({ + stdTTL: 12 * 60 * 60, + }); + + public get(key: string) { + return CacheService.localCache.get(`${this.module}-${key}`); + } + + public set(key: string, value) { + return CacheService.localCache.set(`${this.module}-${key}`, value); + } + + public has(key: string) { + return CacheService.localCache.has(`${this.module}-${key}`); + } + + public delete(key: string) { + return CacheService.localCache.del(`${this.module}-${key}`); + } + + public deleteAll() { + const keys = CacheService.localCache.keys().filter((key) => key.substring(0, this.module.length) === this.module); + + return CacheService.localCache.del(keys); + } + + public keys() { + return CacheService.localCache.keys(); + } +} diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index a2f1c3ee..b636452a 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1,4 +1,4 @@ -import ChatwootClient from '@figuro/chatwoot-sdk'; +import ChatwootClient, { conversation, inbox } from '@figuro/chatwoot-sdk'; import axios from 'axios'; import FormData from 'form-data'; import { createReadStream, unlinkSync, writeFileSync } from 'fs'; @@ -11,15 +11,17 @@ import { Logger } from '../../config/logger.config'; import { ChatwootDto } from '../dto/chatwoot.dto'; import { InstanceDto } from '../dto/instance.dto'; import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto'; -import { MessageRaw } from '../models'; +import { ChatwootRaw, MessageRaw } from '../models'; import { RepositoryBroker } from '../repository/repository.manager'; import { Events } from '../types/wa.types'; +import { CacheService } from './cache.service'; import { WAMonitoringService } from './monitor.service'; export class ChatwootService { private readonly logger = new Logger(ChatwootService.name); private provider: any; + private cache = new CacheService(ChatwootService.name); constructor( private readonly waMonitor: WAMonitoringService, @@ -28,6 +30,11 @@ export class ChatwootService { ) {} private async getProvider(instance: InstanceDto) { + const cacheKey = `getProvider-${instance.instanceName}`; + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey) as ChatwootRaw; + } + this.logger.verbose('get provider to instance: ' + instance.instanceName); const provider = await this.waMonitor.waInstances[instance.instanceName]?.findChatwoot(); @@ -38,6 +45,8 @@ export class ChatwootService { this.logger.verbose('provider found'); + this.cache.set(cacheKey, provider); + return provider; // try { // } catch (error) { @@ -60,6 +69,11 @@ export class ChatwootService { this.provider = provider; + const cacheKey = `clientCw-${instance.instanceName}`; + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey) as ChatwootClient; + } + this.logger.verbose('create client to instance: ' + instance.instanceName); const client = new ChatwootClient({ config: { @@ -72,9 +86,15 @@ export class ChatwootService { this.logger.verbose('client created'); + this.cache.set(cacheKey, client); + return client; } + public getCache() { + return this.cache; + } + public async create(instance: InstanceDto, data: ChatwootDto) { this.logger.verbose('create chatwoot: ' + instance.instanceName); @@ -389,6 +409,26 @@ export class ChatwootService { return null; } + const cacheKey = `createConversation-${instance.instanceName}-${body.key.remoteJid}`; + if (this.cache.has(cacheKey)) { + const conversationId = this.cache.get(cacheKey) as number; + let conversationExists: conversation | boolean; + try { + conversationExists = await client.conversations.get({ + accountId: this.provider.account_id, + conversationId: conversationId, + }); + } catch (error) { + conversationExists = false; + } + if (!conversationExists) { + this.cache.delete(cacheKey); + return await this.createConversation(instance, body); + } + + return conversationId; + } + const isGroup = body.key.remoteJid.includes('@g.us'); this.logger.verbose('is group: ' + isGroup); @@ -539,6 +579,7 @@ export class ChatwootService { if (conversation) { this.logger.verbose('conversation found'); + this.cache.set(cacheKey, conversation.id); return conversation.id; } } @@ -564,6 +605,7 @@ export class ChatwootService { } this.logger.verbose('conversation created'); + this.cache.set(cacheKey, conversation.id); return conversation.id; } catch (error) { this.logger.error(error); @@ -573,6 +615,11 @@ export class ChatwootService { public async getInbox(instance: InstanceDto) { this.logger.verbose('get inbox to instance: ' + instance.instanceName); + const cacheKey = `getInbox-${instance.instanceName}`; + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey) as inbox; + } + const client = await this.clientCw(instance); if (!client) { @@ -599,6 +646,7 @@ export class ChatwootService { } this.logger.verbose('return inbox'); + this.cache.set(cacheKey, findByName); return findByName; } diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 0191079c..ecf70586 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -442,6 +442,7 @@ export class WAMonitoringService { this.eventEmitter.on('logout.instance', async (instanceName: string) => { this.logger.verbose('logout instance: ' + instanceName); try { + this.waInstances[instanceName]?.clearCacheChatwoot(); this.logger.verbose('request cleaning up instance: ' + instanceName); this.cleaningUp(instanceName); } finally { diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 227167f7..1d8d796e 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -368,6 +368,8 @@ export class WAStartupService { Object.assign(this.localChatwoot, { ...data, sign_delimiter: data.sign_msg ? data.sign_delimiter : null }); + this.clearCacheChatwoot(); + this.logger.verbose('Chatwoot set'); } @@ -402,6 +404,14 @@ export class WAStartupService { }; } + public clearCacheChatwoot() { + this.logger.verbose('Removing cache from chatwoot'); + + if (this.localChatwoot.enabled) { + this.chatwootService.getCache().deleteAll(); + } + } + private async loadSettings() { this.logger.verbose('Loading settings'); const data = await this.repository.settings.find(this.instanceName);