Просмотр исходного кода

Prefetch pump event relationships to eliminate N+1 queries

Every pump-event consumer faulted bolus and tempBasal rows one at a
time, amplified by hot paths like determineBasal and the home-screen
insulin array. Add prefetching at each fetch site so relationships
arrive in a single IN-query.

PumpHistoryStorage: pass relationshipKeyPathsForPrefetching for all
getPumpHistory* readers.

OpenAPS.fetchPumpHistoryObjectIDs: prefetch bolus/tempBasal so the
subsequent loadAndMapPumpEvents DTO mapping reads from the row cache.

APSManager.fetchCurrentTempBasal, AppleWatchManager.fetchLastBolus,
GarminManager: prefetch the relationship each caller actually reads.

HomeStateModel setupInsulinArray / updateDeterminationsArray: add a
SELF IN prefetch hop on viewContext before the cross-context
getNSManagedObject materialization, since prefetching on the
background context does not carry over.
Marvin Polscheit 1 месяц назад
Родитель
Сommit
370c5e269b

+ 2 - 1
Trio/Sources/APS/APSManager.swift

@@ -677,7 +677,8 @@ final class BaseAPSManager: APSManager, Injectable {
             predicate: NSPredicate.recentPumpHistory,
             predicate: NSPredicate.recentPumpHistory,
             key: "timestamp",
             key: "timestamp",
             ascending: false,
             ascending: false,
-            fetchLimit: 1
+            fetchLimit: 1,
+            relationshipKeyPathsForPrefetching: ["tempBasal"]
         )
         )
 
 
         let fetchedTempBasal = await context.perform {
         let fetchedTempBasal = await context.perform {

+ 2 - 1
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -223,7 +223,8 @@ final class OpenAPS {
             predicate: NSPredicate.pumpHistoryLast1440Minutes,
             predicate: NSPredicate.pumpHistoryLast1440Minutes,
             key: "timestamp",
             key: "timestamp",
             ascending: false,
             ascending: false,
-            batchSize: 50
+            batchSize: 50,
+            relationshipKeyPathsForPrefetching: ["bolus", "tempBasal"]
         )
         )
 
 
         return try await context.perform {
         return try await context.perform {

+ 8 - 4
Trio/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -266,7 +266,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
             predicate: NSPredicate.pumpHistoryLast24h,
             predicate: NSPredicate.pumpHistoryLast24h,
             key: "timestamp",
             key: "timestamp",
             ascending: false,
             ascending: false,
-            fetchLimit: 288
+            fetchLimit: 288,
+            relationshipKeyPathsForPrefetching: ["bolus", "tempBasal"]
         )
         )
 
 
         return await context.perform {
         return await context.perform {
@@ -317,7 +318,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToNightscout,
             predicate: NSPredicate.pumpEventsNotYetUploadedToNightscout,
             key: "timestamp",
             key: "timestamp",
-            ascending: false
+            ascending: false,
+            relationshipKeyPathsForPrefetching: ["bolus", "tempBasal"]
         )
         )
 
 
         return try await context.perform { [self] in
         return try await context.perform { [self] in
@@ -480,7 +482,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToHealth,
             predicate: NSPredicate.pumpEventsNotYetUploadedToHealth,
             key: "timestamp",
             key: "timestamp",
-            ascending: false
+            ascending: false,
+            relationshipKeyPathsForPrefetching: ["bolus", "tempBasal"]
         )
         )
 
 
         return try await context.perform {
         return try await context.perform {
@@ -526,7 +529,8 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
             onContext: context,
             onContext: context,
             predicate: NSPredicate.pumpEventsNotYetUploadedToTidepool,
             predicate: NSPredicate.pumpEventsNotYetUploadedToTidepool,
             key: "timestamp",
             key: "timestamp",
-            ascending: false
+            ascending: false,
+            relationshipKeyPathsForPrefetching: ["bolus", "tempBasal"]
         )
         )
 
 
         return try await context.perform {
         return try await context.perform {

+ 9 - 0
Trio/Sources/Modules/Home/HomeStateModel+Setup/DeterminationSetup.swift

@@ -30,6 +30,15 @@ extension Home.StateModel {
         with IDs: [NSManagedObjectID],
         with IDs: [NSManagedObjectID],
         keyPath: ReferenceWritableKeyPath<Home.StateModel, [OrefDetermination]>
         keyPath: ReferenceWritableKeyPath<Home.StateModel, [OrefDetermination]>
     ) async throws {
     ) async throws {
+        // Prefetch the determinations into viewContext with one IN-query so the
+        // subsequent per-ID materialization avoids N+1 faults.
+        if !IDs.isEmpty {
+            let prefetchRequest = NSFetchRequest<OrefDetermination>(entityName: "OrefDetermination")
+            prefetchRequest.predicate = NSPredicate(format: "SELF IN %@", IDs)
+            prefetchRequest.returnsObjectsAsFaults = false
+            _ = try? viewContext.fetch(prefetchRequest)
+        }
+
         // Fetch the objects off the main thread
         // Fetch the objects off the main thread
         let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
         let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
             .getNSManagedObject(with: IDs, context: viewContext)
             .getNSManagedObject(with: IDs, context: viewContext)

+ 13 - 0
Trio/Sources/Modules/Home/HomeStateModel+Setup/PumpHistorySetup.swift

@@ -6,6 +6,19 @@ extension Home.StateModel {
         Task {
         Task {
             do {
             do {
                 let ids = try await self.fetchInsulin()
                 let ids = try await self.fetchInsulin()
+
+                // Prefetch events and their bolus/tempBasal relationships into viewContext
+                // with one IN-query so the subsequent per-ID materialization avoids N+1 faults.
+                if !ids.isEmpty {
+                    await viewContext.perform {
+                        let prefetchRequest = NSFetchRequest<PumpEventStored>(entityName: "PumpEventStored")
+                        prefetchRequest.predicate = NSPredicate(format: "SELF IN %@", ids)
+                        prefetchRequest.relationshipKeyPathsForPrefetching = ["bolus", "tempBasal"]
+                        prefetchRequest.returnsObjectsAsFaults = false
+                        _ = try? self.viewContext.fetch(prefetchRequest)
+                    }
+                }
+
                 let insulinObjects: [PumpEventStored] = try await CoreDataStack.shared
                 let insulinObjects: [PumpEventStored] = try await CoreDataStack.shared
                     .getNSManagedObject(with: ids, context: viewContext)
                     .getNSManagedObject(with: ids, context: viewContext)
                 await updateInsulinArray(with: insulinObjects)
                 await updateInsulinArray(with: insulinObjects)

+ 2 - 1
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -407,7 +407,8 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
             predicate: NSPredicate.lastPumpBolus,
             predicate: NSPredicate.lastPumpBolus,
             key: "timestamp",
             key: "timestamp",
             ascending: false,
             ascending: false,
-            fetchLimit: 1
+            fetchLimit: 1,
+            relationshipKeyPathsForPrefetching: ["bolus"]
         )
         )
 
 
         return try await context.perform {
         return try await context.perform {

+ 2 - 1
Trio/Sources/Services/WatchManager/GarminManager.swift

@@ -487,7 +487,8 @@ final class BaseGarminManager: NSObject, GarminManager, Injectable {
             predicate: compoundPredicate,
             predicate: compoundPredicate,
             key: "timestamp",
             key: "timestamp",
             ascending: false,
             ascending: false,
-            fetchLimit: 1
+            fetchLimit: 1,
+            relationshipKeyPathsForPrefetching: ["tempBasal"]
         )
         )
 
 
         return try await context.perform {
         return try await context.perform {