-
Couldn't load subscription status.
- Fork 1.7k
En/db resolver #2140
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: development
Are you sure you want to change the base?
En/db resolver #2140
Conversation
Bumps [go.opentelemetry.io/otel/exporters/prometheus](https://github.com/open-telemetry/opentelemetry-go) from 0.59.0 to 0.59.1. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](open-telemetry/opentelemetry-go@exporters/prometheus/v0.59.0...exporters/prometheus/v0.59.1) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/prometheus dependency-version: 0.59.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]>
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.243.0 to 0.244.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](googleapis/google-api-go-client@v0.243.0...v0.244.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-version: 0.244.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]>
Bumps [gofr.dev](https://github.com/gofr-dev/gofr) from 1.42.4 to 1.42.5. - [Release notes](https://github.com/gofr-dev/gofr/releases) - [Commits](v1.42.4...v1.42.5) --- updated-dependencies: - dependency-name: gofr.dev dependency-version: 1.42.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]>
|
@Umang01-hash Can you add more details on sequence of R(Read) and W(Write) cases under which read and write replicas would be selected ? |
Hey @gizmo-rt we first determine is a query is read/write and if it is a read query we check if healthyReplica is available or not. If the replica is available we select it and send the read query to it and if the replica is not available we send it to primary If all replicas are unhealthy at time of a read query and fallback is enabled, the read falls back to the primary. If fallback is disabled, the read fails with an error. If a replica experiences multiple failures, it's circuit breaker opens and replica is temporarily skipped for queries. This timeout period is 30 seconds by default and we allow 5 failures for replica before cicuit breaker is opened. For an example sequence like R,R,W,R: 1st R: Routed to replica 2nd R: Routed to next replica (depends on which strategy is choosen random or round-robin) 1st W: Routed to primary 3rd R: Routed to next replica |
|
This PR implements SQL-level routing (parsing queries) to split reads vs writes. Another approach is to use the HTTP method (e.g. @aryanmehrotra @ccoVeille @akshat-kumar-singhal any suggestions on it?? |
@Umang01-hash Any issues you see with the current implementation of identifying it based on the query? |
One issue which might occur is if there is a POST request and user is first inserting the value and then fetching it. |
@aryanmehrotra While selecting the primary/replica based on request method type feels safer, we may still have issues due to replication lag. Also, in case of poor implementation (by application dev), example - DB writes in GET call, we can have other issues. |
We should document poor practices like DB writes in GET calls, since features like status code handling are based on request type and overriding them breaks best practices. For routing queries, we could either: Support a config with endpoints that always use replicas for heavy reads, or This covers more use cases, but ideally we want a cleaner approach where devs don’t have to manage this manually. If not, either of the options works. |
|
Hey! Here are my sugegstions for the above problems/comments:
Question to all: Does the above approach sound reasonable? Would these two additions solve the consistency concerns while keeping the implementation simple? Happy to explore HTTP method-based routing if you think it's a better path forward. @aryanmehrotra @ccoVeille @akshat-kumar-singhal @gizmo-rt - thoughts? |
I highly doubt if someone in a transaction would do POST → INSERT → SELECT, as mostly we do insert/update in transactions, SELECT happens separetely. Also, if we think about the three layer architecture, there are different method for creating an entry and getting that entry, right now we can't do that in a single transaction in gofr.
It would be very expensive, imagine writes or updates happening from a cron-job every 15 minutes then mostly it would be happening in a particular table, then we would also need to be table aware in that case. otherwise even after having the read/write split most queries would go to the write replica.
What does the best-practise say about GET and POST request endpoints functioning and what we already follow in gofr? |
|
@aryanmehrotra Thanks for your suggestions. Your points seem valid - transaction scoping wouldn't work in three-layer architecture, and global read-after-write tracking would be too expensive. Request-Scoped Routing: Instead of query-level or global tracking, we can route all queries within a single HTTP request based on the HTTP method. So if someone hits POST /users, ALL database queries (both INSERT and SELECT) within that request handler automatically go to primary. GET requests continue using replicas. This solves the POST → INSERT → SELECT consistency issue and aligns with REST best practices - GET should be safe (no side effects) and POST/PUT for state changes. GoFr already follows these patterns in its routing and status code handling. Thoughts on this approach? An extension on this as suggested by @akshat-kumar-singhal is: |
|
Routes will need to have the method as well. We would be better off
accepting this configuration via code instead of complicating the ENV file.
What we should ensure is the ability to switch seamlessly between
primary-replica setup vs primary only (local).
…On Tue, 28 Oct 2025 at 14:32, Umang Mundhra ***@***.***> wrote:
*Umang01-hash* left a comment (gofr-dev/gofr#2140)
<#2140 (comment)>
@aryanmehrotra <https://github.com/aryanmehrotra> Thanks for your
suggestions. Your points seem valid - transaction scoping wouldn't work in
three-layer architecture, and global read-after-write tracking would be too
expensive.
*Request-Scoped Routing:* Instead of query-level or global tracking, *we
can route all queries within a single HTTP request based on the HTTP method*.
So if someone hits POST /users, ALL database queries (both INSERT and
SELECT) within that request handler automatically go to primary. GET
requests continue using replicas.
*This solves the POST → INSERT → SELECT consistency issue and aligns with
REST best practices - GET should be safe (no side effects) and POST/PUT for
state changes.* GoFr already follows these patterns in its routing and
status code handling. Thoughts on this approach?
An extension on this as suggested by @akshat-kumar-singhal
<https://github.com/akshat-kumar-singhal> is:
*Route-Level Configuration:* We can also provide optional endpoint-level
control where developers can configure specific routes to always use
primary:
# Optional: Force specific endpoints to always use primary
DB_PRIMARY_ROUTES=/users/search,/reports/heavy,/admin/*
—
Reply to this email directly, view it on GitHub
<#2140 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/APUGM5XV6RQAE4CJL7X3JMT3Z4WMBAVCNFSM6AAAAACDKIIUESVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTINJVGA2TKNJXGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Yes this seems better, also env variable shouldn't be used for this we should give option from the code itself, this can be part as an enhancement based on user feedback once we rollout the feature just based on the route level. |
Pull Request Template
Description:
What is DBResolver?
Adds a DBResolver module to GoFr, which provides automatic read/write splitting for SQL databases.
Read queries (e.g.,
SELECT) are routed to read replicas, and write queries (e.g.,INSERT,UPDATE) are routed to the primary database.Seamlessly wraps the existing SQL datasource: does not require any application code changes for existing queries.
Developers interact with c.SQL exactly as before; all routing and failover are fully transparent.
Motivation & Benefits
Example Usage:
Configuration Example:
Testing Strategy:
Primary and Replicas launched with docker-compose (primary:3306, replicas:3307/3308)
Replication automated by setup scripts; seed data from SQL dump and app endpoints
Load Testing: Performed with Apache JMeter simulating high-concurrency API requests (both reads and writes)
Results:
Zero error rate; throughput and latency showed no regression compared to the baseline.
Read/write split is fully performant, and scaling is achieved without application change.
Checklist:
goimportandgolangci-lint.Thank you for your contribution!