Skip to content

fix(mongodb): run as replica set in docker-compose to enable transactions#181

Merged
SantiagoDePolonia merged 2 commits intomainfrom
fix/mongodb-standalone-compat
Mar 26, 2026
Merged

fix(mongodb): run as replica set in docker-compose to enable transactions#181
SantiagoDePolonia merged 2 commits intomainfrom
fix/mongodb-standalone-compat

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Mar 26, 2026

Summary

Configure the docker-compose MongoDB service to run as a single-node replica set (--replSet rs0) with a healthcheck that auto-initializes the RS and waits for primary election before marking the container healthy.

This fixes the startup failure:

(IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos

What changed

docker-compose.yaml only — the Go code is unchanged.

  • Added command: ["--replSet", "rs0", "--bind_ip_all"] to the mongodb service
  • Replaced the simple db.adminCommand('ping') healthcheck with one that:
    1. Calls rs.initiate() on first startup (idempotent — errors on subsequent runs are swallowed)
    2. Checks db.hello().isWritablePrimary — fails until primary is elected, so dependent services only start once the RS is ready

Why not remove the transaction?

A code review on the first commit correctly identified that removing WithTransaction creates a partial-failure window: if InsertOne fails after UpdateMany succeeds, the scope is left with no active version. BulkWrite is not a substitute — it does not provide multi-document atomicity without a session. The transaction is necessary for the same reason the PostgreSQL store uses one.

Compatibility

Deployment Before After
docker-compose (standalone) ❌ startup crash ✅ auto-initializes RS
Single-node replica set
Multi-node cluster

Connecting from the host (mongodb://localhost:27017/gomodel) continues to work — the RS is initialized with localhost:27017 so the driver discovers it normally.

Test plan

  • make test passes
  • docker-compose up mongodb from a clean state — verify container reaches healthy without manual rs.initiate()
  • Run S55 from release-e2e-scenarios.md (MongoDB smoke) against the stack

🤖 Generated with Claude Code

Replace WithTransaction in MongoDBStore.Create with direct operations.
The existing unique indexes (scope_key+version, active partial) already
enforce correctness; transactions added no additional guarantee but
required a replica set, breaking standalone MongoDB deployments.

Also configure docker-compose to start MongoDB as a single-node replica
set (--replSet rs0) with a healthcheck that auto-initializes the RS and
waits for primary election before accepting connections.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

The changes configure MongoDB as a single-node replica set in docker-compose with enhanced replica-set-aware health checking, and refactor MongoDBStore.Create to remove transaction wrapping and session management, executing database operations directly instead.

Changes

Cohort / File(s) Summary
MongoDB Container Configuration
docker-compose.yaml
Enabled MongoDB replica set mode with --replSet rs0 flag, replaced simple ping healthcheck with replica-set-aware probe using rs.status() and rs.initiate, and adjusted timing parameters (interval: 10s→5s, timeout: 5s→10s, retries: 5→10).
MongoDB Store Implementation
internal/executionplans/store_mongodb.go
Removed transaction wrapping and session lifecycle management from Create method; now executes FindOne, UpdateMany, and InsertOne as direct database operations without session.WithTransaction wrapper.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A replica set blooms where once a singleton stood,
Healthchecks now whisper of primaries and good;
Transactions released like dandelion seeds,
Direct operations handle all our needs!
MongoDB evolves with each hop and bound. 🌱

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(mongodb): run as replica set in docker-compose to enable transactions' accurately reflects the primary change — configuring MongoDB as a replica set in docker-compose. However, it does not capture the equally important part of the change: removing transaction requirements from the code. The PR's actual main objective is enabling MongoDB compatibility across standalone and replica set modes by eliminating the transaction dependency, which is only partially captured by the title's focus on docker-compose configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mongodb-standalone-compat

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/executionplans/store_mongodb.go`:
- Around line 151-154: The MongoDB insert path currently returns immediately on
mongo.IsDuplicateKeyError(err), unlike PostgreSQL which retries; change the
insert logic in store_mongodb.go (the block that checks
mongo.IsDuplicateKeyError) to implement a retry loop (match PostgreSQL's
behavior) that attempts up to 5 retries on duplicate-key errors: on each retry
refresh/increment the plan version field used for the insert (same field used
when creating the document), re-attempt the insert, and only return the error
after exhausting retries or on non-duplicate errors; keep existing error
wrapping but preserve the duplicate-key detection using
mongo.IsDuplicateKeyError to trigger retries.
- Around line 115-155: The current sequence deactivates the old version with
s.collection.UpdateMany and then calls s.collection.InsertOne, which can leave
the scope without any active version if InsertOne fails; change to an
atomic-safe pattern: either wrap the deactivation and insertion in a MongoDB
session/transaction around the UpdateMany and InsertOne calls (use a
session.StartTransaction and CommitTransaction) or adopt the
insert-then-activate pattern—InsertOne the new mongoVersionDocument with
Active=false first, then perform a single BulkWrite that sets the new document
Active=true and old documents Active=false (or vice-versa) to guarantee the
swap; update error handling around mongo.IsDuplicateKeyError accordingly and
ensure Version/ mongoVersionDocument construction still uses the same fields
(ID, ScopeProvider, ScopeModel, ScopeKey, Version, Active, etc.).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 133964de-cacb-450d-b462-6c0fbd1fd2e3

📥 Commits

Reviewing files that changed from the base of the PR and between f9fb8e3 and 4be7282.

📒 Files selected for processing (2)
  • docker-compose.yaml
  • internal/executionplans/store_mongodb.go

Comment on lines +151 to +154
if mongo.IsDuplicateKeyError(err) {
return nil, fmt.Errorf("insert execution plan version: duplicate key: %w", err)
}

return version, nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("insert execution plan version: %w", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding retry logic for concurrent version conflicts.

The PostgreSQL implementation (store_postgresql.go:100-105) retries up to 5 times on unique constraint violations to handle concurrent creates. The MongoDB implementation returns immediately on duplicate key errors, which means concurrent creates for the same scope will fail for one caller rather than retry with an updated version number.

This may be acceptable depending on expected concurrency patterns, but it's a behavioral difference between storage backends.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executionplans/store_mongodb.go` around lines 151 - 154, The MongoDB
insert path currently returns immediately on mongo.IsDuplicateKeyError(err),
unlike PostgreSQL which retries; change the insert logic in store_mongodb.go
(the block that checks mongo.IsDuplicateKeyError) to implement a retry loop
(match PostgreSQL's behavior) that attempts up to 5 retries on duplicate-key
errors: on each retry refresh/increment the plan version field used for the
insert (same field used when creating the document), re-attempt the insert, and
only return the error after exhausting retries or on non-duplicate errors; keep
existing error wrapping but preserve the duplicate-key detection using
mongo.IsDuplicateKeyError to trigger retries.

The previous commit removed WithTransaction to support standalone
MongoDB, but this loses atomicity: if InsertOne fails after UpdateMany
succeeds the scope is left without an active version. The PostgreSQL
store uses an explicit transaction for the same reason.

True multi-document atomicity in MongoDB requires a transaction; neither
BulkWrite nor operation reordering is a substitute. Since the deployment
fix (--replSet rs0 in docker-compose) already ensures a replica set is
always available—satisfying both the single-node dev and cluster
production cases—the transaction can be safely kept.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SantiagoDePolonia SantiagoDePolonia changed the title fix(mongodb): remove transaction requirement from execution plan store fix(mongodb): run as replica set in docker-compose to enable transactions Mar 26, 2026
@SantiagoDePolonia SantiagoDePolonia merged commit a478572 into main Mar 26, 2026
16 checks passed
@SantiagoDePolonia SantiagoDePolonia deleted the fix/mongodb-standalone-compat branch April 4, 2026 11:36
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