Skip to content

Commit b11f273

Browse files
authored
[Collections] SQLite optimizations (#3437)
Motivation: Add/delete/refresh package collections lead to frequent deletes in SQLite. This causes the database file size to grow very quickly when we have a huge collection. Modification: - Run `VACUUM` after SQLite delete. This repacks the database file and helps reduce its size. - Don't update FTS tables unless the collection's packages have changed. This helps reduce the number of deletes. rdar://77077510
1 parent 950e77d commit b11f273

File tree

1 file changed

+39
-20
lines changed

1 file changed

+39
-20
lines changed

Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -126,28 +126,38 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable
126126
func put(collection: Model.Collection,
127127
callback: @escaping (Result<Model.Collection, Error>) -> Void) {
128128
DispatchQueue.sharedConcurrent.async {
129-
do {
130-
// write to db
131-
let query = "INSERT OR REPLACE INTO \(Self.packageCollectionsTableName) VALUES (?, ?);"
132-
try self.executeStatement(query) { statement -> Void in
133-
let data = try self.encoder.encode(collection)
134-
135-
let bindings: [SQLite.SQLiteValue] = [
136-
.string(collection.identifier.databaseKey()),
137-
.blob(data),
138-
]
139-
try statement.bind(bindings)
140-
try statement.step()
141-
}
129+
self.get(identifier: collection.identifier) { getResult in
130+
do {
131+
// write to db
132+
let query = "INSERT OR REPLACE INTO \(Self.packageCollectionsTableName) VALUES (?, ?);"
133+
try self.executeStatement(query) { statement -> Void in
134+
let data = try self.encoder.encode(collection)
135+
136+
let bindings: [SQLite.SQLiteValue] = [
137+
.string(collection.identifier.databaseKey()),
138+
.blob(data),
139+
]
140+
try statement.bind(bindings)
141+
try statement.step()
142+
}
142143

143-
// Add to search indices
144-
try self.insertToSearchIndices(collection: collection)
144+
// Add to search indices
145+
// Optimization: do this only if the collection has not been indexed before or its packages have changed
146+
switch getResult {
147+
case .failure: // e.g., not found
148+
try self.insertToSearchIndices(collection: collection)
149+
case .success(let dbCollection) where dbCollection.packages != collection.packages:
150+
try self.insertToSearchIndices(collection: collection)
151+
default: // dbCollection.packages == collection.packages
152+
break
153+
}
145154

146-
// write to cache
147-
self.cache[collection.identifier] = collection
148-
callback(.success(collection))
149-
} catch {
150-
callback(.failure(error))
155+
// write to cache
156+
self.cache[collection.identifier] = collection
157+
callback(.success(collection))
158+
} catch {
159+
callback(.failure(error))
160+
}
151161
}
152162
}
153163
}
@@ -680,6 +690,15 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable
680690
try statement.step()
681691
}
682692

693+
// Repack database file to reduce size (rdar://77077510)
694+
try self.withDB { db in
695+
do {
696+
try db.exec(query: "VACUUM;")
697+
} catch {
698+
self.diagnosticsEngine?.emit(warning: "Failed to 'VACUUM' the database: \(error)")
699+
}
700+
}
701+
683702
self.targetTrie.remove { $0.collection == identifier }
684703
}
685704

0 commit comments

Comments
 (0)