Showing 2 changed files with 93 additions and 24 deletions
+69 -11
HealthProbe/Services/HealthKitService.swift
@@ -1142,13 +1142,15 @@ final class HealthKitService {
1142 1142
                     earliestDate: earliestDate,
1143 1143
                     latestDate: latestDate
1144 1144
                ) {
1145
-                let verificationStartedAt = Date()
1146
-                try await archiveStore.markVerification(
1145
+                captureTimings.finalizeElapsedSeconds += try await finalizeArchiveVerification(
1147 1146
                     sampleType: sampleType,
1148
-                    verifiedAt: Date(),
1149
-                    observationID: archiveObservationID
1147
+                    typeIdentifier: typeIdentifier,
1148
+                    recordCount: unchanged.totalCount,
1149
+                    progressStarted: progressStarted,
1150
+                    processedEventCount: processedEventCount,
1151
+                    archiveObservationID: archiveObservationID,
1152
+                    progress: progress
1150 1153
                 )
1151
-                captureTimings.finalizeElapsedSeconds += Date().timeIntervalSince(verificationStartedAt)
1152 1154
                 progress?.updateBlockProgress(
1153 1155
                     typeIdentifier,
1154 1156
                     detail: "No HealthKit delta",
@@ -1273,9 +1275,15 @@ final class HealthKitService {
1273 1275
             )
1274 1276
         }
1275 1277
 
1276
-        let verificationStartedAt = Date()
1277
-        try await archiveStore.markVerification(sampleType: sampleType, verifiedAt: Date(), observationID: archiveObservationID)
1278
-        captureTimings.finalizeElapsedSeconds += Date().timeIntervalSince(verificationStartedAt)
1278
+        captureTimings.finalizeElapsedSeconds += try await finalizeArchiveVerification(
1279
+            sampleType: sampleType,
1280
+            typeIdentifier: typeIdentifier,
1281
+            recordCount: recordMap.count,
1282
+            progressStarted: progressStarted,
1283
+            processedEventCount: processedEventCount,
1284
+            archiveObservationID: archiveObservationID,
1285
+            progress: progress
1286
+        )
1279 1287
 
1280 1288
         let sortedRecordsStartedAt = Date()
1281 1289
         let sortedKeys = recordMap.keys.sorted {
@@ -1465,9 +1473,15 @@ final class HealthKitService {
1465 1473
             )
1466 1474
         )
1467 1475
 
1468
-        let verificationStartedAt = Date()
1469
-        try await archiveStore.markVerification(sampleType: sampleType, verifiedAt: Date(), observationID: archiveObservationID)
1470
-        captureTimings.finalizeElapsedSeconds += Date().timeIntervalSince(verificationStartedAt)
1476
+        captureTimings.finalizeElapsedSeconds += try await finalizeArchiveVerification(
1477
+            sampleType: sampleType,
1478
+            typeIdentifier: typeIdentifier,
1479
+            recordCount: recordCount,
1480
+            progressStarted: progressStarted,
1481
+            processedEventCount: processedEventCount,
1482
+            archiveObservationID: archiveObservationID,
1483
+            progress: progress
1484
+        )
1471 1485
 
1472 1486
         guard recordCount > 0 || anchor != nil else {
1473 1487
             return SampleDistribution(
@@ -1564,6 +1578,50 @@ final class HealthKitService {
1564 1578
         return Double(processedCount) / elapsedSeconds
1565 1579
     }
1566 1580
 
1581
+    private func finalizeArchiveVerification(
1582
+        sampleType: HKSampleType,
1583
+        typeIdentifier: String,
1584
+        recordCount: Int,
1585
+        progressStarted: Date,
1586
+        processedEventCount: Int,
1587
+        archiveObservationID: Int64,
1588
+        progress: SnapshotFetchProgress?
1589
+    ) async throws -> TimeInterval {
1590
+        let elapsedBeforeFinalize = Date().timeIntervalSince(progressStarted)
1591
+        progress?.updateBlockProgress(
1592
+            typeIdentifier,
1593
+            detail: "Finalizing archive aggregates",
1594
+            recordCount: recordCount,
1595
+            elapsedSeconds: elapsedBeforeFinalize,
1596
+            samplesPerSecond: Self.samplesPerSecond(
1597
+                processedCount: processedEventCount,
1598
+                elapsedSeconds: elapsedBeforeFinalize
1599
+            )
1600
+        )
1601
+
1602
+        let verificationStartedAt = Date()
1603
+        try await archiveStore.markVerification(
1604
+            sampleType: sampleType,
1605
+            verifiedAt: Date(),
1606
+            observationID: archiveObservationID
1607
+        )
1608
+        let finalizeElapsed = Date().timeIntervalSince(verificationStartedAt)
1609
+
1610
+        let elapsedAfterFinalize = Date().timeIntervalSince(progressStarted)
1611
+        progress?.updateBlockProgress(
1612
+            typeIdentifier,
1613
+            detail: "Archive aggregates finalized",
1614
+            recordCount: recordCount,
1615
+            elapsedSeconds: elapsedAfterFinalize,
1616
+            samplesPerSecond: Self.samplesPerSecond(
1617
+                processedCount: processedEventCount,
1618
+                elapsedSeconds: elapsedAfterFinalize
1619
+            )
1620
+        )
1621
+
1622
+        return finalizeElapsed
1623
+    }
1624
+
1567 1625
     private static func pageProgressDetail(
1568 1626
         operation: String,
1569 1627
         pageNumber: Int,
+24 -13
HealthProbe/Services/SQLiteHealthArchiveStore.swift
@@ -1712,6 +1712,7 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
1712 1712
         """, db: db)
1713 1713
         try execute("CREATE INDEX IF NOT EXISTS idx_visibility_open_ranges ON sample_visibility_ranges(last_observation_id)", db: db)
1714 1714
         try execute("CREATE INDEX IF NOT EXISTS idx_visibility_sample_open ON sample_visibility_ranges(sample_id, last_observation_id)", db: db)
1715
+        try execute("CREATE INDEX IF NOT EXISTS idx_visibility_sample_open_version ON sample_visibility_ranges(sample_id, last_observation_id, version_id)", db: db)
1715 1716
         try execute("CREATE INDEX IF NOT EXISTS idx_visibility_sample_version_open ON sample_visibility_ranges(sample_id, version_id, last_observation_id)", db: db)
1716 1717
         try execute("CREATE INDEX IF NOT EXISTS idx_visibility_point_lookup ON sample_visibility_ranges(first_observation_id, last_observation_id)", db: db)
1717 1718
         try execute("""
@@ -2989,6 +2990,11 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
2989 2990
         db: OpaquePointer?
2990 2991
     ) throws -> [ArchiveV2DailyAggregateRow] {
2991 2992
         let sql = """
2993
+        WITH typed_samples AS (
2994
+            SELECT id
2995
+            FROM samples INDEXED BY idx_samples_type_id
2996
+            WHERE sample_type_id = ?
2997
+        )
2992 2998
         SELECT
2993 2999
             CAST(((v.start_date + ?) / 86400) AS INTEGER) * 86400 - ? AS bucket_start,
2994 3000
             CAST(((v.start_date + ?) / 86400) AS INTEGER) * 86400 - ? + 86400 AS bucket_end,
@@ -2996,21 +3002,20 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
2996 3002
             SUM(v.numeric_value),
2997 3003
             MAX(v.numeric_value),
2998 3004
             v.source_revision_id
2999
-        FROM samples s
3000
-        JOIN sample_visibility_ranges r
3005
+        FROM typed_samples s
3006
+        JOIN sample_visibility_ranges r INDEXED BY idx_visibility_sample_open_version
3001 3007
           ON r.sample_id = s.id
3002 3008
          AND r.last_observation_id IS NULL
3003 3009
         JOIN sample_versions v ON v.id = r.version_id
3004
-        WHERE s.sample_type_id = ?
3005 3010
         GROUP BY bucket_start, bucket_end, v.source_revision_id
3006 3011
         ORDER BY bucket_start ASC, v.source_revision_id ASC
3007 3012
         """
3008 3013
         return try withStatement(sql, db: db) { statement in
3009
-            sqlite3_bind_double(statement, 1, Double(secondsFromGMT))
3014
+            bindInt64(sampleTypeID, to: 1, in: statement)
3010 3015
             sqlite3_bind_double(statement, 2, Double(secondsFromGMT))
3011 3016
             sqlite3_bind_double(statement, 3, Double(secondsFromGMT))
3012 3017
             sqlite3_bind_double(statement, 4, Double(secondsFromGMT))
3013
-            bindInt64(sampleTypeID, to: 5, in: statement)
3018
+            sqlite3_bind_double(statement, 5, Double(secondsFromGMT))
3014 3019
 
3015 3020
             var rows: [ArchiveV2DailyAggregateRow] = []
3016 3021
             while sqlite3_step(statement) == SQLITE_ROW {
@@ -3048,11 +3053,13 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
3048 3053
         db: OpaquePointer?
3049 3054
     ) throws -> (appeared: Int, disappeared: Int, representationChanged: Int) {
3050 3055
         let sql = """
3051
-        SELECT event_kind, COUNT(*)
3052
-        FROM sample_observation_events e
3053
-        JOIN samples s ON s.id = e.sample_id
3054
-        WHERE e.observation_id = ? AND s.sample_type_id = ?
3055
-        GROUP BY event_kind
3056
+        SELECT e.event_kind, COUNT(*)
3057
+        FROM samples s INDEXED BY idx_samples_type_id
3058
+        JOIN sample_observation_events e INDEXED BY idx_events_sample
3059
+          ON e.sample_id = s.id
3060
+         AND e.observation_id = ?
3061
+        WHERE s.sample_type_id = ?
3062
+        GROUP BY e.event_kind
3056 3063
         """
3057 3064
         return try withStatement(sql, db: db) { statement in
3058 3065
             bindInt64(observationID, to: 1, in: statement)
@@ -3080,13 +3087,17 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
3080 3087
 
3081 3088
     private func visibleAggregate(sampleTypeID: Int64, db: OpaquePointer?) throws -> ArchiveV2VisibleAggregate {
3082 3089
         let sql = """
3090
+        WITH typed_samples AS (
3091
+            SELECT id
3092
+            FROM samples INDEXED BY idx_samples_type_id
3093
+            WHERE sample_type_id = ?
3094
+        )
3083 3095
         SELECT COUNT(*), MIN(v.start_date), MAX(v.end_date), SUM(v.numeric_value), MAX(v.numeric_value)
3084
-        FROM samples s
3085
-        JOIN sample_visibility_ranges r
3096
+        FROM typed_samples s
3097
+        JOIN sample_visibility_ranges r INDEXED BY idx_visibility_sample_open_version
3086 3098
           ON r.sample_id = s.id
3087 3099
          AND r.last_observation_id IS NULL
3088 3100
         JOIN sample_versions v ON v.id = r.version_id
3089
-        WHERE s.sample_type_id = ?
3090 3101
         """
3091 3102
         return try withStatement(sql, db: db) { statement in
3092 3103
             bindInt64(sampleTypeID, to: 1, in: statement)