11import path from 'path'
22import fs from 'fs'
33import crypto from 'crypto'
4+ import zlib from 'zlib'
45
56import { fetchWithRetry } from './fetch-utils'
67import 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
1215const 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