@@ -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 |
@@ -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 |