Showing 2 changed files with 17 additions and 25 deletions
+14 -3
HealthProbe/Doc/04-project/Import-Optimization-Log.md
@@ -752,6 +752,13 @@ The likely bottleneck is per-row SQLite work:
752 752
   authorized backup volume can be much larger than the original 15-type test
753 753
   subset and that first-import write-path performance must remain a separate
754 754
   acceptance track from repeated incremental capture performance.
755
+- The next first-import optimization removes compact `recordArchiveData`
756
+  generation for new SwiftData TypeCount rows. SQLite is now the source of
757
+  truth for record details, repeated captures no longer need compact archives,
758
+  and legacy SwiftData detail-cache precompute is disabled. Keeping the compact
759
+  blob on fresh first imports only adds CPU, memory, and SwiftData payload size.
760
+  Existing optional fields remain for old rows and compatibility, but new
761
+  SQLite-backed captures should report no compact archive finalize cost.
755 762
 
756 763
 ## Open Issues / Observations
757 764
 
@@ -817,9 +824,13 @@ Prioritize experiments in this order:
817 824
 9. Keep watching first-import insert timing on the next clean large-database
818 825
    import because the new `sample_versions(start_date, sample_id)` index from
819 826
    `bf5a861` is a write-path tradeoff.
820
-10. Decide whether the legacy `recordArchiveData` field can be retired for new
821
-   SwiftData TypeCount rows or must remain populated only for first-import
822
-   compatibility. The validated repeated-capture path no longer needs it.
827
+10. Run a clean full first import after retiring compact `recordArchiveData` for
828
+   new SwiftData TypeCount rows. Compare wall clock, `SummedProcessingElapsed`,
829
+   `SummedProcessingInitialRecordElapsed`,
830
+   `SummedProcessingRecordArchiveFinalizeElapsed`, `SummedInsertElapsed`, and
831
+   post-import responsiveness. Expected success: record archive finalize is
832
+   zero/negligible, details still load from SQLite, and first-import memory /
833
+   post-save behavior does not regress.
823 834
 11. Investigate full-profile empty anchored-query cost for zero-count types.
824 835
    Compare slow empty types across reports before changing behavior; any skip or
825 836
    lower-frequency strategy must preserve the promise that full authorized
+3 -22
HealthProbe/Services/HealthKitService.swift
@@ -988,21 +988,11 @@ final class HealthKitService {
988 988
         }
989 989
         progress?.updateBlockProgress(
990 990
             monitoredType.id,
991
-            detail: "Preparing record archive",
991
+            detail: "Preparing capture summary",
992 992
             recordCount: distribution.totalCount
993 993
         )
994 994
         var timingBreakdown = distribution.timingBreakdown
995 995
         timingBreakdown.fetchElapsedSeconds += dateFetchElapsedSeconds
996
-        let recordArchiveStartedAt = Date()
997
-        let recordArchiveData: Data?
998
-        if let distributionArchiveData = distribution.recordArchiveData {
999
-            recordArchiveData = distributionArchiveData
1000
-        } else if distribution.records.isEmpty {
1001
-            recordArchiveData = nil
1002
-        } else {
1003
-            recordArchiveData = HealthRecordArchive.encode(distribution.records)
1004
-        }
1005
-        timingBreakdown.processingElapsedSeconds += Date().timeIntervalSince(recordArchiveStartedAt)
1006 996
 
1007 997
         var result = TypeCountFetchResult(
1008 998
             typeIdentifier: monitoredType.id,
@@ -1027,7 +1017,7 @@ final class HealthKitService {
1027 1017
                 )
1028 1018
             },
1029 1019
             records: [],
1030
-            recordArchiveData: recordArchiveData
1020
+            recordArchiveData: distribution.recordArchiveData
1031 1021
         )
1032 1022
         result.captureMode = distribution.captureMode.diagnosticValue
1033 1023
         result.deltaEventCount = distribution.deltaEventCount
@@ -1433,7 +1423,6 @@ final class HealthKitService {
1433 1423
         var firstRecordDate: Date?
1434 1424
         var latestRecordDate: Date?
1435 1425
         var yearMap: [Int: Int] = [:]
1436
-        var archiveWriter = HealthRecordArchive.makeCompactWriter(typeIdentifier: typeIdentifier)
1437 1426
         var hashBuilder = HashService.TypeHashBuilder(typeIdentifier: typeIdentifier)
1438 1427
         var shouldFetchNextPage = true
1439 1428
         var pendingArchiveSamples: [HKSample] = []
@@ -1511,7 +1500,6 @@ final class HealthKitService {
1511 1500
             for sample in page.samples {
1512 1501
                 autoreleasepool {
1513 1502
                     let value = Self.recordValue(for: sample, sampleType: sampleType, typeIdentifier: typeIdentifier)
1514
-                    archiveWriter.append(value)
1515 1503
                     hashBuilder.append(recordFingerprint: value.recordFingerprint)
1516 1504
                     yearMap[Calendar.current.component(.year, from: value.startDate), default: 0] += 1
1517 1505
                     firstRecordDate = min(firstRecordDate ?? value.startDate, value.startDate)
@@ -1550,7 +1538,6 @@ final class HealthKitService {
1550 1538
         let contentHash = hashBuilder.finalize()
1551 1539
         let hashFinalizeElapsed = Date().timeIntervalSince(finalizeHashStartedAt)
1552 1540
         captureTimings.processingElapsedSeconds += hashFinalizeElapsed
1553
-        captureTimings.processingRecordArchiveFinalizeElapsedSeconds += hashFinalizeElapsed
1554 1541
         progress?.updateBlockProgress(
1555 1542
             typeIdentifier,
1556 1543
             detail: pageNumber == 1 ? "Imported 1 page" : "Imported \(pageNumber) pages",
@@ -1590,12 +1577,6 @@ final class HealthKitService {
1590 1577
         let rawBinEnd = latestDate ?? latestRecordDate ?? binStart
1591 1578
         let binEnd = rawBinEnd > binStart ? rawBinEnd : binStart.addingTimeInterval(1)
1592 1579
 
1593
-        let archiveFinalizeStartedAt = Date()
1594
-        let recordArchiveData = archiveWriter.finalize()
1595
-        let archiveFinalizeElapsed = Date().timeIntervalSince(archiveFinalizeStartedAt)
1596
-        captureTimings.processingElapsedSeconds += archiveFinalizeElapsed
1597
-        captureTimings.processingRecordArchiveFinalizeElapsedSeconds += archiveFinalizeElapsed
1598
-
1599 1580
         return SampleDistribution(
1600 1581
             totalCount: recordCount,
1601 1582
             bins: [
@@ -1610,7 +1591,7 @@ final class HealthKitService {
1610 1591
             records: [],
1611 1592
             contentHash: contentHash,
1612 1593
             yearlyCounts: yearMap,
1613
-            recordArchiveData: recordArchiveData,
1594
+            recordArchiveData: nil,
1614 1595
             captureMode: .initialImport,
1615 1596
             deltaEventCount: processedEventCount,
1616 1597
             timingBreakdown: captureTimings.importBreakdown