Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SQLite-based package cache #26608

Merged
merged 47 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4b18073
feat: SQLite-based package cache
zharinov Jan 12, 2024
a7fd248
Simplify implementation
zharinov Jan 12, 2024
8be0fb3
feat: Add `totalMs` for package cache stats
zharinov Jan 12, 2024
bc72b8b
Add composite index
zharinov Jan 12, 2024
34d6d90
Refactor
zharinov Jan 12, 2024
9201faf
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 12, 2024
30823fa
Update lib/util/cache/package/sqlite.ts
zharinov Jan 12, 2024
0ea5dd7
Use `unixepoch()`
zharinov Jan 12, 2024
7aeaa87
Options and coverage
zharinov Jan 12, 2024
f293655
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 12, 2024
f204773
Fix doc
zharinov Jan 12, 2024
9225e68
Cleanup log
zharinov Jan 12, 2024
8e41f0d
Fix types
zharinov Jan 12, 2024
8138a8f
Fix coverage
zharinov Jan 12, 2024
a58fa38
Move cleanup
zharinov Jan 13, 2024
5539de7
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 13, 2024
16457bc
Rename table
zharinov Jan 14, 2024
6537b0d
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 15, 2024
66d23e3
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 15, 2024
bfe44df
Revert lockfile
zharinov Jan 15, 2024
3952a7d
Update lockfile
zharinov Jan 15, 2024
3014171
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 15, 2024
247c32b
Add compressing
zharinov Jan 15, 2024
8a9b6d4
Async compression
zharinov Jan 15, 2024
f364782
Lower the compression level
zharinov Jan 16, 2024
1cb6077
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 16, 2024
b6db673
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 16, 2024
e101c79
Apply suggestions from code review
zharinov Jan 17, 2024
ac00892
Fix lockfile
zharinov Jan 17, 2024
d07311a
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 17, 2024
0ce9df1
Fix lockfile
zharinov Jan 17, 2024
c2742f0
Prettier fix
zharinov Jan 17, 2024
2e9f11f
Use env variable
zharinov Jan 17, 2024
5b0c7c0
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 17, 2024
64c78d8
Fix coverage
zharinov Jan 17, 2024
6bbc5d6
Refactor
zharinov Jan 17, 2024
55093bd
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 18, 2024
aecb10a
Add init log message
zharinov Jan 19, 2024
c2e19e0
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 19, 2024
d23ee3b
Fix tests and coverage
zharinov Jan 19, 2024
fae602a
Update docs/usage/self-hosted-experimental.md
zharinov Jan 19, 2024
f6203d9
Update lib/util/cache/package/index.spec.ts
zharinov Jan 19, 2024
fa6b58d
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 19, 2024
c0b64f0
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 23, 2024
a280e27
Simplify file existence check
zharinov Jan 23, 2024
8eaeaea
Merge branch 'main' into feat/sqlite-package-cache
zharinov Jan 29, 2024
926d59b
Fix
zharinov Jan 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface GlobalOnlyConfig {
repositories?: RenovateRepository[];
platform?: PlatformId;
endpoint?: string;
useSqliteCache?: boolean;
rarkins marked this conversation as resolved.
Show resolved Hide resolved
}

// Config options used within the repository worker, but not user configurable
Expand Down
17 changes: 17 additions & 0 deletions lib/util/cache/package/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AllConfig } from '../../../config/types';
import * as memCache from '../memory';
import * as fileCache from './file';
import * as redisCache from './redis';
import { SqlitePackageCache } from './sqlite';
import type { PackageCache } from './types';

let cacheProxy: PackageCache | undefined;
Expand Down Expand Up @@ -60,6 +61,22 @@ export async function init(config: AllConfig): Promise<void> {
get: redisCache.get,
set: redisCache.set,
};
} else if (config.cacheDir && config.useSqliteCache) {
const sqlite = await SqlitePackageCache.init(config.cacheDir);
cacheProxy = {
get: (namespace: string, key: string) =>
Promise.resolve(sqlite.get(namespace, key)),
set: (
namespace: string,
key: string,
value: unknown,
ttlMinutes: number,
) => Promise.resolve(sqlite.set(namespace, key, value, ttlMinutes)),
cleanup: () => {
sqlite.close();
return Promise.resolve();
},
};
} else if (config.cacheDir) {
fileCache.init(config.cacheDir);
cacheProxy = {
Expand Down
33 changes: 33 additions & 0 deletions lib/util/cache/package/sqlite.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { withDir } from 'tmp-promise';
import { SqlitePackageCache } from './sqlite';

function withSqlite<T>(
fn: (sqlite: SqlitePackageCache) => T | Promise<T>,
): Promise<T> {
return withDir(
async ({ path }) => {
const sqlite = await SqlitePackageCache.init(path);
const res = await fn(sqlite);
sqlite.close();
return res;
},
{ unsafeCleanup: true },
);
}

describe('util/cache/package/sqlite', () => {
it('should get undefined', async () => {
const res = await withSqlite((sqlite) => sqlite.get('foo', 'bar'));
expect(res).toBeUndefined();
});

it('should set and get', async () => {
const res = await withSqlite((sqlite) => {
sqlite.set('foo', 'bar', { foo: 'foo' });
sqlite.set('foo', 'bar', { bar: 'bar' });
sqlite.set('foo', 'bar', { baz: 'baz' });
return sqlite.get('foo', 'bar');
});
expect(res).toEqual({ baz: 'baz' });
});
});
91 changes: 91 additions & 0 deletions lib/util/cache/package/sqlite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Sqlite from 'better-sqlite3';
import type { Database, Statement } from 'better-sqlite3';
import * as upath from 'upath';
import { logger } from '../../../logger';
import { ensureDir } from '../../fs';

export class SqlitePackageCache {
private readonly upsertStatement: Statement<unknown[]>;
private readonly getStatement: Statement<unknown[]>;
private readonly cleanupStatement: Statement<unknown[]>;

static async init(cacheDir: string): Promise<SqlitePackageCache> {
const sqliteDir = upath.join(cacheDir, '/renovate/renovate-cache-sqlite');
rarkins marked this conversation as resolved.
Show resolved Hide resolved
await ensureDir(sqliteDir);
const sqliteFile = upath.join(sqliteDir, 'db.sqlite');
const client = new Sqlite(sqliteFile);
const res = new SqlitePackageCache(client);
viceice marked this conversation as resolved.
Show resolved Hide resolved
res.cleanup();
zharinov marked this conversation as resolved.
Show resolved Hide resolved
return res;
}

private constructor(private client: Database) {
client.pragma('journal_mode = WAL');
client.pragma("encoding = 'UTF-8'");

client
.prepare(
`
CREATE TABLE IF NOT EXISTS cache (
namespace TEXT NOT NULL,
key TEXT NOT NULL,
data TEXT NOT NULL,
expiry INTEGER NOT NULL,
PRIMARY KEY (namespace, key)
)
`,
)
.run();
client.prepare('CREATE INDEX IF NOT EXISTS expiry ON cache (expiry)').run();
client
.prepare(
'CREATE INDEX IF NOT EXISTS namespace_key ON cache (namespace, key)',
)
.run();

this.upsertStatement = client.prepare(`
INSERT INTO cache (namespace, key, data, expiry)
VALUES (@namespace, @key, @data, @expiry)
ON CONFLICT (namespace, key) DO UPDATE SET
data = @data,
expiry = @expiry
`);

this.getStatement = client
.prepare(
`
SELECT data FROM cache
WHERE
namespace = @namespace AND key = @key
`,
)
.pluck(true);

this.cleanupStatement = client.prepare(`
DELETE FROM cache
WHERE expiry <= @now
rarkins marked this conversation as resolved.
Show resolved Hide resolved
`);
}

set(namespace: string, key: string, value: unknown, ttlMinutes = 5): void {
const data = JSON.stringify(value);
const expiry = Date.now() + ttlMinutes * 60 * 1000;
this.upsertStatement.run({ namespace, key, data, expiry });
}

get<T = never>(namespace: string, key: string): T | undefined {
const res = this.getStatement.get({ namespace, key }) as string | undefined;
return res ? JSON.parse(res) : undefined;
}

private cleanup(): void {
const now = Date.now();
this.cleanupStatement.run({ now });
const timeMs = Date.now() - now;
rarkins marked this conversation as resolved.
Show resolved Hide resolved
logger.trace(`SQLite cache cleanup: ${timeMs}ms`);
}

close(): void {
this.client.close();
}
}
2 changes: 2 additions & 0 deletions lib/workers/repository/stats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,14 @@ describe('workers/repository/stats', () => {
"count": 4,
"maxMs": 100,
"medianMs": 20,
"totalMs": 160,
},
"set": {
"avgMs": 70,
"count": 3,
"maxMs": 110,
"medianMs": 80,
"totalMs": 210,
},
}
`);
Expand Down
11 changes: 9 additions & 2 deletions lib/workers/repository/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface CacheStats {
avgMs?: number;
medianMs?: number;
maxMs?: number;
totalMs?: number;
}

export function printLookupStats(): void {
Expand Down Expand Up @@ -46,8 +47,11 @@ export function printRequestStats(): void {
},
};
if (packageCacheGets.length) {
packageCacheStats.get.totalMs = Math.round(
packageCacheGets.reduce((a, b) => a + b, 0),
);
packageCacheStats.get.avgMs = Math.round(
packageCacheGets.reduce((a, b) => a + b, 0) / packageCacheGets.length,
packageCacheStats.get.totalMs / packageCacheGets.length,
);
if (packageCacheGets.length > 1) {
packageCacheStats.get.medianMs =
Expand All @@ -57,8 +61,11 @@ export function printRequestStats(): void {
}
}
if (packageCacheSets.length) {
packageCacheStats.set.totalMs = Math.round(
packageCacheSets.reduce((a, b) => a + b, 0),
);
packageCacheStats.set.avgMs = Math.round(
packageCacheSets.reduce((a, b) => a + b, 0) / packageCacheSets.length,
packageCacheStats.set.totalMs / packageCacheSets.length,
);
if (packageCacheSets.length > 1) {
packageCacheStats.set.medianMs =
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
"@renovatebot/pep440": "3.0.17",
"@renovatebot/ruby-semver": "3.0.22",
"@sindresorhus/is": "4.6.0",
"@types/better-sqlite3": "7.6.8",
"@types/ms": "0.7.34",
"@types/tmp": "0.2.6",
"@yarnpkg/core": "4.0.2",
Expand All @@ -177,6 +178,7 @@
"auth-header": "1.0.0",
"aws4": "1.12.0",
"azure-devops-node-api": "12.1.0",
"better-sqlite3": "9.2.2",
"bunyan": "1.8.15",
"cacache": "18.0.2",
"cacheable-lookup": "5.0.4",
Expand Down
Loading