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(core): provide default value for max cache size #30351

Merged
merged 1 commit into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
87 changes: 85 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/nx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ swc_common = "0.31.16"
swc_ecma_parser = { version = "0.137.1", features = ["typescript"] }
swc_ecma_visit = "0.93.0"
swc_ecma_ast = "0.107.0"
sysinfo = "0.33.1"
rand = "0.9.0"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["fileapi"] }
Expand Down
33 changes: 33 additions & 0 deletions packages/nx/src/command-line/report/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import {
createNxKeyLicenseeInformation,
} from '../../utils/nx-key';
import { type NxKey } from '@nx/key';
import {
DbCache,
dbCacheEnabled,
formatCacheSize,
parseMaxCacheSize,
} from '../../tasks-runner/cache';
import { getDefaultMaxCacheSize } from '../../native';
import { cacheDir } from '../../utils/cache-directory';

const nxPackageJson = readJsonFile<typeof import('../../../package.json')>(
join(__dirname, '../../../package.json')
Expand Down Expand Up @@ -75,6 +83,7 @@ export async function reportHandler() {
outOfSyncPackageGroup,
projectGraphError,
nativeTarget,
cache,
} = await getReportData();

const fields = [
Expand Down Expand Up @@ -191,6 +200,15 @@ export async function reportHandler() {
}
}

if (cache) {
bodyLines.push(LINE_SEPARATOR);
bodyLines.push(
`Cache Usage: ${formatCacheSize(cache.used)} / ${
cache.max === 0 ? '∞' : formatCacheSize(cache.max)
}`
);
}

if (outOfSyncPackageGroup) {
bodyLines.push(LINE_SEPARATOR);
bodyLines.push(
Expand Down Expand Up @@ -241,6 +259,10 @@ export interface ReportData {
};
projectGraphError?: Error | null;
nativeTarget: string | null;
cache: {
max: number;
used: number;
} | null;
}

export async function getReportData(): Promise<ReportData> {
Expand Down Expand Up @@ -281,6 +303,16 @@ export async function getReportData(): Promise<ReportData> {
}
}

let cache = dbCacheEnabled(nxJson)
? {
max:
nxJson.maxCacheSize !== undefined
? parseMaxCacheSize(nxJson.maxCacheSize)
: getDefaultMaxCacheSize(cacheDir),
used: new DbCache({ nxCloudRemoteCache: null }).getUsedCacheSpace(),
}
: null;

return {
pm,
nxKey,
Expand All @@ -294,6 +326,7 @@ export async function getReportData(): Promise<ReportData> {
outOfSyncPackageGroup,
projectGraphError,
nativeTarget: native ? native.getBinaryTarget() : null,
cache,
};
}

Expand Down
107 changes: 70 additions & 37 deletions packages/nx/src/native/cache/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use fs_extra::remove_items;
use napi::bindgen_prelude::*;
use regex::Regex;
use rusqlite::params;
use sysinfo::Disks;
use tracing::trace;

use crate::native::cache::expand_outputs::_expand_outputs;
Expand All @@ -29,7 +30,7 @@ pub struct NxCache {
cache_path: PathBuf,
db: External<NxDbConnection>,
link_task_details: bool,
max_cache_size: Option<i64>,
max_cache_size: i64,
}

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

let max_cache_size = max_cache_size.unwrap_or(0);

let r = Self {
db: db_connection,
workspace_root: PathBuf::from(workspace_root),
Expand Down Expand Up @@ -207,48 +210,63 @@ impl NxCache {
"INSERT OR REPLACE INTO cache_outputs (hash, code, size) VALUES (?1, ?2, ?3)",
params![hash, code, size],
)?;
if self.max_cache_size.is_some() {
if self.max_cache_size != 0 {
self.ensure_cache_size_within_limit()?
}
Ok(())
}

#[napi]
pub fn get_cache_size(&self) -> anyhow::Result<i64> {
self.db
.query_row("SELECT SUM(size) FROM cache_outputs", [], |row| {
row.get::<_, Option<i64>>(0)
// If there are no cache entries, the result is
// a single row with a NULL value. This would look like:
// Ok(None). We need to convert this to Ok(0).
.transpose()
.unwrap_or(Ok(0))
})
// The query_row returns an Result<Option<T>> to account for
// a query that returned no rows. This isn't possible when using
// SUM, so we can safely unwrap the Option, but need to transpose
// to access it. The result represents a db error or mapping error.
.transpose()
.unwrap_or(Ok(0))
}

fn ensure_cache_size_within_limit(&self) -> anyhow::Result<()> {
if let Some(user_specified_max_cache_size) = self.max_cache_size {
let buffer_amount = (0.1 * user_specified_max_cache_size as f64) as i64;
let target_cache_size = user_specified_max_cache_size - buffer_amount;

let full_cache_size = self
.db
.query_row("SELECT SUM(size) FROM cache_outputs", [], |row| {
row.get::<_, i64>(0)
})?
.unwrap_or(0);
if user_specified_max_cache_size < full_cache_size {
let mut cache_size = full_cache_size;
let mut stmt = self.db.prepare(
"SELECT hash, size FROM cache_outputs ORDER BY accessed_at ASC LIMIT 100",
)?;
'outer: while cache_size > target_cache_size {
let rows = stmt.query_map([], |r| {
let hash: String = r.get(0)?;
let size: i64 = r.get(1)?;
Ok((hash, size))
})?;
for row in rows {
if let Ok((hash, size)) = row {
cache_size -= size;
self.db.execute(
"DELETE FROM cache_outputs WHERE hash = ?1",
params![hash],
)?;
remove_items(&[self.cache_path.join(&hash)])?;
}
// We've deleted enough cache entries to be under the
// target cache size, stop looking for more.
if cache_size < target_cache_size {
break 'outer;
}
// 0 is equivalent to being unlimited.
if self.max_cache_size == 0 {
return Ok(());
}
let user_specified_max_cache_size = self.max_cache_size;
let buffer_amount = (0.1 * user_specified_max_cache_size as f64) as i64;
let target_cache_size = user_specified_max_cache_size - buffer_amount;

let full_cache_size = self.get_cache_size()?;
if user_specified_max_cache_size < full_cache_size {
let mut cache_size = full_cache_size;
let mut stmt = self.db.prepare(
"SELECT hash, size FROM cache_outputs ORDER BY accessed_at ASC LIMIT 100",
)?;
'outer: while cache_size > target_cache_size {
let rows = stmt.query_map([], |r| {
let hash: String = r.get(0)?;
let size: i64 = r.get(1)?;
Ok((hash, size))
})?;
for row in rows {
if let Ok((hash, size)) = row {
cache_size -= size;
self.db
.execute("DELETE FROM cache_outputs WHERE hash = ?1", params![hash])?;
remove_items(&[self.cache_path.join(&hash)])?;
}
// We've deleted enough cache entries to be under the
// target cache size, stop looking for more.
if cache_size < target_cache_size {
break 'outer;
}
}
}
Expand Down Expand Up @@ -346,6 +364,21 @@ impl NxCache {
}
}

#[napi]
fn get_default_max_cache_size(cache_path: String) -> i64 {
let disks = Disks::new_with_refreshed_list();
let cache_path = PathBuf::from(cache_path);

for disk in disks.list() {
if cache_path.starts_with(disk.mount_point()) {
return (disk.total_space() as f64 * 0.1) as i64;
}
}

// Default to 100gb
100 * 1024 * 1024 * 1024
}

fn try_and_retry<T, F>(mut f: F) -> anyhow::Result<T>
where
F: FnMut() -> anyhow::Result<T>,
Expand Down
3 changes: 3 additions & 0 deletions packages/nx/src/native/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export declare class NxCache {
put(hash: string, terminalOutput: string, outputs: Array<string>, code: number): void
applyRemoteCacheResults(hash: string, result: CachedResult, outputs: Array<string>): void
getTaskOutputsPath(hash: string): string
getCacheSize(): number
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): number
removeOldCacheRecords(): void
checkCacheFsInSync(): boolean
Expand Down Expand Up @@ -166,6 +167,8 @@ export declare export function findImports(projectFileMap: Record<string, Array<

export declare export function getBinaryTarget(): string

export declare export function getDefaultMaxCacheSize(cachePath: string): number

/**
* Expands the given outputs into a list of existing files.
* This is used when hashing outputs
Expand Down
1 change: 1 addition & 0 deletions packages/nx/src/native/native-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ module.exports.EventType = nativeBinding.EventType
module.exports.expandOutputs = nativeBinding.expandOutputs
module.exports.findImports = nativeBinding.findImports
module.exports.getBinaryTarget = nativeBinding.getBinaryTarget
module.exports.getDefaultMaxCacheSize = nativeBinding.getDefaultMaxCacheSize
module.exports.getFilesForOutputs = nativeBinding.getFilesForOutputs
module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs
module.exports.hashArray = nativeBinding.hashArray
Expand Down
Loading