Skip to content

Clone files into the disk cache where the filesystem supports it - #30776

Open
ladd wants to merge 1 commit into
bazelbuild:masterfrom
ladd:disk-cache-cow-upload
Open

Clone files into the disk cache where the filesystem supports it#30776
ladd wants to merge 1 commit into
bazelbuild:masterfrom
ladd:disk-cache-cow-upload

Conversation

@ladd

@ladd ladd commented Aug 18, 2026

Copy link
Copy Markdown

Description

  • DiskCacheClient.uploadFile now copies through FileSystemUtils#copyFile, which reaches Files#copy and lets the filesystem serve the copy as a copy-on-write clone: clonefile on macOS, copy_file_range on Linux with a supporting filesystem. The cache entry then shares its blocks with the output it came from. The previous code streamed the bytes into the temporary file.
  • Where no clone is possible (different filesystem, or no kernel support), Files#copy falls back to a byte copy, so this is never slower than the stream it replaces.
  • The temporary-file-then-rename scaffolding moved into a private save() helper, shared by the stream path and the new file path.
  • A clone inherits the source's permissions and mtime, so both are reset: outputs are typically 0555 and can be older than the upload, an entry has to stay readable for every user of a shared cache, and the garbage collector reads the mtime to find the least recently used entries.
  • The cloned temporary file is fsynced before the rename, keeping the durability the streamed write had.

Motivation

Materializing outputs from the disk cache already clones, since ec90e05 ("Optimize file copies by using NIO methods"). The upload direction still wrote a second physical copy of a file that was already on disk.

Measured on macOS/APFS: one genrule producing a 1 GiB output, fresh disk cache, real disk consumed via df.

before after
1 GiB output plus its cache entry 2058 MiB 1028 MiB

Physical extent mapping (fcntl(F_LOG2PHYS_EXT)) on the same pair agrees: 0/3 sampled extents shared before, 3/3 after. On a large iOS app build, the disk cache held 13.1 GiB duplicating content that was also present in the output tree.

Two consequences worth knowing:

  • An entry sharing blocks with an output is no longer freed by deleting either one alone.
  • DiskCacheGarbageCollector sizes entries by their logical length, so it over-counts what deletion reclaims.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable).

Release Notes

RELNOTES: Files uploaded to the disk cache are now copied with a copy-on-write clone where the filesystem supports it, so a cache entry can share its blocks with the output it was uploaded from.

@google-cla

google-cla Bot commented Aug 18, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@ladd
ladd force-pushed the disk-cache-cow-upload branch 2 times, most recently from 4f477a8 to 0e3a102 Compare August 18, 2026 20:42
}

/** Flushes a file's contents to stable storage, where the filesystem supports it. */
private static void syncFile(Path path) throws IOException {

@fmeum fmeum Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please use the same mechanism as in the fallback path (e.g. FileInputStream).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Something like the revised version? I'm not entirely clear on the importance of the sync semantics here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Me neither, which is why I like this better since the semantics agree ;⁠-⁠)

Uploading a file to the disk cache streamed its bytes into a temporary file,
so the cache entry became a second physical copy of an output that was already
on disk. Copy through FileSystemUtils#copyFile instead: it reaches Files#copy,
which lets the filesystem serve the copy as a copy-on-write clone (clonefile
on macOS, copy_file_range on Linux with a supporting filesystem), and the entry
then shares its blocks with the output it came from. Where no clone is possible
-- a different filesystem, or one without support -- Files#copy falls back to a
byte copy, so this is never slower than the stream it replaces.

The materialization direction already clones, since commit ec90e05
("Optimize file copies by using NIO methods"). Measured on macOS/APFS with a
large iOS app: materializing bazel-out from a warm disk cache costs 200 MiB of
real disk for a 2.07 GiB tree, while a locally built app leaves a 13.1 GiB
second copy in the cache. This change removes that copy.

A clone inherits the source's permissions and mtime, so both are reset: an
entry must stay readable for every user of a shared cache, and the mtime is
what the garbage collector reads to find the least recently used entries. The
cloned temporary file is fsynced before the rename, keeping the durability the
streamed write had.

Note that a cache entry sharing blocks with an output is no longer freed by
deleting either one alone, and that the garbage collector sizes entries by
their logical length, so it now over-counts what deletion will reclaim.
@ladd
ladd force-pushed the disk-cache-cow-upload branch from 0e3a102 to 4c41c03 Compare August 19, 2026 20:09
@ladd
ladd marked this pull request as ready for review August 19, 2026 20:17
@ladd
ladd requested a review from a team as a code owner August 19, 2026 20:17
@github-actions github-actions Bot added team-Remote-Exec Issues and PRs for the Execution (Remote) team awaiting-review PR is awaiting review from an assigned reviewer labels Aug 19, 2026
@fmeum
fmeum requested a review from tjgq August 19, 2026 20:27
@fmeum

fmeum commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

@bazel-io fork 9.3.0

@bazel-docs-pr-commenter

Copy link
Copy Markdown

✅ Bazel docs preview is ready!

Preview URL: https://bazel-pr-30776.mintlify.app/

Updated for 4c41c039e014271c51eed22f0fd75f488505088f

@github-actions github-actions Bot added the community-reviewed Reviewed by a trusted community contributor label Aug 19, 2026
@tjgq tjgq added awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally and removed awaiting-review PR is awaiting review from an assigned reviewer labels Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally community-reviewed Reviewed by a trusted community contributor team-Remote-Exec Issues and PRs for the Execution (Remote) team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants