-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
fix: Prevent stale data reads during update validation with primary readPreference #9859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: alpha
Are you sure you want to change the base?
Conversation
…eadPreference - Ensures update validation always reads from primary replica in DB - Fixes potential data consistency issues in distributed database environments - Adds comprehensive tests for validateOnly behavior with primary readPreference
🚀 Thanks for opening this pull request! |
📝 WalkthroughWalkthroughAdds a primary-read option to the validation path of DatabaseController.update when validateOnly is true. Updates tests to cover both validateOnly=true and validateOnly=false paths, using a mock storage adapter to assert read vs update calls and their arguments. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant DBController as DatabaseController
participant Adapter as StorageAdapter
rect rgb(245,250,255)
note over Client,DBController: validateOnly = true (validation path)
Client->>DBController: update(request, validateOnly=true)
DBController->>Adapter: find(query, options={ readPreference: 'primary' })
alt object not found
DBController-->>Client: error OBJECT_NOT_FOUND
else object found
DBController-->>Client: {}
end
end
rect rgb(250,245,255)
note over Client,DBController: validateOnly = false (update path)
Client->>DBController: update(request, validateOnly=false)
DBController->>Adapter: findOneAndUpdate(query, update, options)
Adapter-->>DBController: result
DBController-->>Client: result/ack
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). Please share your feedback with us on this Discord post. Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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 |
🎉 Snyk checks have passed. No issues have been found so far.✅ security/snyk check is complete. No issues have been found. (View Details) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/Controllers/DatabaseController.js (1)
596-602
: Force-primary read is correct; add limit and projection to cut loadOnly existence is needed here. Pass limit: 1 and a minimal projection to reduce I/O without altering semantics.
- if (validateOnly) { - return this.adapter.find(className, schema, query, { readPreference: 'primary' }).then(result => { + if (validateOnly) { + // Read from primary to avoid replica lag during beforeSave validation (see #9850). + return this.adapter.find( + className, + schema, + query, + { readPreference: 'primary', limit: 1, keys: ['objectId'] } + ).then(result => {spec/DatabaseController.spec.js (2)
619-633
: Prevent schema cache bleed between testsClear the schema cache in this suite to avoid flaky behavior from prior tests.
describe('update with validateOnly', () => { + beforeEach(() => { + Config.get(Parse.applicationId).schemaCache.clear(); + });
655-657
: Make assertion resilient to future option additionsUse partial match so the test won’t break if more options (e.g., limit, keys) are passed.
- expect(findCall.args[3]).toEqual({ readPreference: 'primary' }); // options parameter + expect(findCall.args[3]).toEqual(jasmine.objectContaining({ readPreference: 'primary' })); // options parameter
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
spec/DatabaseController.spec.js
(1 hunks)src/Controllers/DatabaseController.js
(1 hunks)
🔇 Additional comments (1)
spec/DatabaseController.spec.js (1)
663-687
: LGTM: correctly asserts update path when validateOnly is falseGood coverage ensuring validation read is bypassed and write path is invoked.
Pull Request
Issue
Closes: #9850
Approach
This fix addresses a data consistency issue in distributed database environments where
beforeSave
trigger validation queries may read from stale read replicas while update operations target the primary database.Problem
During
beforeSave
trigger execution, Parse Server performs permission validation by querying the database before applying updates. However:Solution
Modified
DatabaseController.update()
method to ensure validation queries (validateOnly: true
) explicitly use{ readPreference: 'primary' }
. This guarantees that:Key Changes
{ readPreference: 'primary' }
option for validation queries insrc/Controllers/DatabaseController.js
beforeSave
triggersTesting
Added comprehensive test coverage in
spec/DatabaseController.spec.js
:validateOnly: true
queries use primary readPreferenceTasks
Summary by CodeRabbit
Bug Fixes
Tests