Skip to content

Commit 6a19561

Browse files
committed
perf: refactor CacheEntryOptions into simple object
1 parent 4234fdb commit 6a19561

File tree

7 files changed

+171
-194
lines changed

7 files changed

+171
-194
lines changed

.changeset/eleven-hats-retire.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'bentocache': patch
3+
---
4+
5+
Refactoring of CacheEntryOptions class. We switch to a simple function that returns an object rather than a class. Given that CacheEntryOptions is heavily used : it was instantiated for every cache operation, we gain a lot in performance.

packages/bentocache/src/cache/cache_entry/cache_entry_options.ts

+137-161
Original file line numberDiff line numberDiff line change
@@ -3,189 +3,165 @@ import { is } from '@julr/utils/is'
33

44
import { errors } from '../../errors.js'
55
import { resolveTtl } from '../../helpers.js'
6-
import type { FactoryError } from '../../errors.js'
76
import type { Duration, RawCommonOptions } from '../../types/main.js'
87

98
const toId = hexoid(12)
109

11-
export class CacheEntryOptions {
12-
/**
13-
* The options that were passed to the constructor
14-
*/
15-
#options: RawCommonOptions
16-
17-
/**
18-
* Unique identifier that will be used when logging
19-
* debug information.
20-
*/
21-
id: string
22-
23-
/**
24-
* Logical TTL is when the value is considered expired
25-
* but still can be in the cache ( Grace period )
26-
*/
27-
logicalTtl?: number
28-
29-
/**
30-
* Physical TTL is the time when value will be automatically
31-
* removed from the cache. This is the Grace period
32-
* duration
33-
*/
34-
physicalTtl?: number
35-
36-
/**
37-
* Timeouts for the cache operations
38-
*/
39-
timeout?: number
40-
hardTimeout?: number
41-
42-
/**
43-
* Resolved grace period options
44-
*/
45-
grace: number
46-
graceBackoff: number
47-
48-
/**
49-
* Max time to wait for the lock to be acquired
50-
*/
51-
lockTimeout?: number
52-
onFactoryError?: (error: FactoryError) => void
53-
54-
constructor(options: RawCommonOptions = {}, defaults: Partial<RawCommonOptions> = {}) {
55-
this.id = toId()
56-
57-
this.#options = { ...defaults, ...options }
58-
59-
this.grace = this.#resolveGrace()
60-
this.graceBackoff = resolveTtl(this.#options.graceBackoff, null) ?? 0
61-
this.logicalTtl = this.#resolveLogicalTtl()
62-
this.physicalTtl = this.#resolvePhysicalTtl()
63-
this.timeout = resolveTtl(this.#options.timeout, null)
64-
this.hardTimeout = resolveTtl(this.#options.hardTimeout, null)
65-
this.lockTimeout = resolveTtl(this.#options.lockTimeout, null)
66-
this.onFactoryError = this.#options.onFactoryError ?? defaults.onFactoryError
67-
}
10+
export type CacheEntryOptions = ReturnType<typeof createCacheEntryOptions>
6811

69-
/**
70-
* Resolve the grace period options
71-
*/
72-
#resolveGrace() {
73-
if (this.#options.grace === false) return 0
74-
return resolveTtl(this.#options.grace, null) ?? 0
75-
}
12+
/**
13+
* Resolve the grace options
14+
*/
15+
function resolveGrace(options: RawCommonOptions) {
16+
if (options.grace === false) return 0
7617

77-
/**
78-
* Returns a new instance of `CacheItemOptions` with the same
79-
* options as the current instance, but with any provided
80-
* options overriding the current
81-
*
82-
* For performance reasons, if no options are provided, the
83-
* current instance is returned
84-
*/
85-
cloneWith(options?: Partial<RawCommonOptions>) {
86-
return options ? new CacheEntryOptions(options, this.#options) : this
87-
}
18+
return resolveTtl(options.grace, null) ?? 0
19+
}
8820

89-
/**
90-
* Resolve the logical TTL to a duration in milliseconds
91-
*/
92-
#resolveLogicalTtl() {
93-
return resolveTtl(this.#options.ttl)
94-
}
21+
/**
22+
* Cache Entry Options. Define how a cache operation should behave
23+
*
24+
* Yes, this is a fake class. Initially, this was a class, but
25+
* since CacheEntryOptions is initialized each time a cache
26+
* operation is performed, it was converted to this
27+
* fake class to have way better performance.
28+
*/
29+
export function createCacheEntryOptions(
30+
newOptions: RawCommonOptions = {},
31+
defaults: Partial<RawCommonOptions> = {},
32+
) {
33+
const options = { ...defaults, ...newOptions }
34+
35+
const grace = resolveGrace(options)
36+
const graceBackoff = resolveTtl(options.graceBackoff, null) ?? 0
37+
38+
let logicalTtl = resolveTtl(options.ttl)
39+
let physicalTtl = grace > 0 ? grace : logicalTtl
40+
41+
const timeout = resolveTtl(options.timeout, null)
42+
const hardTimeout = resolveTtl(options.hardTimeout, null)
43+
const lockTimeout = resolveTtl(options.lockTimeout, null)
44+
45+
const self = {
46+
/**
47+
* Unique identifier that will be used when logging
48+
* debug information.
49+
*/
50+
id: toId(),
9551

96-
/**
97-
* Resolve the physical TTL to a duration in milliseconds
98-
*
99-
* If grace period is not enabled then the physical TTL
100-
* is the same as the logical TTL
101-
*/
102-
#resolvePhysicalTtl() {
103-
return this.isGraceEnabled ? this.grace : this.logicalTtl
104-
}
52+
/**
53+
* Resolved grace period options
54+
*/
55+
grace,
56+
graceBackoff,
10557

106-
get isGraceEnabled() {
107-
return this.grace > 0
108-
}
58+
/**
59+
* Logical TTL is when the value is considered expired
60+
* but still can be in the cache ( Grace period )
61+
*/
62+
logicalTtl,
10963

110-
get suppressL2Errors() {
111-
return this.#options.suppressL2Errors
112-
}
64+
/**
65+
* Physical TTL is the time when value will be automatically
66+
* removed from the cache. This is the Grace period
67+
* duration
68+
*/
69+
physicalTtl,
11370

114-
/**
115-
* Set a new logical TTL
116-
*/
117-
setLogicalTtl(ttl: Duration) {
118-
this.#options.ttl = ttl
71+
/**
72+
* Timeouts for the cache operations
73+
*/
74+
timeout,
75+
hardTimeout,
11976

120-
this.logicalTtl = this.#resolveLogicalTtl()
121-
this.physicalTtl = this.#resolvePhysicalTtl()
77+
/**
78+
* Max time to wait for the lock to be acquired
79+
*/
80+
lockTimeout,
81+
onFactoryError: options.onFactoryError ?? defaults.onFactoryError,
82+
isGraceEnabled: grace > 0,
83+
suppressL2Errors: options.suppressL2Errors,
12284

123-
return this
124-
}
85+
/**
86+
* Returns a new instance of `CacheItemOptions` with the same
87+
* options as the current instance, but with any provided
88+
* options overriding the current
89+
*
90+
* For performance reasons, if no options are provided, the
91+
* current instance is returned
92+
*/
93+
cloneWith(newOptions?: Partial<RawCommonOptions>) {
94+
return newOptions ? createCacheEntryOptions(newOptions, options) : self
95+
},
12596

126-
/**
127-
* Compute the logical TTL timestamp from now
128-
*/
129-
logicalTtlFromNow() {
130-
if (!this.logicalTtl) return undefined
131-
return Date.now() + this.logicalTtl
132-
}
97+
/**
98+
* Set a new logical TTL
99+
*/
100+
setLogicalTtl(newTtl: Duration) {
101+
options.ttl = newTtl
133102

134-
/**
135-
* Compute the physical TTL timestamp from now
136-
*/
137-
physicalTtlFromNow() {
138-
if (!this.physicalTtl) return undefined
139-
return Date.now() + this.physicalTtl
140-
}
103+
logicalTtl = resolveTtl(options.ttl)
104+
physicalTtl = self.isGraceEnabled ? grace : logicalTtl
141105

142-
/**
143-
* Compute the lock timeout we should use for the
144-
* factory
145-
*/
146-
factoryTimeout(hasFallbackValue: boolean) {
147-
if (hasFallbackValue && this.isGraceEnabled && is.number(this.timeout)) {
148-
return {
149-
type: 'soft',
150-
duration: this.timeout,
151-
exception: errors.E_FACTORY_SOFT_TIMEOUT,
152-
}
153-
}
106+
return self
107+
},
154108

155-
if (this.hardTimeout) {
156-
return {
157-
type: 'hard',
158-
duration: this.hardTimeout,
159-
exception: errors.E_FACTORY_HARD_TIMEOUT,
160-
}
161-
}
109+
/**
110+
* Compute the logical TTL timestamp from now
111+
*/
112+
logicalTtlFromNow() {
113+
if (!logicalTtl) return
162114

163-
return
164-
}
115+
return Date.now() + logicalTtl
116+
},
165117

166-
/**
167-
* Determine if we should use the SWR strategy
168-
*/
169-
shouldSwr(hasFallback: boolean) {
170-
return this.isGraceEnabled && this.timeout === 0 && hasFallback
171-
}
118+
/**
119+
* Compute the physical TTL timestamp from now
120+
*/
121+
physicalTtlFromNow() {
122+
if (!physicalTtl) return
172123

173-
/**
174-
* Compute the maximum time we should wait for the
175-
* lock to be acquired
176-
*/
177-
getApplicableLockTimeout(hasFallbackValue: boolean) {
178-
if (this.lockTimeout) {
179-
return this.lockTimeout
180-
}
124+
return Date.now() + physicalTtl
125+
},
181126

182127
/**
183-
* If we have a fallback value and grace period is enabled,
184-
* that means we should wait at most for the soft timeout
185-
* duration.
128+
* Compute the lock timeout we should use for the
129+
* factory
186130
*/
187-
if (hasFallbackValue && this.isGraceEnabled && typeof this.timeout === 'number') {
188-
return this.timeout
189-
}
131+
factoryTimeout(hasFallbackValue: boolean) {
132+
if (hasFallbackValue && self.isGraceEnabled && is.number(timeout)) {
133+
return { type: 'soft', duration: timeout, exception: errors.E_FACTORY_SOFT_TIMEOUT }
134+
}
135+
136+
if (hardTimeout) {
137+
return { type: 'hard', duration: hardTimeout, exception: errors.E_FACTORY_HARD_TIMEOUT }
138+
}
139+
},
140+
141+
/**
142+
* Determine if we should use the SWR strategy
143+
*/
144+
shouldSwr(hasFallback: boolean) {
145+
return self.isGraceEnabled && timeout === 0 && hasFallback
146+
},
147+
148+
/**
149+
* Compute the maximum time we should wait for the
150+
* lock to be acquired
151+
*/
152+
getApplicableLockTimeout(hasFallbackValue: boolean) {
153+
if (lockTimeout) return lockTimeout
154+
155+
/**
156+
* If we have a fallback value and grace period is enabled,
157+
* that means we should wait at most for the soft timeout
158+
* duration.
159+
*/
160+
if (hasFallbackValue && self.isGraceEnabled && typeof timeout === 'number') {
161+
return timeout
162+
}
163+
},
190164
}
165+
166+
return self
191167
}

packages/bentocache/src/cache/stack/cache_stack.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { RemoteCache } from '../facades/remote_cache.js'
88
import { BaseDriver } from '../../drivers/base_driver.js'
99
import { cacheEvents } from '../../events/cache_events.js'
1010
import type { BentoCacheOptions } from '../../bento_cache_options.js'
11-
import { CacheEntryOptions } from '../cache_entry/cache_entry_options.js'
11+
import { createCacheEntryOptions } from '../cache_entry/cache_entry_options.js'
1212
import {
1313
type BusDriver,
1414
type BusOptions,
@@ -23,7 +23,7 @@ export class CacheStack extends BaseDriver {
2323
l1?: LocalCache
2424
l2?: RemoteCache
2525
bus?: Bus
26-
defaultOptions: CacheEntryOptions
26+
defaultOptions: ReturnType<typeof createCacheEntryOptions>
2727
logger: Logger
2828
#busDriver?: BusDriver
2929
#busOptions?: BusOptions
@@ -50,7 +50,7 @@ export class CacheStack extends BaseDriver {
5050
this.bus = bus ? bus : this.#createBus(drivers.busDriver, drivers.busOptions)
5151
if (this.l1) this.bus?.manageCache(this.prefix, this.l1)
5252

53-
this.defaultOptions = new CacheEntryOptions(options)
53+
this.defaultOptions = createCacheEntryOptions(this.options)
5454
}
5555

5656
get emitter() {
@@ -110,7 +110,7 @@ export class CacheStack extends BaseDriver {
110110
* - Publish a message to the bus
111111
* - Emit a CacheWritten event
112112
*/
113-
async set(key: string, value: any, options: CacheEntryOptions) {
113+
async set(key: string, value: any, options: ReturnType<typeof createCacheEntryOptions>) {
114114
if (is.undefined(value)) throw new UndefinedValueError(key)
115115

116116
const rawItem = {

0 commit comments

Comments
 (0)