mirror of
https://github.com/cirruslabs/tart.git
synced 2026-09-30 03:01:12 +02:00
Optimize DiskV2 Deduplication (#878)
* Revert "Lowercase `tart.app` (#751)" This reverts commita9e2a19015. * 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 commite74e9c845a. * format * Save at least 1GB on deduplication logic * Build separately * Revert "subdataChunks optimization" This reverts commite59382aeba. * Another optimization * Removed debug log * reformat * Revert "Hole punch only if needed" This reverts commit8c569fc5
This commit is contained in:
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user