Skip to content

storage/drivers: optimize chowner logic - #1083

Merged
mtrmac merged 1 commit into
podman-container-tools:mainfrom
Luap99:storage-chown
Aug 19, 2026
Merged

storage/drivers: optimize chowner logic#1083
mtrmac merged 1 commit into
podman-container-tools:mainfrom
Luap99:storage-chown

Conversation

@Luap99

@Luap99 Luap99 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Currently the storage-chown-by-maps program is used to chown (possibly large) directory trees of images. In order to preserve hardlinks it stores a map inode -> path which is one entry per inode currently thus could consume a lot of memory.

Since we only care about hard links we should skip storing paths that are known not to have any links, nlink == 1 for files or any directory. This reduces the map size from number of all inodes to number of inodes that have hard links. In practice that should be a lot less files then.

I included some basic benchmarks as well to prove that this helps significantly. To the reduced allocations we also improve the runtime.

Old:

cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
Benchmark_LChown
Benchmark_LChown/1
Benchmark_LChown/1-12     111919             10525 ns/op                 3.000 inodes_count/op      1865 B/op         27 allocs/op
Benchmark_LChown/10
Benchmark_LChown/10-12     49347             24594 ns/op                12.00 inodes_count/op       6718 B/op         79 allocs/op
Benchmark_LChown/100
Benchmark_LChown/100-12                     7455            168185 ns/op               102.0 inodes_count/op       56799 B/op        628 allocs/op
Benchmark_LChown/1000
Benchmark_LChown/1000-12                     664           1740178 ns/op              1005 inodes_count/op        635465 B/op       6099 allocs/op
Benchmark_LChown/10000
Benchmark_LChown/10000-12                     61          19143152 ns/op             10041 inodes_count/op       6390860 B/op      60849 allocs/op
Benchmark_LChown/100000
Benchmark_LChown/100000-12                     6         196209789 ns/op            100392 inodes_count/op      61439924 B/op     608017 allocs/op
PASS
ok      go.podman.io/storage/drivers    8.372s

New:

cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
Benchmark_LChown
Benchmark_LChown/1
Benchmark_LChown/1-12     110064             10335 ns/op                 1.000 inodes_count/op      1865 B/op         27 allocs/op
Benchmark_LChown/10
Benchmark_LChown/10-12     50620             24783 ns/op                 1.000 inodes_count/op      6101 B/op         76 allocs/op
Benchmark_LChown/100
Benchmark_LChown/100-12                     7778            155000 ns/op                 1.000 inodes_count/op     47759 B/op        619 allocs/op
Benchmark_LChown/1000
Benchmark_LChown/1000-12                     823           1485798 ns/op                 1.000 inodes_count/op    474931 B/op       6079 allocs/op
Benchmark_LChown/10000
Benchmark_LChown/10000-12                     67          17650261 ns/op                 1.000 inodes_count/op   5077212 B/op      60767 allocs/op
Benchmark_LChown/100000
Benchmark_LChown/100000-12                     6         170520989 ns/op                 1.000 inodes_count/op  50925166 B/op     607486 allocs/op
PASS
ok      go.podman.io/storage/drivers    8.259s

@github-actions github-actions Bot added the storage Related to "storage" package label Aug 14, 2026
@Luap99

Luap99 commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

cc @giuseppe

@giuseppe giuseppe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@mtrmac mtrmac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about directories and their st_nlink precisely?

At least s/are known to be hard links/could possibly be/ because a directory (if it represents .. physically) will have more links than subdirectories, so every directory with a subdirectory matches the current condition and is added to inodes.

At the other extreme, such a system with physical .. will not have hard links to directories, because the physical .. entry would be ambiguous. So we should never expect the os.Link to succeed, and we should never expect the Walk to give us the same directory inode twice. So we might assume that directories are not links, and not add them to inodes at all.

But then again, there is macOS, which does physically implement hard links to directories (at least used within backups) so it is not an impossible situation.

… and then there is a somewhat-widespread practice of setting directory st_nlink to 1 (e.g. fuse-overlayfs -o static_nlink, reportedly some network filesystems) when unknown and the like.


We clearly want this optimization for regular files. For directories … meh; I think it’s most important to be clear about what the model / assumptions are.

I hope “directories with link count exactly 2 are clearly not hard links; if it is 1 or > 2, we are not entirely sure” should be quite safe.

I am open to an argument that directory hard links should never exist and we don’t need to check them at all; I’m unsure, but at least the macOS code is using a different implementation anyway.

Comment thread storage/drivers/chown_unix.go Outdated
Comment on lines +23 to +24
// inodes to path map, so the chowner can reconstructed hard links
// To safe memory only elements that are known to be a hard links are added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// inodes to path map, so the chowner can reconstructed hard links
// To safe memory only elements that are known to be a hard links are added.
// inodes to path map, so the chowner can reconstruct hard links
// To save memory only elements that are known to be hard links are added.

Comment thread storage/drivers/chown_unix.go Outdated
var normalLinkCount uint64 = 1
if info.IsDir() {
// a regular empty directory has at least two links
normalLinkCount = 2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The code below uses st.Nlink > 1. That shouldn’t be normalLinkCount? See elsewhere.

@Luap99

Luap99 commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

I am open to an argument that directory hard links should never exist and we don’t need to check them at all; I’m unsure, but at least the macOS code is using a different implementation anyway.

I think that is reasonable and the easiest thing to do. Our main targets are linux and freebsd for the storage code here and they both do not support directory hardlinks

https://man.freebsd.org/cgi/man.cgi?query=ln

Directories may not be hardlinked

@packit-as-a-service

Copy link
Copy Markdown

Packit jobs failed. @containers/packit-build please check.

@mtrmac mtrmac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code LGTM. Please update the commit message.

@giuseppe PTAL again.

@giuseppe giuseppe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Currently the storage-chown-by-maps program is used to chown (possibly
large) directory trees of images. In order to preserve hardlinks it
stores a map inode -> path which is one entry per inode currently thus
could consume a lot of memory.

Since we only care about hard links we should skip storing paths that
are known not to have any links, nlink == 1 for files or any directory.
This reduces the map size from number of all inodes to number of inodes
that have hard links. In practice that should be a lot less files then.

I included some basic benchmarks as well to prove that this helps
significantly. To the reduced allocations we also improve the runtime.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
@Luap99

Luap99 commented Aug 19, 2026

Copy link
Copy Markdown
Member Author

Please update the commit message.

I could have sworn I did, but clearly I did not. Fixed.

@packit-as-a-service

Copy link
Copy Markdown

Packit jobs failed. @podman-container-tools/packit-jobs please check.

@mtrmac mtrmac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks!

@mtrmac
mtrmac merged commit 0fd968f into podman-container-tools:main Aug 19, 2026
35 of 36 checks passed
@Luap99
Luap99 deleted the storage-chown branch August 19, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

storage Related to "storage" package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants