Repository navigation
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #9528 +/- ##
===========================================
- Coverage 82.98% 69.27% -13.71%
===========================================
Files 670 669 -1
Lines 40875 41030 +155
Branches 3918 3933 +15
===========================================
- Hits 33920 28425 -5495
- Misses 6479 12129 +5650
Partials 476 476 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
camd
force-pushed
the
camd/read-replica-queries
branch
from
May 23, 2026 17:44
fb7dc10 to
5ed5343
Compare
This comment was marked as resolved.
This comment was marked as resolved.
camd
force-pushed
the
camd/read-replica-queries
branch
from
May 25, 2026 16:45
5ed5343 to
4eeff5a
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
from
June 14, 2026 14:56
be85438 to
295e3bd
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
4 times, most recently
from
July 3, 2026 23:43
b9f0688 to
8884b98
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
from
July 18, 2026 17:21
8884b98 to
88fdb8e
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
from
July 31, 2026 17:53
88fdb8e to
a3c367e
Compare
✅ Deploy Preview for treeherder ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
camd
force-pushed
the
camd/read-replica-queries
branch
2 times, most recently
from
August 16, 2026 03:29
9d7a88a to
09f99ef
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
2 times, most recently
from
August 29, 2026 20:00
2e29f73 to
c1adde3
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
2 times, most recently
from
September 11, 2026 20:54
4f907fd to
57ace9d
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
3 times, most recently
from
September 22, 2026 16:43
5dcd8c5 to
cc1f6fc
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
2 times, most recently
from
October 1, 2026 16:21
44488bb to
a58b747
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
3 times, most recently
from
October 9, 2026 00:01
8d0dcec to
9e72a86
Compare
camd
force-pushed
the
camd/read-replica-queries
branch
2 times, most recently
from
October 13, 2026 16:13
4771945 to
5c0c64f
Compare
Routes 10 read-only Perfherder viewsets to the read replica (PerformanceSignatureViewSet, PerformancePlatformViewSet, PerformanceJobViewSet, PerformanceDatumViewSet, PerformanceBugTemplateViewSet, PerformanceIssueTrackerViewSet, PerformanceSummary, PerformanceAlertSummaryTasks, PerfCompareResults, TestSuiteHealthViewSet). Adds integration tests for signature list and summary endpoints. Updates test_perfcompare_api.py, test_performance_data_api.py, and test_performance_bug_template_api.py to declare read_replica database access.
Two review fixes: 1. When the kill switch is off (READ_REPLICA_ENABLED=false or READ_REPLICA_DATABASE_URL unset), the mixin previously still wrapped dispatch, set the thread-local (a no-op without a router), and would emit a misleading "db_routing_fallback" log on any primary failure. The mixin now checks connections.databases for the alias and bypasses wrapping when absent. 2. The three test files that gained databases=["default","read_replica"] implicitly relied on fixtures pulling in transactional_db. Make the transactional requirement explicit with transaction=True so the behavior survives future fixture refactors.
The feature is a no-op locally with the default .env (both env vars unset → no read_replica alias → mixin early-returns). Standard local dev doesn't need to know about the routing. The design, code-level docstring, and PR deploy notes cover the cases that do.
Successful replica routing was previously silent — only the fallback path emitted a log line. Adds a single DEBUG-level log per routed request so a developer (or ops) can grep \`db_routing routed_to=read_replica\` to confirm which requests hit the replica. Prod-silent by default (logger threshold is INFO); enable locally with LOGGING_LEVEL=DEBUG.
Apply ReadReplicaMixin to JobsViewSet, JobsProjectViewSet, and PushViewSet so the jobs-view polling GET endpoints read from the read_replica alias, relieving the primary of the busiest read traffic in the app. Only safe HTTP methods route; the existing one-shot fallback to primary is inherited unchanged. Gated by READ_REPLICA_ENABLED, so this is a no-op until the replica is configured. Existing tests that exercise these endpoints now need transaction=True and the read_replica alias in their django_db marker, since the routed viewset reads through a separate connection that only sees committed data.
Apply ReadReplicaMixin to SummaryByGroupName (/api/groupsummary/). This is one of the heaviest read queries in the app: a multi-join aggregation over a full day of jobs across autoland and mozilla-central, run once per platform, joining Job/Push/JobType/JobLog/Group/GroupStatus. It is a pure GET reporting query and tolerant of replica lag, so it is a strong candidate for offloading from the primary. All tables are in the model app, already on the router allow-list. The existing groupsummary tests need transaction=True and the read_replica alias in their django_db marker, since the routed view reads through a separate connection that only sees committed data.
camd
force-pushed
the
camd/read-replica-queries
branch
from
October 20, 2026 16:51
5c0c64f to
79651ce
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Route read-only endpoints to a PostgreSQL read replica
Summary
Adds an opt-in mechanism to serve a defined set of read-only GET endpoints from a PostgreSQL read replica instead of the primary, to relieve the primary of read load that competes with the data-ingestion write workload. Ships off by default and is controlled entirely by environment variables — no code change needed to turn it on or off.
How it works
ReadReplicaRouter(treeherder/config/db_routing.py) — a Django DB router that sends reads to aread_replicaalias when a thread-local flag is set, but only for models whose app label is in an allow-list ({"perf", "model"}). Everything else stays on the primary.ReadReplicaMixin— a viewset/APIView mixin that flips the flag for safe HTTP methods only (GET/HEAD/OPTIONS). Unsafe methods (POST/PUT/PATCH/DELETE) always use the primary, so a viewset can mix routed reads and primary writes safely.OperationalError/InterfaceError, the request is retried once against the primary and adb_routing_fallbackWARNING is logged. A degraded replica means "served from primary," not errors.READ_REPLICA_ENABLED; when off (or the replica URL is unset), the alias isn't registered and the router isn't installed (complete no-op). Celery, ETL, and management commands are untouched and always use the primary.Endpoints routed
/api/jobs/,/api/project/{repo}/push/,/api/project/{repo}/jobs/{id}/(detail + similar/text-log actions)./api/groupsummary/— a heavy day-wide multi-join aggregation, one of the most expensive reads in the app.Perfherder mutating endpoints (alert summaries, alerts, tags) intentionally stay on the primary so a user's write is immediately visible on re-read.
Consistency / safety
The routed jobs/push/group/Perfherder-read paths have no synchronous read-after-write: job actions go through Taskcluster + ingestion (not a direct DB write the user re-reads), and job classification updates
failure_classification_idandlast_modifiedin the same row, so a lagging replica returns either the whole old row or the whole new row — no torn reads. Polling tolerates replica lag via the existing cursor safety buffer; worst case a new job/push appears one poll cycle later.Enabling (for deploy/app-config owners)
Set both (both required to activate):
READ_REPLICA_DATABASE_URLDATABASE_URLREAD_REPLICA_ENABLEDtrueVerify from inside the app with
./manage.py dbshell --database=read_replica. Recommended rollout: enable in stage → soak several days → enable in prod. Kill switch isREAD_REPLICA_ENABLED=false(no deploy). During soak, watch: primary DB CPU/connections (should drop), replica CPU (should rise — currently near-zero, ample headroom), anddb_routing_fallbacklog volume (should stay ~0).Testing
tests/config/test_db_routing.py) — allow-list routing, safe-vs-unsafe methods, state cleanup, one-shot fallback.tests/webapp/api/test_read_replica_routing.py) asserting per-alias query capture for the jobs, push, job-detail, group-summary, and Perfherder endpoints, plus a control proving a non-opted-in viewset stays on primary.transaction=True,databases=["default", "read_replica"]) since routed reads use a separate connection that only sees committed data.Follow-ups (not in this PR)
Once proven in prod, the next routing candidates are the Intermittent Failures views (
/api/failures/,/api/failuresbybug/,/api/failurecount/) — heavy date-range aggregations, GET-only, lag-tolerant, all in the already-allow-listedmodelapp.Next candidates
Failures,FailuresByBug,FailureCount(intermittents_view.py). Heavy date-rangeCount/TruncDateaggregations, GET-only, highly lag-tolerant, all in themodelapp → no allow-list change. This is the standout.InfraCompareView,NoteViewSet,BugJobMapViewSet,HashViewSet.tocommit,FilesBugzillaMapViewSet— with a flagged read-after-write nuance on notes/bug-job-map (classify→view) to decide on first.ChangelogViewSet(changelog app),UserViewSet(auth app).{model, perf}, so they're all "add mixin + test," no router change.