Skip to content

Commit 08be659

Browse files
heiskrCopilot
andauthored
Store remote JSON cache as compressed Buffers to reduce memory (#60247)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent dd97ed7 commit 08be659

File tree

1 file changed

+24
-7
lines changed

1 file changed

+24
-7
lines changed

src/frame/lib/get-remote-json.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import path from 'path'
22
import fs from 'fs'
33
import crypto from 'crypto'
4+
import zlib from 'zlib'
45

56
import { fetchWithRetry } from './fetch-utils'
67
import statsd from '@/observability/lib/statsd'
78

8-
// The only reason this is exported is for the sake of the unit tests'
9-
// ability to test in-memory miss after purging this with a mutation
10-
export const cache = new Map<string, unknown>()
9+
// Store compressed Buffers instead of parsed JSON objects.
10+
// Redirect JSON files are 5-10 MB each when parsed but compress
11+
// to ~1-2 MB with deflate. We decompress on each access (~1 ms)
12+
// which is negligible compared to the memory savings.
13+
export const cache = new Map<string, Buffer>()
1114

1215
const inProd = process.env.NODE_ENV === 'production'
1316

@@ -20,6 +23,16 @@ interface GetRemoteJSONConfig {
2023
}
2124
}
2225

26+
function compressStringToCache(cacheKey: string, jsonString: string): void {
27+
cache.set(cacheKey, zlib.deflateSync(Buffer.from(jsonString)))
28+
}
29+
30+
function decompressFromCache(cacheKey: string): unknown {
31+
const compressed = cache.get(cacheKey)
32+
if (!compressed) return undefined
33+
return JSON.parse(zlib.inflateSync(compressed).toString())
34+
}
35+
2336
// Wrapper on `got()` that is able to both cache in memory and on disk.
2437
// The on-disk caching is in `.remotejson/`.
2538
// We use this for downloading `redirects.json` files from one of the
@@ -60,8 +73,10 @@ export default async function getRemoteJSON(
6073
// It might exist on disk, but it could be empty
6174
if (body) {
6275
try {
63-
// It might be corrupted JSON.
64-
cache.set(cacheKey, JSON.parse(body))
76+
// Validate JSON, then compress the raw string directly
77+
// instead of parse → stringify → compress
78+
JSON.parse(body)
79+
compressStringToCache(cacheKey, body)
6580
fromCache = 'disk'
6681
foundOnDisk = true
6782
} catch (error) {
@@ -107,7 +122,9 @@ export default async function getRemoteJSON(
107122
}
108123

109124
const body = await res.text()
110-
cache.set(cacheKey, JSON.parse(body))
125+
// Validate JSON, then compress raw string directly
126+
JSON.parse(body)
127+
compressStringToCache(cacheKey, body)
111128

112129
// Only write to disk for testing and local review.
113130
// In production, we never write to disk. Only in-memory.
@@ -119,5 +136,5 @@ export default async function getRemoteJSON(
119136
}
120137
const tags = [`from_cache:${fromCache}`]
121138
statsd.increment('middleware.get_remote_json', 1, tags)
122-
return cache.get(cacheKey)
139+
return decompressFromCache(cacheKey)
123140
}

0 commit comments

Comments
 (0)