From e27d6ba05e2cfa44e8748b71d804f8dda6b602fb Mon Sep 17 00:00:00 2001 From: akirapham Date: Thu, 11 Apr 2024 17:42:40 +0700 Subject: [PATCH] feat: add setMemoryLimit function and checks for cached topics memory limit --- src/shardus/index.ts | 4 ++ src/state-manager/CachedAppDataManager.ts | 60 +++++++++++++++++------ src/state-manager/state-manager-types.ts | 2 + src/types/Helpers.ts | 2 +- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/shardus/index.ts b/src/shardus/index.ts index 01467a988..2cad65db3 100644 --- a/src/shardus/index.ts +++ b/src/shardus/index.ts @@ -2830,6 +2830,10 @@ class Shardus extends EventEmitter { this.statistics.countEvent(category, name, count, message) } + setMemoryLimit(topic: string, mazSize: number) { + this.stateManager.cachedAppDataManager.setMemoryLimit(topic, mazSize) + } + async getAppDataSignatures( type: string, hash: string, diff --git a/src/state-manager/CachedAppDataManager.ts b/src/state-manager/CachedAppDataManager.ts index 4552dcffa..ce527eeb1 100644 --- a/src/state-manager/CachedAppDataManager.ts +++ b/src/state-manager/CachedAppDataManager.ts @@ -11,7 +11,12 @@ import { InternalRouteEnum } from '../types/enum/InternalRouteEnum' import { RequestErrorEnum } from '../types/enum/RequestErrorEnum' import { TypeIdentifierEnum } from '../types/enum/TypeIdentifierEnum' import { InternalBinaryHandler } from '../types/Handler' -import { getStreamWithTypeCheck, requestErrorHandler, verificationDataCombiner } from '../types/Helpers' +import { + estimateBinarySizeOfObject, + getStreamWithTypeCheck, + requestErrorHandler, + verificationDataCombiner, +} from '../types/Helpers' import { deserializeSendCachedAppDataReq, SendCachedAppDataReq, @@ -165,15 +170,17 @@ class CachedAppDataManager { const { topic, dataId } = payload const foundCachedAppData = this.getCachedItem(topic, dataId) if (foundCachedAppData == null) { - this.mainLogger.error(`cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}`) - /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}`) + this.mainLogger.error( + `cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}` + ) + /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Cannot find cached data for topic: ${topic}, dataId: ${dataId}`) } await respond(foundCachedAppData) profilerInstance.scopedProfileSectionEnd('get_cached_app_data') return } catch (e) { - this.mainLogger.error(`cachedAppData: Error while processing get_cachedAppData`, e) - /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Error while processing get_cachedAppData`, e) + this.mainLogger.error(`cachedAppData: Error while processing get_cachedAppData`, e) + /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: Error while processing get_cachedAppData`, e) } finally { profilerInstance.scopedProfileSectionEnd('get_cached_app_data') } @@ -227,6 +234,9 @@ class CachedAppDataManager { maxCacheElements, cacheAppDataMap: new Map(), cachedAppDataArray: [], + currentCacheSize: 0, + // default cache limit + cacheSizeLimit: Number.MAX_VALUE, } if (this.cacheTopicMap.has(topic)) return false this.cacheTopicMap.set(topic, cacheTopic) @@ -247,6 +257,7 @@ class CachedAppDataManager { let count = 0 const { maxCycleAge, maxCacheElements } = cacheTopic const prunedCachedAppDataArray = [] + let newCacheSizeAfterPrune = 0 for (const cachedAppData of reversed(cacheTopic.cachedAppDataArray)) { count += 1 const cycleAge = this.stateManager.currentCycleShardData.cycleNumber - cachedAppData.cycle @@ -258,9 +269,11 @@ class CachedAppDataManager { cacheTopic.cacheAppDataMap.delete(cachedAppData.dataID) } else { prunedCachedAppDataArray.push(cachedAppData) + newCacheSizeAfterPrune += estimateBinarySizeOfObject(cachedAppData) } } cacheTopic.cachedAppDataArray = prunedCachedAppDataArray.reverse() + cacheTopic.currentCacheSize = newCacheSizeAfterPrune /* prettier-ignore */ if (logFlags.verbose) this.mainLogger.debug( `cachedAppData: Updated cached array size: ${cacheTopic.cachedAppDataArray.length}, cacheMapSize: ${cacheTopic.cacheAppDataMap.size}` ) /* prettier-ignore */ if(logFlags.shardedCache) console.log(( `cachedAppData: Updated cached array size: ${cacheTopic.cachedAppDataArray.length}, cacheMapSize: ${cacheTopic.cacheAppDataMap.size}` )) } @@ -275,17 +288,35 @@ class CachedAppDataManager { const cacheTopic: CacheTopic = this.cacheTopicMap.get(topic) if (!cacheTopic) { // not safe to log such a large object in prod. commented out: - /* prettier-ignore */ if(logFlags.shardedCache) this.statemanager_fatal( 'insertCachedItem', `Topic ${topic} is not registered yet.`)// ${JSON.stringify(this.cacheTopicMap)}` ) + /* prettier-ignore */ if(logFlags.shardedCache) this.statemanager_fatal( 'insertCachedItem', `Topic ${topic} is not registered yet.`) // ${JSON.stringify(this.cacheTopicMap)}` ) return } - if (!cacheTopic.cacheAppDataMap.has(dataID)) { + if (!cacheTopic.cacheAppDataMap.has(dataID)) { + const dataSize = estimateBinarySizeOfObject(cachedAppData) + // cache size per topic is at max by default + if (cacheTopic.currentCacheSize + dataSize > cacheTopic.cacheSizeLimit) { + /* prettier-ignore */ if(logFlags.shardedCache) this.statemanager_fatal( 'insertCachedItem', `Topic ${topic} is at max memory limit`) + return + } /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: insert cache app data`, dataID, Date.now()) cacheTopic.cacheAppDataMap.set(dataID, cachedAppData) cacheTopic.cachedAppDataArray.push(cachedAppData) + cacheTopic.currentCacheSize += dataSize } } + setMemoryLimit(topic: string, sizeLimit: number) { + const cacheTopic: CacheTopic = this.cacheTopicMap.get(topic) + if (!cacheTopic) { + // not safe to log such a large object in prod. commented out: + /* prettier-ignore */ if(logFlags.shardedCache) this.statemanager_fatal( 'setMemoryLimit', `Topic ${topic} is not registered yet.`) // ${JSON.stringify(this.cacheTopicMap)}` ) + return + } + + cacheTopic.cacheSizeLimit = sizeLimit + } + async sendCorrespondingCachedAppData( topic: string, dataID: string, @@ -347,7 +378,6 @@ class CachedAppDataManager { /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: sendCorrespondingCachedAppData hasKey=false: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull.map((v) => v.id))}`); /* prettier-ignore */ if(logFlags.shardedCache) console.log(`sendCorrespondingCachedAppData hasKey=false: full: ${utils.stringifyReduce(remoteHomeNode.nodeThatStoreOurParitionFull)}`); - } /* prettier-ignore */ if (logFlags.verbose) this.mainLogger.debug(`cachedAppData: sendCorrespondingCachedAppData hasKey=false key: ${utils.stringifyReduce(key)}`); /* prettier-ignore */ if(logFlags.shardedCache) console.log(`cachedAppData: sendCorrespondingCachedAppData hasKey=false key: ${utils.stringifyReduce(key)}`); @@ -533,7 +563,7 @@ class CachedAppDataManager { const validNodeRetries = 5 let randomConsensusNode = null - for(let i=0; i cachedAppDataArray: CachedAppData[] + currentCacheSize: number + cacheSizeLimit: number } export type CacheAppDataResponse = { diff --git a/src/types/Helpers.ts b/src/types/Helpers.ts index 6a9d05dd3..7cb8905ee 100644 --- a/src/types/Helpers.ts +++ b/src/types/Helpers.ts @@ -97,7 +97,7 @@ export const requestErrorHandler = ( nestedCountersInstance.countEvent('internal', counter) } -function estimateBinarySizeOfObject(obj): number { +export function estimateBinarySizeOfObject(obj): number { const sizes = { number: 8, // assuming a number is serialized as a double (8 bytes) boolean: 1,