Optimize DiskV2 Deduplication (#878)

* Revert "Lowercase `tart.app` (#751)"

This reverts commit a9e2a19015.

* Optimize DiskV2 deduplication logic

In case we cloned `disk.img` from a local image, check if data at offset has the expected contents already.

* Hole punch only if needed

* Calculate hash only if needed

* subdataChunks optimization

* Reapply "Lowercase `tart.app` (#751)"

This reverts commit e74e9c845a.

* format

* Save at least 1GB on deduplication logic

* Build separately

* Revert "subdataChunks optimization"

This reverts commit e59382aeba.

* Another optimization

* Removed debug log

* reformat

* Revert "Hole punch only if needed"

This reverts commit 8c569fc5
This commit is contained in:
Fedor Korotkov
2024-08-05 12:24:31 -04:00
committed by GitHub
parent 33b5cfe2ed
commit ff928ad77d
5 changed files with 44 additions and 17 deletions
+2 -2
View File
@@ -8,11 +8,11 @@ task:
name: dev-mini name: dev-mini
resources: resources:
tart-vms: 1 tart-vms: 1
build_script:
- swift build
test_script: test_script:
- swift test - swift test
integration_test_script: integration_test_script:
# Build Tart
- swift build
- codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/debug/tart - codesign --sign - --entitlements Resources/tart-dev.entitlements --force .build/debug/tart
- export PATH=$(pwd)/.build/arm64-apple-macosx/debug:$PATH - export PATH=$(pwd)/.build/arm64-apple-macosx/debug:$PATH
# Run integration tests # Run integration tests
+23 -6
View File
@@ -1,12 +1,19 @@
import Foundation import Foundation
struct LocalLayerCache { struct LocalLayerCache {
struct DigestInfo {
let range: Range<Data.Index>
let compressedDigest: String
let uncompressedContentDigest: String?
}
let name: String let name: String
let deduplicatedBytes: UInt64 let deduplicatedBytes: UInt64
let diskURL: URL let diskURL: URL
private let mappedDisk: Data private let mappedDisk: Data
private var digestToRange: [String : Range<Data.Index>] = [:] private var digestToRange: [String: DigestInfo] = [:]
private var offsetToRange: [UInt64: DigestInfo] = [:]
init?(_ name: String, _ deduplicatedBytes: UInt64, _ diskURL: URL, _ manifest: OCIManifest) throws { init?(_ name: String, _ deduplicatedBytes: UInt64, _ diskURL: URL, _ manifest: OCIManifest) throws {
self.name = name self.name = name
@@ -24,17 +31,27 @@ struct LocalLayerCache {
return nil return nil
} }
self.digestToRange[layer.digest] = Int(offset)..<Int(offset+uncompressedSize) let info = DigestInfo(
range: Int(offset)..<Int(offset + uncompressedSize),
compressedDigest: layer.digest,
uncompressedContentDigest: layer.uncompressedContentDigest()!
)
self.digestToRange[layer.digest] = info
self.offsetToRange[offset] = info
offset += uncompressedSize offset += uncompressedSize
} }
} }
func find(_ digest: String) -> Data? { func findInfo(digest: String, offsetHint: UInt64) -> DigestInfo? {
guard let foundRange = self.digestToRange[digest] else { // Layers can have the same digests, for example, empty ones. Let's use the offset hint to make a better guess.
return nil if let info = self.offsetToRange[offsetHint], info.compressedDigest == digest {
return info
} }
return self.digestToRange[digest]
}
return self.mappedDisk.subdata(in: foundRange) func subdata(_ range: Range<Data.Index>) -> Data {
return self.mappedDisk.subdata(in: range)
} }
} }
+18 -7
View File
@@ -121,11 +121,14 @@ class DiskV2: Disk {
// Launch a fetching and decompression task // Launch a fetching and decompression task
group.addTask { group.addTask {
// No need to fetch and decompress anything if we've already done so // No need to fetch and decompress anything if we've already done so
if try pullResumed && Digest.hash(diskURL, offset: diskWritingOffset, size: uncompressedLayerSize) == uncompressedLayerContentDigest { if pullResumed {
// Update the progress // do not check hash in the condition above to make it lazy e.g. only do expensive calculations if needed
progress.completedUnitCount += Int64(diskLayer.size) if try Digest.hash(diskURL, offset: diskWritingOffset, size: uncompressedLayerSize) == uncompressedLayerContentDigest {
// Update the progress
progress.completedUnitCount += Int64(diskLayer.size)
return return
}
} }
// Open the disk file for writing // Open the disk file for writing
@@ -140,9 +143,17 @@ class DiskV2: Disk {
} }
// Check if we already have this layer contents in the local layer cache // Check if we already have this layer contents in the local layer cache
if let localLayerCache = localLayerCache, let data = localLayerCache.find(diskLayer.digest), Digest.hash(data) == uncompressedLayerContentDigest { if let localLayerCache = localLayerCache, let localLayerInfo = localLayerCache.findInfo(digest: diskLayer.digest, offsetHint: diskWritingOffset) {
// Fulfil the layer contents from the local blob cache // indicates that the locally cloned disk image has the same content at the given offset
_ = try zeroSkippingWrite(disk, rdisk, fsBlockSize, diskWritingOffset, data) let localHit = localLayerInfo.uncompressedContentDigest == uncompressedLayerContentDigest
&& localLayerInfo.range.lowerBound == diskWritingOffset
// doesn't seem that localHit can ever be false if the localLayerCache is not nil
// but let's just add extra safety here and check it
if !localHit {
// Fulfil the layer contents from the local blob cache
let data = localLayerCache.subdata(localLayerInfo.range)
_ = try zeroSkippingWrite(disk, rdisk, fsBlockSize, diskWritingOffset, data)
}
try disk.close() try disk.close()
// Update the progress // Update the progress
-1
View File
@@ -17,7 +17,6 @@ extension URL: Prunable {
func deduplicatedSizeBytes() throws -> Int { func deduplicatedSizeBytes() throws -> Int {
let values = try resourceValues(forKeys: [.totalFileAllocatedSizeKey, .mayShareFileContentKey]) let values = try resourceValues(forKeys: [.totalFileAllocatedSizeKey, .mayShareFileContentKey])
// make sure the file's origin file is there and duplication works // make sure the file's origin file is there and duplication works
var dedublicatedSize = 0
if values.mayShareFileContent == true { if values.mayShareFileContent == true {
return Int(deduplicatedBytes()) return Int(deduplicatedBytes())
} }
+1 -1
View File
@@ -297,7 +297,7 @@ class VMStorageOCI: PrunableStorage {
// Now, find the best match based on how many bytes we'll deduplicate // Now, find the best match based on how many bytes we'll deduplicate
let choosen = candidates.filter { let choosen = candidates.filter {
$0.deduplicatedBytes > 0 $0.deduplicatedBytes > 1024 * 1024 * 1024 // save at least 1GB
}.max { left, right in }.max { left, right in
return left.deduplicatedBytes < right.deduplicatedBytes return left.deduplicatedBytes < right.deduplicatedBytes
} }