Skip to content

feat(cache): compress big sqlite cache entries with zstd - #5744

Open
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:feat/sqlite-zstd-compression
Open

feat(cache): compress big sqlite cache entries with zstd#5744
mcollina wants to merge 2 commits into
nodejs:mainfrom
mcollina:feat/sqlite-zstd-compression

Conversation

@mcollina

Copy link
Copy Markdown
Member

Store SQLite cache response bodies that are larger than the new compressThreshold option (default 1 MiB) compressed with zstdCompressSync, decompressing them when read back so cached responses still return the exact original body. This lets large cached responses occupy significantly less database space.

The change:

  • Adds a compressThreshold option to SqliteCacheStore.
  • Adds a compressed column to the cache table (bumping cacheInterceptorV3 to cacheInterceptorV4) so stored bodies know whether to decompress.
  • Falls back to storing bodies uncompressed when zstd is unavailable (Node < 22.19), while still requiring node:sqlite.
  • Documents the new option in the Cache Store API docs and the SqliteCacheStoreOpts types.
  • Adds a test verifying big bodies are stored compressed and round-trip correctly, and that small bodies remain uncompressed.

Store response bodies larger than the compressThreshold (default 1 MiB)
compressed with zstdCompressSync, decompressing them when read back. Adds
a compressThreshold option and a compressed column (bumping the cache table
to V4), so large cached responses occupy less database space.
@mcollina
mcollina requested a review from ronag August 31, 2026 13:17

@ronag ronag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 MB is quite big? text and json is quite compressible also for smaller size?

Comment thread lib/cache/sqlite-cache-store.js Outdated

-- Data returned to the interceptor
body BUF NULL,
compressed INTEGER NOT NULL DEFAULT 0,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be good to make this a string and use "zstd", would make it more forward compatible

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - the compressed column now stores the algorithm as a string ("zstd") instead of an integer, and get() only decompresses rows whose algorithm is zstd, so future algorithms can be added without breaking existing data.

@codecov-commenter

codecov-commenter commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.97590% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.47%. Comparing base (fc3450d) to head (3b07894).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
lib/cache/sqlite-cache-store.js 93.97% 5 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #5744    +/-   ##
========================================
  Coverage   93.47%   93.47%            
========================================
  Files         110      110            
  Lines       38908    39017   +109     
========================================
+ Hits        36368    36473   +105     
- Misses       2540     2544     +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread lib/cache/sqlite-cache-store.js Outdated
Comment on lines +302 to +305
if (body && size > this.#compressThreshold && hasZstd) {
storedBody = zstdCompressSync(body)
compressed = 1
}

@ronag ronag Aug 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend checking the response mime type to find easily compressible responses, e.g. json, xml, plain text etc... and ignore the compressThreshold for those

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - responses with an easily compressible Content-Type (text/*, application/json, application/xml, application/javascript, ...) are now compressed regardless of the compressThreshold. I also guard against pathological inflation: compression only applies when the zstd output is actually smaller than the original body.

Address review feedback: store the compression algorithm as a string
('zstd') in the compressed column for forward compatibility, and compress
responses with easily compressible Content-Types (json, xml, plain text)
even when they are below compressThreshold. Only compress when zstd actually
shrinks the payload.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants