Skip to content

Restructure MongoDB V3 compacting - #752

Draft
rkistner wants to merge 27 commits into
mainfrom
compact-lite
Draft

Restructure MongoDB V3 compacting#752
rkistner wants to merge 27 commits into
mainfrom
compact-lite

Conversation

@rkistner

@rkistner rkistner commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The goals and design are described in v3-compaction-design.md.

The main changes are:

  1. On V3 storage, initial replication now triggers a new "chunk-merge compact", instead of a checksum pre-calculation. This has the same benefits as checksum pre-calculation, but additionally merges small chunks into larger chunks. Unlike a full compact, this does not de-duplicate operations, making it much cheaper to run.
  2. The fields and indexes on the bucket-state collections are modified (breaking change for V3 storage; no migration path; V3 storage is still considered unstable).
  3. Any compact run only checks buckets that may need compacting, making it fast to run on much shorter intervals.
  4. Multiple compact jobs may be run concurrently, without duplicating significant work.
  5. All buckets would eventually get a full compact, preventing historical data from persisting indefinitely if the bucket is not modified further.

This does not affect compacting on MongoDB storage V1 or Postgres storage. This does not affect compacting of parameter indexes.

The per-bucket full compact implementation is mostly unchanged.

Compact action

This adds a new --incremental-only option, which can be used for compact jobs running on a short interval. This has skips compacting streams using V1 storage. This is useful for cases that may still have replication stream using both V1 and V3 storage, or where the job does not know upfront what storage version is used.

For V3 compaction, the compact job now also covers PROCESSING streams.

Future work

  1. Make some parameters configurable, such as minimum period between chunk-merge compacts or full compacts.
  2. Add a mode for running a persistent compact job that polls for new work, in addition to the current mode of periodic jobs.
  3. Run a compact job concurrently with initial replication, instead of only afterwards, to reduce the overall time required for initial replication.

AI Usage

Manually designed and documented the approach. Used Codex gpt-5.6 to implement, especially the MongoDB queries. Reviewed manually and with Claude Opus 5.

@changeset-bot

changeset-bot Bot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4ed22d4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@powersync/service-module-mongodb-storage Minor
@powersync/service-core Minor
@powersync/service-module-postgres-storage Patch
@powersync/service-core-tests Patch
@powersync/service-module-postgres Patch
@powersync/service-module-mongodb Patch
@powersync/service-module-convex Patch
@powersync/service-module-mysql Patch
@powersync/service-schema Minor
@powersync/service-module-mssql Patch
@powersync/service-image Minor
@powersync/service-module-core Patch
test-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@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: 2c76691643

ℹ️ 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".

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

ℹ️ 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".

) {
const context = new CompactionContext(lease, kind, decision, rescheduleNotBefore);
lease.startRenewal();
await this.retryCompaction(context.state._id.b, () => this.compactSingleBucket(context));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make partial chunk replacements retry-safe

When a retryable error occurs after one flushCompactionGroup() transaction succeeds but before the bucket is finalized, this retries with the original context.state even though bucket documents have already changed. In a subsequent chunk-compaction pass, the successful merge may have removed the document at the original compacted_state.op_id, so combineChunkStats() repeatedly throws Missing previous compacted chunk; even retries in later jobs retain that stale state and cannot compact the bucket. Persist partial progress atomically or reconstruct the compaction baseline from the current documents before retrying.

Useful? React with 👍 / 👎.

Comment thread packages/service-core/src/entry/commands/compact-action.ts Outdated

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

ℹ️ 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 modules/module-mongodb-storage/src/storage/implementation/v1/MongoCompactorV1.ts Outdated
Comment thread modules/module-postgres-storage/src/storage/PostgresSyncRulesStorage.ts Outdated
@rkistner
rkistner marked this pull request as draft August 14, 2026 08:03
@rkistner
rkistner marked this pull request as ready for review August 14, 2026 14:12

@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: 4ed22d41b0

ℹ️ 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".

const compactedStats = await this.readBucketStats(bucket, resolvedDefinitionId, compactedOpId);
const result = {
compactedState: compactedStats,
bucketStats: this.applyCompactionDelta(bucketStats(context.state), preCompactionPrefix, compactedStats)

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 Rebuild full-compaction stats after a partial retry

When a retryable error occurs after one flushCompactionGroup() transaction commits but before finalization, the retry recomputes preCompactionPrefix from the already-replaced documents while bucketStats(context.state) still describes the pre-attempt documents. Any byte or chunk delta from the committed replacement is therefore omitted, leaving bucket_stats permanently inflated and skewing later scheduling and compaction deltas. Fresh evidence in the current code is that the chunk path now rebuilds authoritative stats after a partial merge, but this full-compaction path still applies a delta to the stale claimed state.

Useful? React with 👍 / 👎.

Comment on lines +379 to +381
await this.db
.bucketState(this.group_id)
.updateOne(this.unclaimedSnapshotFilter(state), [{ $set: { next_compact_check: notBefore } }]);

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 Clear expired leases when rescheduling pre-claim failures

If decision calculation throws for a due bucket that still carries an expired lease from a crashed worker, findScheduledBucketBatch() includes the row, but this update uses unclaimedSnapshotFilter(), which requires compact_lease not to exist. The reschedule consequently matches nothing, and the fixed-boundary loop immediately fetches the same malformed row forever instead of isolating it and continuing to later buckets. Claim the expired lease before evaluating the state, or fence and remove that exact expired lease while rescheduling.

Useful? React with 👍 / 👎.

@rkistner
rkistner marked this pull request as draft August 14, 2026 14:42
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