From 74b4dd49c4eda38e306615f15ac171a7c4d7bdac Mon Sep 17 00:00:00 2001 From: Eric Eldredge Date: Wed, 3 Nov 2021 13:31:58 -0400 Subject: [PATCH] Fall back to FS for large blobs in LMDBCache --- packages/core/cache/src/LMDBCache.js | 55 ++++++++++++++++++---------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/packages/core/cache/src/LMDBCache.js b/packages/core/cache/src/LMDBCache.js index a9ac101a9de..2f8c988fa64 100644 --- a/packages/core/cache/src/LMDBCache.js +++ b/packages/core/cache/src/LMDBCache.js @@ -44,44 +44,46 @@ export class LMDBCache implements Cache { } has(key: string): Promise { - return Promise.resolve(this.store.get(key) != null); + if (this.store.get(key) != null) return Promise.resolve(true); + return this.hasLargeBlob(key); } - get(key: string): Promise { - let data = this.store.get(key); - if (data == null) { - return Promise.resolve(null); - } - - return Promise.resolve(deserialize(data)); + async get(key: string): Promise { + let data = await this.getBuffer(key); + return data == null ? null : deserialize(data); } async set(key: string, value: mixed): Promise { - await this.store.put(key, serialize(value)); + await this.setBlob(key, serialize(value)); } getStream(key: string): Readable { - return blobToStream(this.store.get(key)); + return blobToStream( + this.store.get(key) ?? this.fs.readFileSync(path.join(this.dir, key)), + ); } async setStream(key: string, stream: Readable): Promise { - let buf = await bufferStream(stream); - await this.store.put(key, buf); + await this.setBlob(key, await bufferStream(stream)); } - getBlob(key: string): Promise { - let buffer = this.store.get(key); - return buffer != null - ? Promise.resolve(buffer) - : Promise.reject(new Error(`Key ${key} not found in cache`)); + async getBlob(key: string): Promise { + let buffer = await this.getBuffer(key); + if (buffer == null) throw new Error(`Key ${key} not found in cache`); + return buffer; } async setBlob(key: string, contents: Buffer | string): Promise { - await this.store.put(key, contents); + if (isLargeBlob(contents)) await this.setLargeBlob(key, contents); + else await this.store.put(key, contents); } - getBuffer(key: string): Promise { - return Promise.resolve(this.store.get(key)); + async getBuffer(key: string): Promise { + let buffer = this.store.get(key); + if (buffer == null && (await this.hasLargeBlob(key))) { + buffer = await this.getLargeBlob(key); + } + return buffer; } hasLargeBlob(key: string): Promise { @@ -97,4 +99,17 @@ export class LMDBCache implements Cache { } } +// lmbd-store decodes cached binary data into a Node Buffer +// via `Nan::NewBuffer`, which enforces a max size of ~1GB. +// We subtract 9 bytes to account for any compression heaader +// added by lmbd-store when encoding the data. +// See: https://github.com/nodejs/nan/issues/883 +const MAX_BUFFER_SIZE = 0x3fffffff - 9; + +function isLargeBlob(contents: Buffer | string): boolean { + return typeof contents === 'string' + ? Buffer.byteLength(contents) > MAX_BUFFER_SIZE + : contents.length > MAX_BUFFER_SIZE; +} + registerSerializableClass(`${packageJson.version}:LMDBCache`, LMDBCache);