Skip to content

Commit 41b8750

Browse files
committed
refactor(@angular/build): add composite index on last_accessed in sqlite cache store
Previously, the SQLite cache table had no secondary index on `last_accessed`. Cache eviction routines during `close()` required full table scans for both TTL-based pruning and LRU size-based pruning. Specifically, calculating running sizes using a window function ordered by `last_accessed DESC, key DESC` required SQLite to materialize and sort the entire table in temporary memory on every cache close. In large persistent caches, these full scans and temporary sorts introduced unnecessary CPU and I/O overhead on shutdown. A composite index `idx_cache_accessed` on `(last_accessed DESC, key DESC)` is now created on the cache table. This enables SQLite to directly seek entries older than the TTL limit using an indexed binary search and stream rows in descending access order for the window aggregate calculation without requiring an in-memory sort pass.
1 parent 6ee559c commit 41b8750

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export class SqliteCacheStore implements PersistentCacheStore<unknown> {
4646
this.#db.exec(
4747
'CREATE TABLE IF NOT EXISTS cache (key TEXT PRIMARY KEY, value BLOB, last_accessed INTEGER NOT NULL) WITHOUT ROWID;',
4848
);
49+
this.#db.exec(
50+
'CREATE INDEX IF NOT EXISTS idx_cache_accessed ON cache (last_accessed DESC, key DESC);',
51+
);
4952

5053
this.#getStmt = this.#db.prepare('SELECT value FROM cache WHERE key = ?');
5154
this.#hasStmt = this.#db.prepare('SELECT 1 FROM cache WHERE key = ?');

packages/angular/build/src/tools/esbuild/sqlite-cache-store_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,23 @@ describe('SqliteCacheStore', () => {
187187
checkStore.close();
188188
});
189189

190+
it('should create an index on last_accessed and key', async () => {
191+
// Trigger db initialization
192+
await store.set('test-key', 'test-value');
193+
store.close();
194+
195+
const { DatabaseSync } = await import('node:sqlite');
196+
const directDb = new DatabaseSync(cachePath);
197+
const indexRows = directDb
198+
.prepare(
199+
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'cache' AND name = 'idx_cache_accessed'",
200+
)
201+
.all();
202+
directDb.close();
203+
204+
expect(indexRows.length).toBe(1);
205+
});
206+
190207
describe('NG_BUILD_CACHE_STORE env variable option', () => {
191208
it('should force SQLite when NG_BUILD_CACHE_STORE=sqlite', () => {
192209
const code = `

0 commit comments

Comments
 (0)