layercache 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +42 -41
  2. package/dist/{chunk-BORDQ3LA.js → chunk-7KMKQ6QZ.js} +15 -1
  3. package/dist/{chunk-5RCAX2BQ.js → chunk-FFZCC7EQ.js} +3 -3
  4. package/dist/{chunk-4PPBOOXT.js → chunk-KJDFYE5T.js} +38 -26
  5. package/dist/cli.cjs +9 -9
  6. package/dist/cli.js +4 -4
  7. package/dist/{edge-CUHTP9Bc.d.cts → edge-D2FpRlyS.d.cts} +74 -36
  8. package/dist/{edge-CUHTP9Bc.d.ts → edge-D2FpRlyS.d.ts} +74 -36
  9. package/dist/edge.cjs +9 -9
  10. package/dist/edge.d.cts +1 -1
  11. package/dist/edge.d.ts +1 -1
  12. package/dist/edge.js +2 -2
  13. package/dist/index.cjs +787 -466
  14. package/dist/index.d.cts +6 -6
  15. package/dist/index.d.ts +6 -6
  16. package/dist/index.js +682 -383
  17. package/package.json +6 -6
  18. package/benchmarks/direct.ts +0 -221
  19. package/benchmarks/edge-utils.ts +0 -28
  20. package/benchmarks/edge.ts +0 -491
  21. package/benchmarks/http.ts +0 -99
  22. package/benchmarks/latency.ts +0 -45
  23. package/benchmarks/memory-pressure.ts +0 -144
  24. package/benchmarks/multi-process-fanout.ts +0 -231
  25. package/benchmarks/multi-process-worker.ts +0 -151
  26. package/benchmarks/paths.ts +0 -25
  27. package/benchmarks/queue-amplification-utils.ts +0 -48
  28. package/benchmarks/queue-amplification.ts +0 -230
  29. package/benchmarks/redis-latency-proxy.ts +0 -100
  30. package/benchmarks/redis.ts +0 -107
  31. package/benchmarks/scenario-utils.ts +0 -38
  32. package/benchmarks/server.ts +0 -157
  33. package/benchmarks/slow-redis-latency.ts +0 -309
  34. package/benchmarks/slow-redis-utils.ts +0 -29
  35. package/benchmarks/slow-redis.ts +0 -47
  36. package/benchmarks/stampede.ts +0 -26
  37. package/benchmarks/stats.ts +0 -46
  38. package/benchmarks/workload.ts +0 -77
  39. package/examples/express-api/index.ts +0 -31
  40. package/examples/nextjs-api-routes/route.ts +0 -16
@@ -1,491 +0,0 @@
1
- import { CacheStack, MemoryLayer, RedisInvalidationBus, RedisLayer, RedisSingleFlightCoordinator } from '../src'
2
- import { type OutageResult, buildPayloadString, normalizeOutageResult } from './edge-utils'
3
- import {
4
- createRedisClient,
5
- ensureRedisContainer,
6
- pauseRedisContainer,
7
- stopRedisContainer,
8
- unpauseRedisContainer,
9
- waitForRedisReady
10
- } from './redis'
11
- import { createCountedFetcher, runConcurrent, summarizeScenario } from './scenario-utils'
12
- import { type DurationSummary, summarizeDurations } from './stats'
13
-
14
- const TTL_CONCURRENCY = 40
15
- const TTL_RUNS = 5
16
- const TTL_MS = 1_100
17
- const PAYLOAD_SAMPLES = 60
18
- const PAYLOAD_SMALL_BYTES = 1_024
19
- const PAYLOAD_LARGE_BYTES = 1_024 * 1_024
20
- const DISTRIBUTED_CONCURRENCY = 60
21
- const COMMAND_TIMEOUT_MS = 200
22
-
23
- interface ModeSummary extends DurationSummary {
24
- mode: string
25
- scenario: string
26
- fetchCount?: number
27
- }
28
-
29
- interface InvalidationResult {
30
- scenario: string
31
- success: boolean
32
- latencyMs: number
33
- observedVersion: number | null
34
- }
35
-
36
- interface DistributedSingleFlightResult {
37
- scenario: string
38
- concurrency: number
39
- latencyMs: number
40
- fetchCount: number
41
- }
42
-
43
- function sleep(ms: number): Promise<void> {
44
- return new Promise((resolve) => setTimeout(resolve, ms))
45
- }
46
-
47
- async function measure<TResult>(task: () => Promise<TResult>): Promise<{ durationMs: number; result: TResult }> {
48
- const startedAt = process.hrtime.bigint()
49
- const result = await task()
50
- return {
51
- durationMs: Number(process.hrtime.bigint() - startedAt) / 1_000_000,
52
- result
53
- }
54
- }
55
-
56
- function requireValue<T>(value: T | null, label: string): T {
57
- if (value === null) {
58
- throw new Error(`${label} unexpectedly returned null`)
59
- }
60
-
61
- return value
62
- }
63
-
64
- async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
65
- return Promise.race([
66
- promise,
67
- new Promise<T>((_, reject) => {
68
- setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)
69
- })
70
- ])
71
- }
72
-
73
- async function runTtlExpiryStampede(): Promise<ModeSummary[]> {
74
- const redis = createRedisClient()
75
- await redis.ping()
76
-
77
- const memoryCache = new CacheStack([new MemoryLayer({ ttl: 1, maxSize: 1_000 })], {
78
- stampedePrevention: true
79
- })
80
-
81
- const layeredCache = new CacheStack(
82
- [
83
- new MemoryLayer({ ttl: 1, maxSize: 1_000 }),
84
- new RedisLayer({ client: redis, ttl: 1, prefix: 'layercache-bench:edge:ttl:' })
85
- ],
86
- {
87
- stampedePrevention: true,
88
- singleFlightCoordinator: new RedisSingleFlightCoordinator({
89
- client: redis,
90
- prefix: 'layercache-bench:edge:ttl:sf:'
91
- })
92
- }
93
- )
94
-
95
- try {
96
- const results: ModeSummary[] = []
97
-
98
- for (const { mode, cache } of [
99
- { mode: 'memory', cache: memoryCache },
100
- { mode: 'layered', cache: layeredCache }
101
- ]) {
102
- const samples: number[] = []
103
- let fetchCount = 0
104
-
105
- for (let index = 0; index < TTL_RUNS; index += 1) {
106
- await cache.clear()
107
- if (mode === 'layered') {
108
- await redis.flushdb()
109
- }
110
-
111
- await cache.get(`ttl:key:${index}`, async () => ({ version: 1 }), { ttl: 1 })
112
- await sleep(TTL_MS)
113
-
114
- const fetcher = createCountedFetcher(async () => ({ version: 2 }))
115
- const { durationMs } = await measure(() =>
116
- runConcurrent(TTL_CONCURRENCY, async () =>
117
- requireValue(await cache.get(`ttl:key:${index}`, () => fetcher.run(), { ttl: 1 }), `${mode} ttl`)
118
- )
119
- )
120
-
121
- samples.push(durationMs)
122
- fetchCount += fetcher.getCount()
123
- }
124
-
125
- results.push({
126
- ...summarizeScenario('ttl-expiry-stampede', samples, fetchCount),
127
- mode,
128
- scenario: 'ttl-expiry-stampede'
129
- })
130
- }
131
-
132
- return results
133
- } finally {
134
- await Promise.all([memoryCache.disconnect(), layeredCache.disconnect()])
135
- await redis.quit()
136
- }
137
- }
138
-
139
- async function runPayloadSizeVariation(): Promise<ModeSummary[]> {
140
- const redis = createRedisClient()
141
- await redis.ping()
142
-
143
- const memoryCache = new CacheStack([new MemoryLayer({ ttl: 60, maxSize: 100 })])
144
- const redisCache = new CacheStack([
145
- new RedisLayer({ client: redis, ttl: 300, prefix: 'layercache-bench:edge:payload:' })
146
- ])
147
-
148
- try {
149
- const results: ModeSummary[] = []
150
-
151
- for (const scenario of [
152
- { mode: 'memory-1kb', cache: memoryCache, bytes: PAYLOAD_SMALL_BYTES },
153
- { mode: 'memory-1mb', cache: memoryCache, bytes: PAYLOAD_LARGE_BYTES },
154
- { mode: 'redis-1kb', cache: redisCache, bytes: PAYLOAD_SMALL_BYTES },
155
- { mode: 'redis-1mb', cache: redisCache, bytes: PAYLOAD_LARGE_BYTES }
156
- ]) {
157
- await scenario.cache.clear()
158
- if (scenario.mode.startsWith('redis')) {
159
- await redis.flushdb()
160
- }
161
-
162
- await scenario.cache.set(
163
- `payload:${scenario.mode}`,
164
- { size: scenario.bytes, payload: buildPayloadString(scenario.bytes) },
165
- { ttl: 60 }
166
- )
167
-
168
- const samples: number[] = []
169
- for (let index = 0; index < PAYLOAD_SAMPLES; index += 1) {
170
- const { durationMs } = await measure(async () => {
171
- requireValue(await scenario.cache.get(`payload:${scenario.mode}`), `${scenario.mode} payload`)
172
- })
173
- samples.push(durationMs)
174
- }
175
-
176
- results.push({
177
- ...summarizeDurations('payload-warm-hit', samples),
178
- mode: scenario.mode,
179
- scenario: 'payload-warm-hit'
180
- })
181
- }
182
-
183
- return results
184
- } finally {
185
- await Promise.all([memoryCache.disconnect(), redisCache.disconnect()])
186
- await redis.quit()
187
- }
188
- }
189
-
190
- async function runRedisOutageScenario(): Promise<OutageResult[]> {
191
- const strictRedis = createRedisClient()
192
- const gracefulRedis = createRedisClient()
193
- await Promise.all([strictRedis.ping(), gracefulRedis.ping()])
194
-
195
- const strictCache = new CacheStack(
196
- [
197
- new MemoryLayer({ ttl: 60, maxSize: 500 }),
198
- new RedisLayer({
199
- client: strictRedis,
200
- ttl: 300,
201
- prefix: 'layercache-bench:edge:strict:',
202
- commandTimeoutMs: COMMAND_TIMEOUT_MS
203
- })
204
- ],
205
- {
206
- stampedePrevention: true,
207
- singleFlightCoordinator: new RedisSingleFlightCoordinator({
208
- client: strictRedis,
209
- prefix: 'layercache-bench:edge:strict:sf:',
210
- commandTimeoutMs: COMMAND_TIMEOUT_MS
211
- })
212
- }
213
- )
214
-
215
- const gracefulCache = new CacheStack(
216
- [
217
- new MemoryLayer({ ttl: 60, maxSize: 500 }),
218
- new RedisLayer({
219
- client: gracefulRedis,
220
- ttl: 300,
221
- prefix: 'layercache-bench:edge:graceful:',
222
- commandTimeoutMs: COMMAND_TIMEOUT_MS
223
- })
224
- ],
225
- {
226
- stampedePrevention: true,
227
- gracefulDegradation: { retryAfterMs: 10_000 },
228
- singleFlightCoordinator: new RedisSingleFlightCoordinator({
229
- client: gracefulRedis,
230
- prefix: 'layercache-bench:edge:graceful:sf:',
231
- commandTimeoutMs: COMMAND_TIMEOUT_MS
232
- })
233
- }
234
- )
235
-
236
- try {
237
- await strictCache.clear()
238
- await gracefulCache.clear()
239
- await strictRedis.flushdb()
240
-
241
- await strictCache.get('outage:warm', async () => ({ version: 'warm-strict' }), { ttl: 60 })
242
- await gracefulCache.get('outage:warm', async () => ({ version: 'warm-graceful' }), { ttl: 60 })
243
-
244
- await pauseRedisContainer()
245
-
246
- const hotResults = await Promise.all([
247
- measure(async () => {
248
- requireValue(await strictCache.get('outage:warm'), 'strict hot')
249
- })
250
- .then(({ durationMs }) => normalizeOutageResult('strict-hot-hit', true, durationMs))
251
- .catch((error) =>
252
- normalizeOutageResult('strict-hot-hit', false, 0, error instanceof Error ? error.message : String(error))
253
- ),
254
- measure(async () => {
255
- requireValue(await gracefulCache.get('outage:warm'), 'graceful hot')
256
- })
257
- .then(({ durationMs }) => normalizeOutageResult('graceful-hot-hit', true, durationMs))
258
- .catch((error) =>
259
- normalizeOutageResult('graceful-hot-hit', false, 0, error instanceof Error ? error.message : String(error))
260
- )
261
- ])
262
-
263
- const coldStrict = await measure(() =>
264
- withTimeout(
265
- strictCache.get('outage:cold:strict', async () => ({ version: 'strict-cold' }), { ttl: 60 }),
266
- 2_000,
267
- 'strict cold miss'
268
- )
269
- )
270
- .then(({ durationMs }) => normalizeOutageResult('strict-cold-miss', true, durationMs))
271
- .catch((error) =>
272
- normalizeOutageResult('strict-cold-miss', false, 0, error instanceof Error ? error.message : String(error))
273
- )
274
-
275
- const coldGraceful = await measure(() =>
276
- withTimeout(
277
- gracefulCache.get('outage:cold:graceful', async () => ({ version: 'graceful-cold' }), { ttl: 60 }),
278
- 2_000,
279
- 'graceful cold miss'
280
- )
281
- )
282
- .then(({ durationMs }) => normalizeOutageResult('graceful-cold-miss', true, durationMs))
283
- .catch((error) =>
284
- normalizeOutageResult('graceful-cold-miss', false, 0, error instanceof Error ? error.message : String(error))
285
- )
286
-
287
- return [...hotResults, coldStrict, coldGraceful]
288
- } finally {
289
- await unpauseRedisContainer()
290
- await waitForRedisReady()
291
- await Promise.all([strictCache.disconnect(), gracefulCache.disconnect()])
292
- await Promise.all([strictRedis.quit(), gracefulRedis.quit()])
293
- }
294
- }
295
-
296
- async function runMultiInstanceInvalidation(): Promise<InvalidationResult> {
297
- const publisher = createRedisClient()
298
- const subscriberA = createRedisClient()
299
- const subscriberB = createRedisClient()
300
- const dataA = createRedisClient()
301
- const dataB = createRedisClient()
302
-
303
- await Promise.all([publisher.ping(), subscriberA.ping(), subscriberB.ping(), dataA.ping(), dataB.ping()])
304
-
305
- const busA = new RedisInvalidationBus({
306
- publisher,
307
- subscriber: subscriberA,
308
- channel: 'layercache-bench:edge:invalidation'
309
- })
310
- const busB = new RedisInvalidationBus({
311
- publisher,
312
- subscriber: subscriberB,
313
- channel: 'layercache-bench:edge:invalidation'
314
- })
315
-
316
- const cacheA = new CacheStack(
317
- [
318
- new MemoryLayer({ ttl: 60, maxSize: 100 }),
319
- new RedisLayer({ client: dataA, ttl: 300, prefix: 'layercache-bench:edge:invalidation:' })
320
- ],
321
- {
322
- invalidationBus: busA,
323
- broadcastL1Invalidation: true
324
- }
325
- )
326
- const cacheB = new CacheStack(
327
- [
328
- new MemoryLayer({ ttl: 60, maxSize: 100 }),
329
- new RedisLayer({ client: dataB, ttl: 300, prefix: 'layercache-bench:edge:invalidation:' })
330
- ],
331
- {
332
- invalidationBus: busB,
333
- broadcastL1Invalidation: true
334
- }
335
- )
336
-
337
- try {
338
- await cacheA.clear()
339
- await dataA.flushdb()
340
-
341
- await cacheA.get('shared:key', async () => ({ version: 1 }), { ttl: 60 })
342
- await cacheB.get('shared:key', async () => ({ version: 1 }), { ttl: 60 })
343
-
344
- await cacheA.delete('shared:key')
345
- await cacheA.get('shared:key', async () => ({ version: 2 }), { ttl: 60 })
346
-
347
- const startedAt = performance.now()
348
- let observedVersion: number | null = null
349
- for (let attempt = 0; attempt < 40; attempt += 1) {
350
- const value = await cacheB.get<{ version: number }>('shared:key')
351
- observedVersion = value?.version ?? null
352
- if (observedVersion === 2) {
353
- return {
354
- scenario: 'multi-instance-invalidation',
355
- success: true,
356
- latencyMs: Number((performance.now() - startedAt).toFixed(3)),
357
- observedVersion
358
- }
359
- }
360
-
361
- await sleep(25)
362
- }
363
-
364
- return {
365
- scenario: 'multi-instance-invalidation',
366
- success: false,
367
- latencyMs: Number((performance.now() - startedAt).toFixed(3)),
368
- observedVersion
369
- }
370
- } finally {
371
- await Promise.all([cacheA.disconnect(), cacheB.disconnect()])
372
- await Promise.all([publisher.quit(), subscriberA.quit(), subscriberB.quit(), dataA.quit(), dataB.quit()])
373
- }
374
- }
375
-
376
- async function runDistributedSingleFlight(): Promise<DistributedSingleFlightResult> {
377
- const redisA = createRedisClient()
378
- const redisB = createRedisClient()
379
- const coordinatorClient = createRedisClient()
380
-
381
- await Promise.all([redisA.ping(), redisB.ping(), coordinatorClient.ping()])
382
-
383
- const coordinator = new RedisSingleFlightCoordinator({
384
- client: coordinatorClient,
385
- prefix: 'layercache-bench:edge:distributed:sf:'
386
- })
387
-
388
- const cacheA = new CacheStack(
389
- [
390
- new MemoryLayer({ ttl: 60, maxSize: 100 }),
391
- new RedisLayer({ client: redisA, ttl: 300, prefix: 'layercache-bench:edge:distributed:' })
392
- ],
393
- {
394
- stampedePrevention: true,
395
- singleFlightCoordinator: coordinator
396
- }
397
- )
398
- const cacheB = new CacheStack(
399
- [
400
- new MemoryLayer({ ttl: 60, maxSize: 100 }),
401
- new RedisLayer({ client: redisB, ttl: 300, prefix: 'layercache-bench:edge:distributed:' })
402
- ],
403
- {
404
- stampedePrevention: true,
405
- singleFlightCoordinator: coordinator
406
- }
407
- )
408
-
409
- try {
410
- await cacheA.clear()
411
- await redisA.flushdb()
412
-
413
- let fetchCount = 0
414
- const fetchUser = async () => {
415
- fetchCount += 1
416
- await sleep(25)
417
- return { id: 1 }
418
- }
419
-
420
- const startedAt = performance.now()
421
- await runConcurrent(DISTRIBUTED_CONCURRENCY, (index) =>
422
- (index % 2 === 0 ? cacheA : cacheB)
423
- .get('distributed:user:1', fetchUser)
424
- .then((value) => requireValue(value, 'distributed'))
425
- )
426
-
427
- return {
428
- scenario: 'distributed-single-flight',
429
- concurrency: DISTRIBUTED_CONCURRENCY,
430
- latencyMs: Number((performance.now() - startedAt).toFixed(3)),
431
- fetchCount
432
- }
433
- } finally {
434
- await Promise.all([cacheA.disconnect(), cacheB.disconnect()])
435
- await Promise.all([redisA.quit(), redisB.quit(), coordinatorClient.quit()])
436
- }
437
- }
438
-
439
- async function main(): Promise<void> {
440
- await ensureRedisContainer()
441
- await waitForRedisReady()
442
-
443
- try {
444
- const ttlResults = await runTtlExpiryStampede()
445
- const payloadResults = await runPayloadSizeVariation()
446
- const outageResults = await runRedisOutageScenario()
447
- const invalidationResult = await runMultiInstanceInvalidation()
448
- const distributedResult = await runDistributedSingleFlight()
449
-
450
- console.table([
451
- ...ttlResults.map((result) => ({
452
- mode: result.mode,
453
- scenario: result.scenario,
454
- avgMs: result.avgMs,
455
- p95Ms: result.p95Ms,
456
- fetchCount: result.fetchCount
457
- })),
458
- ...payloadResults.map((result) => ({
459
- mode: result.mode,
460
- scenario: result.scenario,
461
- avgMs: result.avgMs,
462
- p95Ms: result.p95Ms
463
- })),
464
- ...outageResults,
465
- invalidationResult,
466
- distributedResult
467
- ])
468
- console.log(
469
- JSON.stringify(
470
- {
471
- type: 'edge-benchmark',
472
- ttlResults,
473
- payloadResults,
474
- outageResults,
475
- invalidationResult,
476
- distributedResult
477
- },
478
- null,
479
- 2
480
- )
481
- )
482
- } finally {
483
- await unpauseRedisContainer().catch(() => {})
484
- await stopRedisContainer()
485
- }
486
- }
487
-
488
- void main().catch((error) => {
489
- console.error(error)
490
- process.exitCode = 1
491
- })
@@ -1,99 +0,0 @@
1
- import autocannon, { type Result } from 'autocannon'
2
- import { createRedisClient, ensureRedisContainer, stopRedisContainer, waitForRedisReady } from './redis'
3
- import { startBenchmarkServer } from './server'
4
-
5
- interface HttpBenchmarkSummary {
6
- route: string
7
- coldStartMs: number
8
- averageLatencyMs: number
9
- p97_5LatencyMs: number
10
- maxLatencyMs: number
11
- requestsPerSec: number
12
- throughputBytesPerSec: number
13
- errors: number
14
- timeouts: number
15
- }
16
-
17
- async function singleRequestLatency(url: string): Promise<number> {
18
- const startedAt = process.hrtime.bigint()
19
- const response = await fetch(url)
20
- await response.arrayBuffer()
21
-
22
- return Number(process.hrtime.bigint() - startedAt) / 1_000_000
23
- }
24
-
25
- function runAutocannon(url: string): Promise<Result> {
26
- return new Promise((resolve, reject) => {
27
- const instance = autocannon(
28
- {
29
- url,
30
- connections: 40,
31
- duration: 8,
32
- pipelining: 1
33
- },
34
- (error: Error | null, result: Result) => {
35
- if (error) {
36
- reject(error)
37
- return
38
- }
39
-
40
- resolve(result)
41
- }
42
- )
43
-
44
- instance.on('error', reject)
45
- })
46
- }
47
-
48
- function summarizeHttpRoute(route: string, coldStartMs: number, result: Result): HttpBenchmarkSummary {
49
- return {
50
- route,
51
- coldStartMs: Number(coldStartMs.toFixed(3)),
52
- averageLatencyMs: Number(result.latency.average.toFixed(3)),
53
- p97_5LatencyMs: Number(result.latency.p97_5.toFixed(3)),
54
- maxLatencyMs: Number(result.latency.max.toFixed(3)),
55
- requestsPerSec: Number(result.requests.average.toFixed(3)),
56
- throughputBytesPerSec: Number(result.throughput.average.toFixed(3)),
57
- errors: result.errors,
58
- timeouts: result.timeouts
59
- }
60
- }
61
-
62
- async function main(): Promise<void> {
63
- await ensureRedisContainer()
64
-
65
- await waitForRedisReady()
66
- const redis = createRedisClient()
67
- await redis.ping()
68
-
69
- const server = await startBenchmarkServer(redis)
70
- const baseUrl = `http://127.0.0.1:${server.port}`
71
-
72
- try {
73
- const routes = ['/nocache', '/memory', '/layered']
74
- const results: HttpBenchmarkSummary[] = []
75
-
76
- for (const route of routes) {
77
- await server.reset()
78
- if (route !== '/nocache') {
79
- await server.warm()
80
- }
81
-
82
- const coldStartMs = await singleRequestLatency(`${baseUrl}${route}`)
83
- const result = await runAutocannon(`${baseUrl}${route}`)
84
- results.push(summarizeHttpRoute(route, coldStartMs, result))
85
- }
86
-
87
- console.table(results)
88
- console.log(JSON.stringify({ type: 'http-benchmark', results }, null, 2))
89
- } finally {
90
- await server.close()
91
- await redis.quit()
92
- await stopRedisContainer()
93
- }
94
- }
95
-
96
- void main().catch((error) => {
97
- console.error(error)
98
- process.exitCode = 1
99
- })
@@ -1,45 +0,0 @@
1
- import { performance } from 'node:perf_hooks'
2
- import Redis from 'ioredis-mock'
3
- import { CacheStack, MemoryLayer, RedisLayer } from '../src'
4
-
5
- async function main(): Promise<void> {
6
- const iterations = 5_000
7
- const redis = new Redis()
8
- const cache = new CacheStack([
9
- new MemoryLayer({ ttl: 60, maxSize: 10_000 }),
10
- new RedisLayer({ client: redis, ttl: 300 })
11
- ])
12
-
13
- await cache.set('bench:key', { ok: true })
14
-
15
- const memoryStart = performance.now()
16
- for (let index = 0; index < iterations; index += 1) {
17
- await cache.get('bench:key')
18
- }
19
- const memoryElapsed = performance.now() - memoryStart
20
-
21
- await redis.del('bench:key')
22
- await redis.set('bench:key', JSON.stringify({ ok: true }))
23
-
24
- const redisOnlyStart = performance.now()
25
- for (let index = 0; index < iterations; index += 1) {
26
- await cache.get('bench:key')
27
- await cache.delete('bench:key')
28
- await redis.set('bench:key', JSON.stringify({ ok: true }))
29
- }
30
- const redisOnlyElapsed = performance.now() - redisOnlyStart
31
-
32
- const noCacheStart = performance.now()
33
- for (let index = 0; index < iterations; index += 1) {
34
- await new Promise((resolve) => setTimeout(resolve, 1))
35
- }
36
- const noCacheElapsed = performance.now() - noCacheStart
37
-
38
- console.table({
39
- l1MemoryAvgMs: memoryElapsed / iterations,
40
- l2RedisAvgMs: redisOnlyElapsed / iterations,
41
- noCacheAvgMs: noCacheElapsed / iterations
42
- })
43
- }
44
-
45
- void main()