Add configurable concurrent layer downloads (#311)

Adds `maxConcurrentDownloads` parameter to `ImageStore.pull()`.

Performance improvement: ~1.2-1.3x faster pulls for multi-layer images
with higher concurrency.

Signed-off-by: Santosh Bhavani <santosh.bhavani@live.com>
This commit is contained in:
Santosh Bhavani
2025-10-13 22:29:12 -07:00
committed by GitHub
parent e283e023ab
commit 87e8fd1673
2 changed files with 9 additions and 4 deletions
@@ -30,13 +30,15 @@ extension ImageStore {
let contentStore: ContentStore
let progress: ProgressHandler?
let name: String
let maxConcurrentDownloads: Int
public init(name: String, contentStore: ContentStore, client: ContentClient, ingestDir: URL, progress: ProgressHandler? = nil) {
public init(name: String, contentStore: ContentStore, client: ContentClient, ingestDir: URL, progress: ProgressHandler? = nil, maxConcurrentDownloads: Int = 3) {
self.client = client
self.ingestDir = ingestDir
self.contentStore = contentStore
self.progress = progress
self.name = name
self.maxConcurrentDownloads = maxConcurrentDownloads
}
/// Pull the required image layers for the provided descriptor and platform(s) into the given directory using the provided client. Returns a descriptor to the Index manifest.
@@ -120,13 +122,15 @@ extension ImageStore {
private func fetchAll(_ descriptors: [Descriptor]) async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
var iterator = descriptors.makeIterator()
for _ in 0..<8 {
// Start initial batch of concurrent downloads based on maxConcurrentDownloads
for _ in 0..<self.maxConcurrentDownloads {
if let desc = iterator.next() {
group.addTask {
try await self.fetch(desc)
}
}
}
// As tasks complete, add new ones to maintain concurrency
for try await _ in group {
if let desc = iterator.next() {
group.addTask {
@@ -192,7 +192,7 @@ extension ImageStore {
/// - Returns: A `Containerization.Image` object to the newly pulled image.
public func pull(
reference: String, platform: Platform? = nil, insecure: Bool = false,
auth: Authentication? = nil, progress: ProgressHandler? = nil
auth: Authentication? = nil, progress: ProgressHandler? = nil, maxConcurrentDownloads: Int = 3
) async throws -> Image {
let matcher = createPlatformMatcher(for: platform)
@@ -206,7 +206,8 @@ extension ImageStore {
let rootDescriptor = try await client.resolve(name: name, tag: tag)
let (id, tempDir) = try await self.contentStore.newIngestSession()
let operation = ImportOperation(name: name, contentStore: self.contentStore, client: client, ingestDir: tempDir, progress: progress)
let operation = ImportOperation(
name: name, contentStore: self.contentStore, client: client, ingestDir: tempDir, progress: progress, maxConcurrentDownloads: maxConcurrentDownloads)
do {
let index = try await operation.import(root: rootDescriptor, matcher: matcher)
return try await self.lock.withLock { lock in