Skip to content

Commit 231a9fb

Browse files
Improved datastore root and snapshot iteration caching
1 parent 50830f9 commit 231a9fb

File tree

5 files changed

+51
-30
lines changed

5 files changed

+51
-30
lines changed

Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/DatastoreIndex.swift

+11
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ extension DiskPersistence.Datastore.Index {
9898
case .secondary(let index, let manifest): self = .secondary(index: index, manifest: manifest)
9999
}
100100
}
101+
102+
init(_ id: DatastoreRootManifest.IndexManifestID) {
103+
switch id {
104+
case .primary(let manifest):
105+
self = .primary(manifest: manifest)
106+
case .direct(let index, let manifest):
107+
self = .direct(index: index, manifest: manifest)
108+
case .secondary(let index, let manifest):
109+
self = .secondary(index: index, manifest: manifest)
110+
}
111+
}
101112
}
102113
}
103114

Sources/CodableDatastore/Persistence/Disk Persistence/Datastore/PersistenceDatastore.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,16 @@ extension DiskPersistence.Datastore {
217217

218218
extension DiskPersistence.Datastore {
219219
/// Load the root object from disk for the given identifier.
220-
func loadRootObject(for rootIdentifier: DatastoreRootIdentifier) throws -> DatastoreRootManifest {
220+
func loadRootObject(for rootIdentifier: DatastoreRootIdentifier, shouldCache: Bool = true) throws -> DatastoreRootManifest {
221221
let rootObjectURL = rootURL(for: rootIdentifier)
222222

223223
let data = try Data(contentsOf: rootObjectURL)
224224

225225
let root = try JSONDecoder.shared.decode(DatastoreRootManifest.self, from: data)
226226

227-
cachedRootObject = root
227+
if shouldCache {
228+
cachedRootObject = root
229+
}
228230
return root
229231
}
230232

Sources/CodableDatastore/Persistence/Disk Persistence/DiskPersistence.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ extension DiskPersistence {
291291
return snapshot
292292
}
293293

294-
let snapshot = Snapshot(id: snapshotID, persistence: self)
294+
let snapshot = Snapshot(id: snapshotID, persistence: self, isExtendedIterationCacheEnabled: !_transactionRetentionPolicy.isIndefinite)
295295
snapshots[snapshotID] = snapshot
296296

297297
return snapshot

Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/Snapshot.swift

+29-27
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ actor Snapshot<AccessMode: _AccessMode> {
3030
/// A cached instance of the manifest as last loaded from disk.
3131
var cachedManifest: SnapshotManifest?
3232

33-
/// A cached instance of the current iteration as last loaded from disk.
34-
var cachedIteration: SnapshotIteration?
33+
/// Cache for the loaded iterations as last loaded from disk. ``isExtendedIterationCacheEnabled`` controls if multiple iterations are cached or not.
34+
var cachedIterations: [SnapshotIterationIdentifier : SnapshotIteration] = [:]
35+
var isExtendedIterationCacheEnabled: Bool
3536

3637
/// A pointer to the last manifest updater, so updates can be serialized after the last request.
3738
var lastUpdateManifestTask: Task<Sendable, Error>?
@@ -42,11 +43,13 @@ actor Snapshot<AccessMode: _AccessMode> {
4243
init(
4344
id: SnapshotIdentifier,
4445
persistence: DiskPersistence<AccessMode>,
45-
isBackup: Bool = false
46+
isBackup: Bool = false,
47+
isExtendedIterationCacheEnabled: Bool = false
4648
) {
4749
self.id = id
4850
self.persistence = persistence
4951
self.isBackup = isBackup
52+
self.isExtendedIterationCacheEnabled = isExtendedIterationCacheEnabled
5053
}
5154
}
5255

@@ -124,14 +127,25 @@ extension Snapshot {
124127
}
125128
}
126129

130+
func setExtendedIterationCacheEnabled(_ isEnabled: Bool) {
131+
isExtendedIterationCacheEnabled = isEnabled
132+
}
133+
127134
/// Load an iteration from disk, or create a suitable starting value if such a file does not exist.
128-
private func loadIteration(for iterationID: SnapshotIterationIdentifier) throws -> SnapshotIteration {
135+
func loadIteration(for iterationID: SnapshotIterationIdentifier?) async throws -> SnapshotIteration? {
136+
guard let iterationID else { return nil }
137+
if let iteration = cachedIterations[iterationID] {
138+
return iteration
139+
}
129140
do {
130141
let data = try Data(contentsOf: iterationURL(for: iterationID))
131142

132143
let iteration = try JSONDecoder.shared.decode(SnapshotIteration.self, from: data)
133144

134-
cachedIteration = iteration
145+
if !isExtendedIterationCacheEnabled {
146+
cachedIterations.removeAll()
147+
}
148+
cachedIterations[iteration.id] = iteration
135149
return iteration
136150
} catch {
137151
throw error
@@ -155,7 +169,7 @@ extension Snapshot {
155169
cachedManifest = manifest
156170
}
157171

158-
/// Write the specified iteration to the store, and cache the results in ``Snapshot/cachedIteration``.
172+
/// Write the specified iteration to the store, and cache the results in ``Snapshot/cachedIterations``.
159173
private func write(iteration: SnapshotIteration) throws where AccessMode == ReadWrite {
160174
let iterationURL = iterationURL(for: iteration.id)
161175
/// Make sure the directories exists first.
@@ -166,7 +180,10 @@ extension Snapshot {
166180
try data.write(to: iterationURL, options: .atomic)
167181

168182
/// Update the cache since we know what it should be.
169-
cachedIteration = iteration
183+
if !isExtendedIterationCacheEnabled {
184+
cachedIterations.removeAll()
185+
}
186+
cachedIterations[iteration.id] = iteration
170187
}
171188

172189
/// Load and update the manifest in an updater, returning the task for the updater.
@@ -200,26 +217,19 @@ extension Snapshot {
200217

201218
/// Load the manifest so we have a fresh copy, unless we have a cached copy already.
202219
var manifest = try cachedManifest ?? self.loadManifest()
203-
var iteration: SnapshotIteration
204-
if let cachedIteration, cachedIteration.id == manifest.currentIteration {
205-
iteration = cachedIteration
206-
} else if let iterationID = manifest.currentIteration {
207-
iteration = try self.loadIteration(for: iterationID)
208-
} else {
209-
let date = Date()
210-
iteration = SnapshotIteration(id: SnapshotIterationIdentifier(date: date), creationDate: date)
211-
}
220+
let precedingIteration = try await self.loadIteration(for: manifest.currentIteration)
221+
var iteration = precedingIteration ?? SnapshotIteration()
212222

213223
/// Let the updater do something with the manifest, storing the variable on the Task Local stack.
214224
let returnValue = try await SnapshotTaskLocals.$manifest.withValue((manifest, iteration)) {
215225
try await updater(&manifest, &iteration)
216226
}
217227

218228
/// Only write to the store if we changed the manifest for any reason
219-
if iteration.isMeaningfullyChanged(from: cachedIteration) {
229+
if iteration.isMeaningfullyChanged(from: precedingIteration) {
220230
iteration.creationDate = Date()
221231
iteration.id = SnapshotIterationIdentifier(date: iteration.creationDate)
222-
iteration.precedingIteration = cachedIteration?.id
232+
iteration.precedingIteration = precedingIteration?.id
223233

224234
try write(iteration: iteration)
225235
}
@@ -260,15 +270,7 @@ extension Snapshot {
260270

261271
/// Load the manifest so we have a fresh copy, unless we have a cached copy already.
262272
let manifest = try cachedManifest ?? self.loadManifest()
263-
var iteration: SnapshotIteration
264-
if let cachedIteration, cachedIteration.id == manifest.currentIteration {
265-
iteration = cachedIteration
266-
} else if let iterationID = manifest.currentIteration {
267-
iteration = try self.loadIteration(for: iterationID)
268-
} else {
269-
let date = Date()
270-
iteration = SnapshotIteration(id: SnapshotIterationIdentifier(date: date), creationDate: date)
271-
}
273+
let iteration = try await self.loadIteration(for: manifest.currentIteration) ?? SnapshotIteration()
272274

273275
/// Let the accessor do something with the manifest, storing the variable on the Task Local stack.
274276
return try await SnapshotTaskLocals.$manifest.withValue((manifest, iteration)) {

Sources/CodableDatastore/Persistence/Disk Persistence/Snapshot/SnapshotIteration.swift

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ extension SnapshotIteration {
6868
}
6969

7070
extension SnapshotIteration {
71+
/// Initialize a snapshot iteration with a date
72+
/// - Parameter date: The date to base the identifier and creation date off of.
73+
init(date: Date = Date()) {
74+
self.init(id: SnapshotIterationIdentifier(date: date), creationDate: date)
75+
}
76+
7177
/// Internal method to check if an instance should be persisted based on iff it changed significantly from a previous iteration
7278
/// - Parameter existingInstance: The previous iteration to check
7379
/// - Returns: `true` if the iteration should be persisted, `false` if it represents the same data from `existingInstance`.

0 commit comments

Comments
 (0)