Skip to content

Conversation

jabrown85
Copy link
Contributor

@jabrown85 jabrown85 commented Aug 8, 2025

This pull request introduces significant performance optimizations and code improvements to the image saving and layer handling logic in local/store.go, with a focus on supporting containerd storage scenarios more efficiently. The main changes include implementing caching for containerd storage detection, optimizing layer processing by introducing parallelism, and improving how layer sizes are calculated and cached. These updates are especially beneficial for environments using containerd, leading to faster image saves and more efficient resource usage.

Performance optimizations for containerd storage:

  • Added a cached detection mechanism for containerd storage in the Store struct and used it throughout the codebase to avoid redundant checks and improve performance. (containerdStorageCache, usesContainerdStorageCached) [1] [2]
  • Optimized the process of adding image layers to tar files by pre-processing layer metadata in parallel and streaming layer data more efficiently, with special handling for containerd storage to minimize expensive uncompressed size calculations.
  • Improved the AddLayer method to proactively calculate (or defer) the uncompressed size of compressed layers, optimizing for containerd by skipping unnecessary calculations during download and only performing them when required. [1] [2]

Parallelization and concurrency improvements:

  • Introduced parallel processing using errgroup and semaphores in both layer preparation for tar creation and during layer downloads, reducing wall-clock time and making better use of system resources. [1] [2]

API and function signature updates:

  • Updated several internal function signatures (e.g., doSave, addImageToTar) to accept an isContainerdStorage flag, ensuring the optimizations are applied conditionally based on the storage backend. [1] [2] [3] [4]

These changes collectively enhance the efficiency and scalability of image saving and layer management, especially when working with containerd-based Docker setups.

Note: the streaming of the existing layer files is the biggest win here by a large margin

This is an effort to close out buildpacks/pack#2272 and make sure perf is decent on docker's containerd storage option.

Using @edmorley 's slightly modified example in the issue above. This is a subsequent pack build. I've only extracted the Export numbers here as this isn't targeting the other pack inefficiencies.

baseline non-containerd storage current lifecycle:

2025/08/08 10:44:20.176273 [exporter] Saving testcase...
2025/08/08 10:44:20.213789 [exporter] *** Images (6c4091617b09):
2025/08/08 10:44:20.213810 [exporter]       testcase
2025/08/08 10:44:20.213815 [exporter]
2025/08/08 10:44:20.213819 [exporter] *** Image ID: 6c4091617b09fd8c1dcbe214c22785e55497a1b1882c3f454634ee1b6735d99f
2025/08/08 10:44:20.213824 [exporter]
2025/08/08 10:44:20.213827 [exporter] *** Manifest Size: 1087
2025/08/08 10:44:20.213829 [exporter] Timer: Saving testcase... ran for 37.526ms and ended at 2025-08-08T15:44:20Z
2025/08/08 10:44:20.213832 [exporter] Timer: Exporter ran for 66.211458ms and ended at 2025-08-08T15:44:20Z
2025/08/08 10:44:20.213904 [exporter] Timer: Cache started at 2025-08-08T15:44:20Z

containerd storage current lifecycle:

$ pack build --builder testbuilder:24 --pull-policy if-not-present --timestamps --verbose --buildpack heroku/procfile testcase
2025/08/08 10:43:08.903421 [exporter] Saving testcase...
2025/08/08 10:43:17.900220 [exporter] *** Images (e4de4e43ad71):
2025/08/08 10:43:17.900242 [exporter]       testcase
2025/08/08 10:43:17.900246 [exporter]
2025/08/08 10:43:17.900249 [exporter] *** Image ID: e4de4e43ad7143ad0d5c3a4b6e28ff1bca059713c78879e35cee8c3236cde8e0
2025/08/08 10:43:17.900255 [exporter]
2025/08/08 10:43:17.900258 [exporter] *** Manifest Size: 1087
2025/08/08 10:43:17.900260 [exporter] Timer: Saving testcase... ran for 8.996857171s and ended at 2025-08-08T15:43:17Z
2025/08/08 10:43:17.900262 [exporter] Timer: Exporter ran for 9.027226629s and ended at 2025-08-08T15:43:17Z
2025/08/08 10:43:17.900521 [exporter] Timer: Cache started at 2025-08-08T15:43:17Z

containerd storage improved lifecycle:

$ pack build --builder testbuilder:24 --pull-policy if-not-present --timestamps --verbose --buildpack heroku/procfile --lifecycle-image lifecycle:test testcase
2025/08/08 10:40:33.692477 [exporter] Saving testcase...
2025/08/08 10:40:36.632895 [exporter] *** Images (8a71eb3516e7):
2025/08/08 10:40:36.632916 [exporter]       testcase
2025/08/08 10:40:36.632920 [exporter]
2025/08/08 10:40:36.632924 [exporter] *** Image ID: 8a71eb3516e7ee4993c5363bfd719bd57c459d7d801818f60e4a537f3eef30ce
2025/08/08 10:40:36.632927 [exporter]
2025/08/08 10:40:36.632930 [exporter] *** Manifest Size: 1086
2025/08/08 10:40:36.632932 [exporter] Timer: Saving testcase... ran for 2.940171127s and ended at 2025-08-08T15:40:36Z
2025/08/08 10:40:36.632935 [exporter] Timer: Exporter ran for 2.968824459s and ended at 2025-08-08T15:40:36Z
2025/08/08 10:40:36.632938 [exporter] Timer: Cache started at 2025-08-08T15:40:36Z

We are never going to get to pre-containerd perf due to the cheat we were using before. We now have to give the docker daemon layers we were entirely able to skip before. Those layers are also the bigger base layers. We are much closer to docker load <exported> on a clean docker installation though. IME it was about ~1.7s.

- Use compressed layer format for containerd storage
- Added parallelization in layer processing
- Optimized size calculation during tar processing
@jabrown85 jabrown85 requested a review from a team as a code owner August 8, 2025 15:37
@jabrown85 jabrown85 requested a review from Copilot August 8, 2025 15:39
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request introduces significant performance optimizations for containerd storage scenarios in the Docker image save/load functionality. The changes focus on reducing expensive operations and adding parallelization to improve image saving performance, especially when using containerd as the storage backend.

  • Implements cached containerd storage detection to avoid redundant checks
  • Adds parallel processing for layer preparation and downloads using errgroups and semaphores
  • Optimizes layer size calculations by deferring expensive operations for containerd storage

@jabrown85 jabrown85 merged commit d318e60 into main Aug 12, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants