Skip to content

Commit fe49308

Browse files
authored
feat(core): provide default value for max cache size (#30351)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior The max cache size is disabled by default ## Expected Behavior Max cache size is set to 10% of the current disk by default, and can be disabled by specifying 0. Information about this shows up in `nx report`. <img width="331" alt="image" src="https://github.com/user-attachments/assets/ee937101-9915-49d1-b3f1-c2f0d929c140" /> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent cc9e993 commit fe49308

File tree

8 files changed

+256
-47
lines changed

8 files changed

+256
-47
lines changed

Cargo.lock

Lines changed: 85 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/nx/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ swc_common = "0.31.16"
4343
swc_ecma_parser = { version = "0.137.1", features = ["typescript"] }
4444
swc_ecma_visit = "0.93.0"
4545
swc_ecma_ast = "0.107.0"
46+
sysinfo = "0.33.1"
4647
rand = "0.9.0"
4748
[target.'cfg(windows)'.dependencies]
4849
winapi = { version = "0.3", features = ["fileapi"] }

packages/nx/src/command-line/report/report.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ import {
2929
createNxKeyLicenseeInformation,
3030
} from '../../utils/nx-key';
3131
import { type NxKey } from '@nx/key';
32+
import {
33+
DbCache,
34+
dbCacheEnabled,
35+
formatCacheSize,
36+
parseMaxCacheSize,
37+
} from '../../tasks-runner/cache';
38+
import { getDefaultMaxCacheSize } from '../../native';
39+
import { cacheDir } from '../../utils/cache-directory';
3240

3341
const nxPackageJson = readJsonFile<typeof import('../../../package.json')>(
3442
join(__dirname, '../../../package.json')
@@ -75,6 +83,7 @@ export async function reportHandler() {
7583
outOfSyncPackageGroup,
7684
projectGraphError,
7785
nativeTarget,
86+
cache,
7887
} = await getReportData();
7988

8089
const fields = [
@@ -191,6 +200,15 @@ export async function reportHandler() {
191200
}
192201
}
193202

203+
if (cache) {
204+
bodyLines.push(LINE_SEPARATOR);
205+
bodyLines.push(
206+
`Cache Usage: ${formatCacheSize(cache.used)} / ${
207+
cache.max === 0 ? '∞' : formatCacheSize(cache.max)
208+
}`
209+
);
210+
}
211+
194212
if (outOfSyncPackageGroup) {
195213
bodyLines.push(LINE_SEPARATOR);
196214
bodyLines.push(
@@ -241,6 +259,10 @@ export interface ReportData {
241259
};
242260
projectGraphError?: Error | null;
243261
nativeTarget: string | null;
262+
cache: {
263+
max: number;
264+
used: number;
265+
} | null;
244266
}
245267

246268
export async function getReportData(): Promise<ReportData> {
@@ -281,6 +303,16 @@ export async function getReportData(): Promise<ReportData> {
281303
}
282304
}
283305

306+
let cache = dbCacheEnabled(nxJson)
307+
? {
308+
max:
309+
nxJson.maxCacheSize !== undefined
310+
? parseMaxCacheSize(nxJson.maxCacheSize)
311+
: getDefaultMaxCacheSize(cacheDir),
312+
used: new DbCache({ nxCloudRemoteCache: null }).getUsedCacheSpace(),
313+
}
314+
: null;
315+
284316
return {
285317
pm,
286318
nxKey,
@@ -294,6 +326,7 @@ export async function getReportData(): Promise<ReportData> {
294326
outOfSyncPackageGroup,
295327
projectGraphError,
296328
nativeTarget: native ? native.getBinaryTarget() : null,
329+
cache,
297330
};
298331
}
299332

packages/nx/src/native/cache/cache.rs

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use fs_extra::remove_items;
66
use napi::bindgen_prelude::*;
77
use regex::Regex;
88
use rusqlite::params;
9+
use sysinfo::Disks;
910
use tracing::trace;
1011

1112
use crate::native::cache::expand_outputs::_expand_outputs;
@@ -29,7 +30,7 @@ pub struct NxCache {
2930
cache_path: PathBuf,
3031
db: External<NxDbConnection>,
3132
link_task_details: bool,
32-
max_cache_size: Option<i64>,
33+
max_cache_size: i64,
3334
}
3435

3536
#[napi]
@@ -47,6 +48,8 @@ impl NxCache {
4748
create_dir_all(&cache_path)?;
4849
create_dir_all(cache_path.join("terminalOutputs"))?;
4950

51+
let max_cache_size = max_cache_size.unwrap_or(0);
52+
5053
let r = Self {
5154
db: db_connection,
5255
workspace_root: PathBuf::from(workspace_root),
@@ -207,48 +210,63 @@ impl NxCache {
207210
"INSERT OR REPLACE INTO cache_outputs (hash, code, size) VALUES (?1, ?2, ?3)",
208211
params![hash, code, size],
209212
)?;
210-
if self.max_cache_size.is_some() {
213+
if self.max_cache_size != 0 {
211214
self.ensure_cache_size_within_limit()?
212215
}
213216
Ok(())
214217
}
215218

219+
#[napi]
220+
pub fn get_cache_size(&self) -> anyhow::Result<i64> {
221+
self.db
222+
.query_row("SELECT SUM(size) FROM cache_outputs", [], |row| {
223+
row.get::<_, Option<i64>>(0)
224+
// If there are no cache entries, the result is
225+
// a single row with a NULL value. This would look like:
226+
// Ok(None). We need to convert this to Ok(0).
227+
.transpose()
228+
.unwrap_or(Ok(0))
229+
})
230+
// The query_row returns an Result<Option<T>> to account for
231+
// a query that returned no rows. This isn't possible when using
232+
// SUM, so we can safely unwrap the Option, but need to transpose
233+
// to access it. The result represents a db error or mapping error.
234+
.transpose()
235+
.unwrap_or(Ok(0))
236+
}
237+
216238
fn ensure_cache_size_within_limit(&self) -> anyhow::Result<()> {
217-
if let Some(user_specified_max_cache_size) = self.max_cache_size {
218-
let buffer_amount = (0.1 * user_specified_max_cache_size as f64) as i64;
219-
let target_cache_size = user_specified_max_cache_size - buffer_amount;
220-
221-
let full_cache_size = self
222-
.db
223-
.query_row("SELECT SUM(size) FROM cache_outputs", [], |row| {
224-
row.get::<_, i64>(0)
225-
})?
226-
.unwrap_or(0);
227-
if user_specified_max_cache_size < full_cache_size {
228-
let mut cache_size = full_cache_size;
229-
let mut stmt = self.db.prepare(
230-
"SELECT hash, size FROM cache_outputs ORDER BY accessed_at ASC LIMIT 100",
231-
)?;
232-
'outer: while cache_size > target_cache_size {
233-
let rows = stmt.query_map([], |r| {
234-
let hash: String = r.get(0)?;
235-
let size: i64 = r.get(1)?;
236-
Ok((hash, size))
237-
})?;
238-
for row in rows {
239-
if let Ok((hash, size)) = row {
240-
cache_size -= size;
241-
self.db.execute(
242-
"DELETE FROM cache_outputs WHERE hash = ?1",
243-
params![hash],
244-
)?;
245-
remove_items(&[self.cache_path.join(&hash)])?;
246-
}
247-
// We've deleted enough cache entries to be under the
248-
// target cache size, stop looking for more.
249-
if cache_size < target_cache_size {
250-
break 'outer;
251-
}
239+
// 0 is equivalent to being unlimited.
240+
if self.max_cache_size == 0 {
241+
return Ok(());
242+
}
243+
let user_specified_max_cache_size = self.max_cache_size;
244+
let buffer_amount = (0.1 * user_specified_max_cache_size as f64) as i64;
245+
let target_cache_size = user_specified_max_cache_size - buffer_amount;
246+
247+
let full_cache_size = self.get_cache_size()?;
248+
if user_specified_max_cache_size < full_cache_size {
249+
let mut cache_size = full_cache_size;
250+
let mut stmt = self.db.prepare(
251+
"SELECT hash, size FROM cache_outputs ORDER BY accessed_at ASC LIMIT 100",
252+
)?;
253+
'outer: while cache_size > target_cache_size {
254+
let rows = stmt.query_map([], |r| {
255+
let hash: String = r.get(0)?;
256+
let size: i64 = r.get(1)?;
257+
Ok((hash, size))
258+
})?;
259+
for row in rows {
260+
if let Ok((hash, size)) = row {
261+
cache_size -= size;
262+
self.db
263+
.execute("DELETE FROM cache_outputs WHERE hash = ?1", params![hash])?;
264+
remove_items(&[self.cache_path.join(&hash)])?;
265+
}
266+
// We've deleted enough cache entries to be under the
267+
// target cache size, stop looking for more.
268+
if cache_size < target_cache_size {
269+
break 'outer;
252270
}
253271
}
254272
}
@@ -346,6 +364,21 @@ impl NxCache {
346364
}
347365
}
348366

367+
#[napi]
368+
fn get_default_max_cache_size(cache_path: String) -> i64 {
369+
let disks = Disks::new_with_refreshed_list();
370+
let cache_path = PathBuf::from(cache_path);
371+
372+
for disk in disks.list() {
373+
if cache_path.starts_with(disk.mount_point()) {
374+
return (disk.total_space() as f64 * 0.1) as i64;
375+
}
376+
}
377+
378+
// Default to 100gb
379+
100 * 1024 * 1024 * 1024
380+
}
381+
349382
fn try_and_retry<T, F>(mut f: F) -> anyhow::Result<T>
350383
where
351384
F: FnMut() -> anyhow::Result<T>,

packages/nx/src/native/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export declare class NxCache {
4242
put(hash: string, terminalOutput: string, outputs: Array<string>, code: number): void
4343
applyRemoteCacheResults(hash: string, result: CachedResult, outputs: Array<string>): void
4444
getTaskOutputsPath(hash: string): string
45+
getCacheSize(): number
4546
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): number
4647
removeOldCacheRecords(): void
4748
checkCacheFsInSync(): boolean
@@ -166,6 +167,8 @@ export declare export function findImports(projectFileMap: Record<string, Array<
166167

167168
export declare export function getBinaryTarget(): string
168169

170+
export declare export function getDefaultMaxCacheSize(cachePath: string): number
171+
169172
/**
170173
* Expands the given outputs into a list of existing files.
171174
* This is used when hashing outputs

packages/nx/src/native/native-bindings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ module.exports.EventType = nativeBinding.EventType
379379
module.exports.expandOutputs = nativeBinding.expandOutputs
380380
module.exports.findImports = nativeBinding.findImports
381381
module.exports.getBinaryTarget = nativeBinding.getBinaryTarget
382+
module.exports.getDefaultMaxCacheSize = nativeBinding.getDefaultMaxCacheSize
382383
module.exports.getFilesForOutputs = nativeBinding.getFilesForOutputs
383384
module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs
384385
module.exports.hashArray = nativeBinding.hashArray

0 commit comments

Comments
 (0)