@@ -7,13 +7,16 @@ import { AppComponents } from '../types'
77export type IDeployRateLimiterComponent = {
88 newDeployment ( entityType : EntityType , pointers : string [ ] , localTimestamp : number ) : void
99 isRateLimited ( entityType : EntityType , pointers : string [ ] ) : boolean
10+ newUnchangedDeployment ( entityType : EntityType , pointers : string [ ] , localTimestamp : number ) : void
11+ isUnchangedDeploymentRateLimited ( entityType : EntityType , pointers : string [ ] ) : boolean
1012}
1113
1214export type DeploymentRateLimitConfig = {
1315 defaultTtl : number
1416 defaultMax : number
1517 entitiesConfigTtl : Map < EntityType , number >
1618 entitiesConfigMax : Map < EntityType , number >
19+ entitiesConfigUnchangedTtl : Map < EntityType , number >
1720}
1821
1922export function createDeployRateLimiter (
@@ -27,6 +30,11 @@ export function createDeployRateLimiter(
2730 rateLimitConfig
2831 )
2932
33+ const unchangedDeploymentCacheMap : Map < EntityType , NodeCache > = generateUnchangedDeploymentCacheMap (
34+ logs ,
35+ rateLimitConfig
36+ )
37+
3038 function getCacheFromEntityType ( entityType : EntityType ) : { cache : NodeCache ; maxSize : number } {
3139 const cache = deploymentCacheMap . get ( entityType )
3240 if ( ! cache ) {
@@ -35,10 +43,18 @@ export function createDeployRateLimiter(
3543 return cache
3644 }
3745
46+ function getUnchangedCacheFromEntityType ( entityType : EntityType ) : NodeCache {
47+ const cache = unchangedDeploymentCacheMap . get ( entityType )
48+ if ( ! cache ) {
49+ throw new Error ( `Invalid Entity Type: ${ entityType } ` )
50+ }
51+ return cache
52+ }
53+
3854 return {
3955 newDeployment ( entityType : EntityType , pointers : string [ ] , localTimestamp : number ) : void {
4056 const cacheByEntityType = getCacheFromEntityType ( entityType )
41- for ( const pointer in pointers ) {
57+ for ( const pointer of pointers ) {
4258 cacheByEntityType . cache . set ( pointer , localTimestamp )
4359 }
4460 } ,
@@ -51,10 +67,46 @@ export function createDeployRateLimiter(
5167 pointers . some ( ( p ) => ! ! cacheByEntityType . cache . get ( p ) ) ||
5268 cacheByEntityType . cache . stats . keys > cacheByEntityType . maxSize
5369 )
70+ } ,
71+
72+ newUnchangedDeployment ( entityType : EntityType , pointers : string [ ] , localTimestamp : number ) : void {
73+ const cache = getUnchangedCacheFromEntityType ( entityType )
74+ for ( const pointer of pointers ) {
75+ cache . set ( pointer , localTimestamp )
76+ }
77+ } ,
78+
79+ isUnchangedDeploymentRateLimited ( entityType : EntityType , pointers : string [ ] ) : boolean {
80+ const cache = getUnchangedCacheFromEntityType ( entityType )
81+ return pointers . some ( ( p ) => ! ! cache . get ( p ) )
5482 }
5583 }
5684}
5785
86+ function generateUnchangedDeploymentCacheMap (
87+ logs : ILoggerComponent . ILogger ,
88+ rateLimitConfig : DeploymentRateLimitConfig
89+ ) : Map < EntityType , NodeCache > {
90+ const unchangedCacheMap : Map < EntityType , NodeCache > = new Map ( )
91+
92+ for ( const entityType of Object . values ( EntityType ) ) {
93+ const ttl : number = toSeconds ( rateLimitConfig . entitiesConfigUnchangedTtl . get ( entityType ) ?? 0 )
94+ unchangedCacheMap . set ( entityType , new NodeCache ( { stdTTL : ttl , checkperiod : ttl } ) )
95+ }
96+
97+ const configEntries : string [ ] = [ ]
98+ for ( const [ entityType , cache ] of unchangedCacheMap ) {
99+ if ( cache . options . stdTTL && cache . options . stdTTL > 0 ) {
100+ configEntries . push ( `${ entityType } : { unchanged_ttl: ${ cache . options . stdTTL } }` )
101+ }
102+ }
103+ if ( configEntries . length > 0 ) {
104+ logs . info ( `Unchanged deployment rate limit configured for:\n${ configEntries . join ( '\n' ) } ` )
105+ }
106+
107+ return unchangedCacheMap
108+ }
109+
58110function generateDeploymentCacheMap (
59111 logs : ILoggerComponent . ILogger ,
60112 rateLimitConfig : DeploymentRateLimitConfig
@@ -97,6 +149,13 @@ function toString(deploymentCacheMap: Map<EntityType, { cache: NodeCache; maxSiz
97149 return stringifyMap . join ( '\n' )
98150}
99151
152+ /**
153+ * Convert milliseconds to seconds for NodeCache stdTTL which expects seconds.
154+ */
155+ function toSeconds ( milliseconds : number ) : number {
156+ return Math . floor ( milliseconds / 1000 )
157+ }
158+
100159function getCacheConfigPerEntityMap (
101160 entitiesConfigMax : Map < EntityType , number > ,
102161 entitiesConfigTtl : Map < EntityType , number >
@@ -106,28 +165,28 @@ function getCacheConfigPerEntityMap(
106165 EntityType . PROFILE ,
107166 {
108167 max : entitiesConfigMax . get ( EntityType . PROFILE ) ?? 300 ,
109- ttl : entitiesConfigTtl . get ( EntityType . PROFILE ) ?? ms ( '1m' )
168+ ttl : toSeconds ( entitiesConfigTtl . get ( EntityType . PROFILE ) ?? ms ( '15s' ) )
110169 }
111170 ] ,
112171 [
113172 EntityType . SCENE ,
114173 {
115174 max : entitiesConfigMax . get ( EntityType . SCENE ) ?? 100000 ,
116- ttl : entitiesConfigTtl . get ( EntityType . SCENE ) ?? ms ( '20s' )
175+ ttl : toSeconds ( entitiesConfigTtl . get ( EntityType . SCENE ) ?? ms ( '20s' ) )
117176 }
118177 ] ,
119178 [
120179 EntityType . WEARABLE ,
121180 {
122181 max : entitiesConfigMax . get ( EntityType . WEARABLE ) ?? 100000 ,
123- ttl : entitiesConfigTtl . get ( EntityType . WEARABLE ) ?? ms ( '20s' )
182+ ttl : toSeconds ( entitiesConfigTtl . get ( EntityType . WEARABLE ) ?? ms ( '20s' ) )
124183 }
125184 ] ,
126185 [
127186 EntityType . STORE ,
128187 {
129188 max : entitiesConfigMax . get ( EntityType . STORE ) ?? 300 ,
130- ttl : entitiesConfigTtl . get ( EntityType . STORE ) ?? ms ( '1m' )
189+ ttl : toSeconds ( entitiesConfigTtl . get ( EntityType . STORE ) ?? ms ( '1m' ) )
131190 }
132191 ]
133192 ] )
0 commit comments