fix(mongodb): run as replica set in docker-compose to enable transactions#181
fix(mongodb): run as replica set in docker-compose to enable transactions#181SantiagoDePolonia merged 2 commits intomainfrom
Conversation
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>
📝 WalkthroughWalkthroughThe changes configure MongoDB as a single-node replica set in docker-compose with enhanced replica-set-aware health checking, and refactor Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
docker-compose.yamlinternal/executionplans/store_mongodb.go
| 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) |
There was a problem hiding this comment.
🧹 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>
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:
What changed
docker-compose.yamlonly — the Go code is unchanged.command: ["--replSet", "rs0", "--bind_ip_all"]to themongodbservicedb.adminCommand('ping')healthcheck with one that:rs.initiate()on first startup (idempotent — errors on subsequent runs are swallowed)db.hello().isWritablePrimary— fails until primary is elected, so dependent services only start once the RS is readyWhy not remove the transaction?
A code review on the first commit correctly identified that removing
WithTransactioncreates a partial-failure window: ifInsertOnefails afterUpdateManysucceeds, the scope is left with no active version.BulkWriteis 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
Connecting from the host (
mongodb://localhost:27017/gomodel) continues to work — the RS is initialized withlocalhost:27017so the driver discovers it normally.Test plan
make testpassesdocker-compose up mongodbfrom a clean state — verify container reacheshealthywithout manualrs.initiate()release-e2e-scenarios.md(MongoDB smoke) against the stack🤖 Generated with Claude Code