Skip to content

Use persistent staging for Apple Health imports#197

Merged
dzarlax merged 2 commits into
mainfrom
codex/persistent-import-staging
Jul 5, 2026
Merged

Use persistent staging for Apple Health imports#197
dzarlax merged 2 commits into
mainfrom
codex/persistent-import-staging

Conversation

@dzarlax

@dzarlax dzarlax commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace Apple Health XML temp staging with persistent import_run_id-keyed staging tables.
  • Keep parser staging outside a long transaction and promote points/workouts in a final transaction with stage cleanup.
  • Persist failure counters, add conservative abandoned-stage cleanup, and document the new import lifecycle.

Test Plan

  • HEALTH_DB_TESTS=1 GOCACHE=/tmp/health-go-build-cache go test ./internal/storage -run "TestAppleHealthXMLImport|TestEnsureIndexesIntegration" -count=1 -v
  • GOCACHE=/tmp/health-go-build-cache go test ./internal/storage -run 'AppleHealthXMLImport|Import|Bulk|Workout|EnsureIndexesIntegration' -count=1
  • GOCACHE=/tmp/health-go-build-cache go test ./internal/applehealth ./cmd/import ./internal/ui -run 'ExportDate|Import' -count=1
  • GOCACHE=/tmp/health-go-build-cache make test-unit

Summary by CodeRabbit

  • New Features

    • Apple Health imports now use more reliable staging and promotion handling, improving large import processing and recovery.
  • Bug Fixes

    • Fixed import cleanup so failed or abandoned runs are cleared more safely.
    • Improved isolation between concurrent or repeated imports to prevent cross-run staging issues.
    • Added better handling for failed imports, including cleaner status tracking and cleanup behavior.
  • Documentation

    • Updated Apple Health import docs to reflect the latest import flow, cleanup behavior, and failure handling.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@dzarlax, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6645df7-78bd-449a-b0d0-9972883c2ef4

📥 Commits

Reviewing files that changed from the base of the PR and between be6f1b9 and 74f323e.

📒 Files selected for processing (4)
  • internal/storage/import_run.go
  • internal/storage/import_run_test.go
  • internal/storage/import_schema.go
  • internal/storage/indexes.go
📝 Walkthrough

Walkthrough

This PR replaces temporary-table staging for Apple Health XML imports with persistent staging tables (import_stage_points, import_stage_workouts) keyed by import_run_id. It adds schema/index provisioning, reworks ImportSession/Commit to use a short promotion transaction, adds abandoned-run cleanup, extends tests, and updates related documentation.

Changes

Persistent Import Staging

Layer / File(s) Summary
Staging table schema and provisioning
internal/storage/import_schema.go, internal/storage/db.go, internal/storage/indexes.go
Adds import_stage_points/import_stage_workouts DDL, wires table creation into EnsureAllTables/EnsureIndexes, adds dedup/coverage indexes, and schedules CleanupAbandonedImportStages(24h) after index setup.
ImportSession staging and abandoned-run cleanup
internal/storage/import_run.go
Removes session-owned pgx.Tx; AddPoints/AddWorkouts bulk-copy staged rows (with import_run_id) into persistent tables; BeginAppleHealthXMLImport cleans up abandoned running imports before starting a new one.
Commit/promotion transaction and failure handling
internal/storage/import_run.go
Commit begins a dedicated promotion transaction, runs promoteCoverage/promotePoints/promoteWorkouts and markCommitted against it, deletes staged rows, then commits; adds deleteStageRows and exported CleanupAbandonedImportStages; failure counters now merge via COALESCE/GREATEST.
Staging lifecycle tests
internal/storage/import_run_test.go, internal/storage/indexes_test.go
Extends abort test with staging assertions; adds tests for staged-row visibility/cleanup, cross-run isolation, and abandoned-stage cleanup; adds assertStageCounts and tableExists helpers, and verifies required tables/indexes exist post-provisioning.
Documentation updates
AGENTS.md, CLAUDE.md, README.md, docs/ai-plans/*
Updates import-flow descriptions to reference persistent staging and short promotion transactions, revises the earlier dedup/batching plan's limitations, and adds a new implementation plan document.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Parser
  participant ImportSession
  participant StageTables as StageTables (import_stage_points/workouts)
  participant PromotionTx
  participant ImportRuns

  Parser->>ImportSession: AddPoints/AddWorkouts
  ImportSession->>StageTables: CopyFrom (import_run_id)
  Parser->>ImportSession: Commit()
  ImportSession->>PromotionTx: db.pool.Begin()
  PromotionTx->>StageTables: promoteCoverage/promotePoints/promoteWorkouts
  PromotionTx->>ImportRuns: markCommitted(tx)
  PromotionTx->>StageTables: deleteStageRows(tx)
  PromotionTx-->>ImportSession: Commit()
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and test plan, but omits most required template sections such as linked issue, privacy, evidence, methodology, and deployment notes. Add the missing template sections: linked issue, privacy/data impact checklist, evidence boundary, methodology/documentation, and deployment notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: switching Apple Health imports to persistent staging.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/persistent-import-staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be6f1b903f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/storage/import_run.go Outdated
Comment on lines +65 to +66
if err := s.cleanupAbandonedImportStages(ctx, abandonedRunningImportAge); err != nil {
return nil, fmt.Errorf("cleanup import staging: %w", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ensure stage tables before CLI imports

When the standalone cmd/import path is run against a schema that has not already been migrated by server startup, BeginAppleHealthXMLImport now calls cleanup before doing anything else, and that cleanup references the new import_stage_* tables. I checked the import CLI path: it opens storage.New but does not call EnsureIndexes, so a fresh/just-upgraded CLI import can fail immediately with relation "import_stage_points" does not exist instead of creating the new persistent staging tables. Ensure the stage-table DDL is run in the CLI/Begin path before this cleanup.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/storage/import_run.go (1)

174-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated rollback/fail/cleanup block in Commit().

The 4-line tx.RollbackmarkImportFailedWithCounterscleanupImportStageRowsreturn sequence is duplicated verbatim across all four error branches (promoteCoverage, promotePoints, promoteWorkouts, markCommitted, deleteStageRows — 5 occurrences). A future change to this failure path (e.g., adding a log line, changing arguments) risks being applied inconsistently. Also, the errors from markImportFailedWithCounters/cleanupImportStageRows are silently discarded (_ =), so a failure in the failure-handling path itself is invisible.

♻️ Proposed refactor
+func (s *ImportSession) failCommit(ctx context.Context, tx pgx.Tx, cause error) (ImportCounters, error) {
+	_ = tx.Rollback(ctx)
+	if err := s.db.markImportFailedWithCounters(s.runID, cause.Error(), s.counters); err != nil {
+		log.Printf("mark import %d failed: %v", s.runID, err)
+	}
+	if err := s.db.cleanupImportStageRows(s.runID); err != nil {
+		log.Printf("cleanup stage rows for import %d: %v", s.runID, err)
+	}
+	return s.counters, cause
+}
+
 func (s *ImportSession) Commit() (ImportCounters, error) {
 	...
 	if err := s.promoteCoverage(ctx, tx); err != nil {
-		_ = tx.Rollback(ctx)
-		_ = s.db.markImportFailedWithCounters(s.runID, err.Error(), s.counters)
-		_ = s.db.cleanupImportStageRows(s.runID)
-		return s.counters, err
+		return s.failCommit(ctx, tx, err)
 	}
 	// ...repeat for promotePoints, promoteWorkouts, markCommitted, deleteStageRows
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/import_run.go` around lines 174 - 221, Refactor the
duplicated failure path in ImportSession.Commit by extracting the repeated
tx.Rollback, markImportFailedWithCounters, cleanupImportStageRows, and return
sequence into a single helper used by the promoteCoverage, promotePoints,
promoteWorkouts, markCommitted, and deleteStageRows error branches. Make sure
the helper preserves the current behavior while centralizing any future changes,
and consider surfacing or at least logging errors from
markImportFailedWithCounters and cleanupImportStageRows instead of discarding
them so failures in the error-handling path are visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/storage/import_run.go`:
- Around line 65-68: The cleanup call in BeginAppleHealthXMLImport currently
turns abandoned-stage cleanup failures into a hard error, which blocks starting
new imports. Make the cleanup best-effort here, matching EnsureIndexes, by
handling the error from cleanupAbandonedImportStages with logging only and
continuing the import flow instead of returning fmt.Errorf. Keep the existing
cleanupAbandonedImportStages call and adjust the surrounding error handling so
transient failures do not abort BeginAppleHealthXMLImport.
- Around line 184-213: The rollback paths in the import transaction flow are
using the request context, so if ctx is canceled or times out the transaction
rollback may not execute cleanly. Update the rollback calls in the
promoteCoverage, promotePoints, promoteWorkouts, markCommitted, and
deleteStageRows error branches in importRun to use a cancellation-independent
context such as context.Background(), matching the approach used by
execStartupDDL, while keeping the existing failure marking and cleanup behavior
unchanged.

---

Nitpick comments:
In `@internal/storage/import_run.go`:
- Around line 174-221: Refactor the duplicated failure path in
ImportSession.Commit by extracting the repeated tx.Rollback,
markImportFailedWithCounters, cleanupImportStageRows, and return sequence into a
single helper used by the promoteCoverage, promotePoints, promoteWorkouts,
markCommitted, and deleteStageRows error branches. Make sure the helper
preserves the current behavior while centralizing any future changes, and
consider surfacing or at least logging errors from markImportFailedWithCounters
and cleanupImportStageRows instead of discarding them so failures in the
error-handling path are visible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f7df2661-f4f5-4d33-9f1c-a84e4df0fc8b

📥 Commits

Reviewing files that changed from the base of the PR and between 89ffdad and be6f1b9.

📒 Files selected for processing (11)
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • docs/ai-plans/2026-07-03-healthkit-import-dedup-batching.html
  • docs/ai-plans/2026-07-05-healthkit-persistent-import-staging.html
  • internal/storage/db.go
  • internal/storage/import_run.go
  • internal/storage/import_run_test.go
  • internal/storage/import_schema.go
  • internal/storage/indexes.go
  • internal/storage/indexes_test.go

Comment thread internal/storage/import_run.go
Comment thread internal/storage/import_run.go
@dzarlax dzarlax merged commit e96ce2a into main Jul 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant