From cad134e4203880d575381c090e7359d9d5eab68d Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Wed, 2 Apr 2025 16:08:44 +0200 Subject: [PATCH] cache: more efficient pruning Reduce transaction lock times. Refs: https://github.com/nodejs/undici/issues/4124 --- lib/cache/sqlite-cache-store.js | 70 +++++++++++---------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/lib/cache/sqlite-cache-store.js b/lib/cache/sqlite-cache-store.js index e027cff84ea..69ffc524c60 100644 --- a/lib/cache/sqlite-cache-store.js +++ b/lib/cache/sqlite-cache-store.js @@ -62,16 +62,13 @@ module.exports = class SqliteCacheStore { */ #deleteByUrlQuery - /** - * @type {import('node:sqlite').StatementSync} - */ - #countEntriesQuery - /** * @type {import('node:sqlite').StatementSync | null} */ #deleteOldValuesQuery + #pruneInterval + /** * @param {import('../../types/cache-interceptor.d.ts').default.SqliteCacheStoreOpts | undefined} opts */ @@ -196,10 +193,6 @@ module.exports = class SqliteCacheStore { `DELETE FROM cacheInterceptorV${VERSION} WHERE url = ?` ) - this.#countEntriesQuery = this.#db.prepare( - `SELECT COUNT(*) AS total FROM cacheInterceptorV${VERSION}` - ) - this.#deleteExpiredValuesQuery = this.#db.prepare( `DELETE FROM cacheInterceptorV${VERSION} WHERE deleteAt <= ?` ) @@ -213,12 +206,17 @@ module.exports = class SqliteCacheStore { id FROM cacheInterceptorV${VERSION} ORDER BY cachedAt DESC - LIMIT ? + OFFSET ${this.#maxCount} ) `) + + this.#pruneInterval = setInterval(() => { + this.#prune() + }, 10e3) } close () { + clearInterval(this.#pruneInterval) this.#db.close() } @@ -232,7 +230,7 @@ module.exports = class SqliteCacheStore { const value = this.#findValue(key) return value ? { - body: value.body ? Buffer.from(value.body.buffer, value.body.byteOffset, value.body.byteLength) : undefined, + body: value.body ? Buffer.from(value.body.buffer) : undefined, statusCode: value.statusCode, statusMessage: value.statusMessage, headers: value.headers ? JSON.parse(value.headers) : undefined, @@ -279,7 +277,6 @@ module.exports = class SqliteCacheStore { existingValue.id ) } else { - this.#prune() // New response, let's insert it this.#insertValueQuery.run( url, @@ -346,36 +343,12 @@ module.exports = class SqliteCacheStore { } #prune () { - if (this.size <= this.#maxCount) { - return 0 - } - - { - const removed = this.#deleteExpiredValuesQuery.run(Date.now()).changes - if (removed) { - return removed - } - } - - { - const removed = this.#deleteOldValuesQuery?.run(Math.max(Math.floor(this.#maxCount * 0.1), 1)).changes - if (removed) { - return removed - } - } + this.#deleteExpiredValuesQuery.run(Date.now()) + this.#deleteOldValuesQuery?.run() return 0 } - /** - * Counts the number of rows in the cache - * @returns {Number} - */ - get size () { - const { total } = this.#countEntriesQuery.get() - return total - } - /** * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key * @returns {string} @@ -411,6 +384,10 @@ module.exports = class SqliteCacheStore { let matches = true if (value.vary) { + if (!headers) { + return undefined + } + const vary = JSON.parse(value.vary) for (const header in vary) { @@ -436,21 +413,18 @@ module.exports = class SqliteCacheStore { * @returns {boolean} */ function headerValueEquals (lhs, rhs) { - if (lhs == null && rhs == null) { - return true - } - - if ((lhs == null && rhs != null) || - (lhs != null && rhs == null)) { - return false - } - if (Array.isArray(lhs) && Array.isArray(rhs)) { if (lhs.length !== rhs.length) { return false } - return lhs.every((x, i) => x === rhs[i]) + for (let i = 0; i < lhs.length; i++) { + if (rhs.includes(lhs[i])) { + return false + } + } + + return true } return lhs === rhs