Skip to content

Release catalog locks before worker ranking - #72

Merged
abhi1693 merged 2 commits into
masterfrom
fix/catalog-publication-locks
Oct 4, 2026
Merged

abhi1693 merged 2 commits into
masterfrom
fix/catalog-publication-locks

Conversation

@abhi1693

@abhi1693 abhi1693 commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

What changes and why?

Analysis workers still loaded and ranked the full topic/tag catalog while holding source, article and topic SHARE locks. They repeated catalog work during publication checks and when queueing replacements for superseded results. A catalog writer had to wait for that Python work to finish even when it was unrelated to the edited topic.

The original production audit measured 541 ms average topic write-lock execution. A new read-only PostgreSQL window (2026-10-03 23:04:00–23:06:06 UTC) recorded 11 write-lock calls averaging 345.6 ms. It caught a writer waiting on ShareRowExclusiveLock, blocked by a reader holding ShareLock while idle in its transaction on ClientRead (reader transaction age 608 ms, writer transaction age 318 ms). These are production baseline observations; production remains unchanged.

Workers now load the current catalog and article evidence, close the read transaction, and prepare candidate results and identity membership before acquiring application locks. Under the existing locks they verify PostgreSQL catalog revisions and the prepared article content, then reuse those results for validity, evidence, publication and superseded-job checks. A change rolls back the application transaction and prepares again outside locks, reusing the same inference result. After three preparation attempts, continued churn follows the existing durable dependency-failure retry policy. Job leases, source approval, editorial revisions, evidence validation and publication policy keep their existing checks.

Inference prompt ranking also runs after its read transaction closes. Full-catalog reuse scopes start after inference returns, so this change does not retain a full catalog through the external model wait.

On a synthetic catalog matching production size (4,450 topics and 34,057 tags), median catalog lock holding fell from 602.2 ms to 52.4 ms (91.3% less) across three paired runs, with identical published results and one inference call per run. The comparison used the parent PR worker versus this worker, warm Redis/catalog indexes, a fake model and disposable PostgreSQL. Timing runs from successful table-lock acquisition through database commit completion; fixture setup is excluded. This measures local lock holding, not production write-lock wait latency.

Related issues

Depends on #71 for revision-checked catalog reuse and migration 0022. #71 is merged, and this PR now targets master. The branch is updated after #69 and #71; its remaining diff contains only the worker follow-up. Merge this lock fix before updating the overlapping service-boundary refactor (#79).

Validation

  • bash .github/scripts/python-checks.sh: Ruff lint/format, mypy, version consistency and pre-commit configuration passed.
  • Commit hooks passed, including the full backend unit suite.
  • Focused final regression suite: 86 passed, including nine real PostgreSQL application/concurrency tests. Existing full-automation and automation regressions also passed in the broader targeted run (176 passed).
  • A catalog writer commits while worker application ranking is deliberately paused. After the catalog revision changes, the worker prepares again and publishes the same valid result, with exactly one inference call.
  • Ranking instrumentation confirms that it runs with no publication/catalog locks and no checked-out database connection on the worker thread, including replacement requests after changed content.
  • Races cover topic identities, tag edits, article content, editorial revisions, rejected and disabled sources, lease replacement, and continuous catalog churn. Removed IDs remain invalid even with prepared membership.
  • Full disposable CI integration suite: 1,120 passed with no skips, including PostgreSQL/Redis, imgproxy and database-failure recovery checks.
  • No UI change; browser screenshots and extension packaging are not applicable.

Rollout notes

This follow-up adds no migration. It requires #71 and its migration 0022 so topic description edits invalidate retrieval revisions. Deploy through the normal migration-first release. The dependency is merged; deploy its migration before starting the updated services.

Repeated edits can exhaust the three local preparation attempts. Existing dependency retries may rerun inference on a later delivery, with the existing attempt limit and backoff. No stale result is applied during churn. The lock order and topic lock modes remain unchanged; the improvement comes from moving catalog computation outside the critical transaction.

After release, compare topic write-lock mean latency, blocking chains and scheduler/worker resource use over matching windows. This work did not mutate or deploy production.

Merge queue validation

Retargeted to master after #71 merged, then updated against that merge. The changelog conflict retains the worker-lock fix, scheduler/catalog fix and authentication-redaction fix. 76 analysis/unit regressions passed. 28 focused real PostgreSQL 18 / Redis 8 lock and catalog regressions passed, with no skips. They verify concurrent writers, ranking without held connections/locks, stale-input and ownership rejection, and bounded churn retries. Disposable services were removed after the run. Full hosted CI, CI required, CodeQL and Sonar passed on commit e7b7f671. Hosted suites passed with 2,620 backend unit tests, 1,120 integration tests, 785 reader tests and 647 admin tests, with no skips. Populated-schema upgrades, web/Chrome/Edge parity and all ARM64 image build/scan/smoke checks passed. Sonar MCP confirms 95.6% new-code coverage, 0.0% duplication, and zero new bugs, vulnerabilities or code smells. The separate GitHub-managed AI review remains quota-blocked, independently of these passing checks. Auto-merge is disabled.

Ready for review

  • The description explains the change and the validation results, including any gaps.
  • Relevant documentation and regression tests are updated.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

PR reader API performance

Result: INCONCLUSIVE

7 inconclusive · 1 within threshold. Zero request failures across all six runs.

Base d4030c8ba37ac2dc1de0625417d985f26521a2d3 → PR e7b7f6712e6b50bc6fbdad0bcaf8369e50d39146

Three independent runs per revision, each on a fresh GitHub-hosted ARM64 runner. 16 users, 60 seconds/run, 1,000 synthetic articles, cache off, five PgBouncer slots.

Latency comparison

All latency values are in milliseconds; lower is better. Values are medians of three runs.

Route Base p95 PR p95 Change Result
All requests 98 98 +0 (+0.0%) Inconclusive
Latest feed 130 130 +0 (+0.0%) Within threshold
Feed pagination 93 92 -1 (-1.1%) Inconclusive
Article detail 75 75 +0 (+0.0%) Inconclusive
Source feed 90 96 +6 (+6.7%) Inconclusive
Topic feed 89 88 -1 (-1.1%) Inconclusive
Feed filters 54 78 +24 (+44.4%) Inconclusive
Sources 39 39 +0 (+0.0%) Inconclusive
Topics 37 55 +18 (+48.6%) Inconclusive
Additional latency and throughput metrics

Each cell shows base → PR. Latencies are in milliseconds.

Endpoint p50 p99 Requests/s
Aggregated 31 → 28 150 → 150 29.89 → 30.45
/v1/feed [latest] 46 → 45 190 → 180 8.55 → 8.75
/v1/feed [page] 30 → 27 120 → 120 3.56 → 3.62
/v1/articles/[id] 18 → 19 100 → 120 6.32 → 6.09
/v1/feed [source] 29 → 29 140 → 120 1.84 → 2.06
/v1/feed [topic] 28 → 27 150 → 140 1.82 → 1.82
/v1/feed/options 21 → 21 73 → 110 2.56 → 2.66
/v1/sources 11 → 12 62 → 76 2.56 → 2.66
/v1/topics 11 → 13 70 → 91 2.56 → 2.66
Individual runs
Run Requests Failures Exit code
Base 1 1680 0 0
Base 2 1686 0 0
Base 3 1634 0 0
PR 1 1715 0 0
PR 2 1669 0 0
PR 3 1709 0 0
How to interpret this report

Regressed / Improved: a p95 regression needs >20% and >20 ms increase in the medians and at least two head samples versus the base median. Improvements use the inverse threshold. Within threshold means the change did not meet both thresholds.

Inconclusive: a within-revision p95 range >35% of its median and >20 ms is too noisy for a confident latency verdict.

Inconclusive latency comparisons are non-blocking: the check passes without claiming an improvement. Confirmed regressions and test/report errors fail the PR gate. Throughput is descriptive: paced users do not measure maximum capacity. This uncached public API test does not establish production health, browser performance or worker capacity. Separate runners can differ in hardware or host load; repetitions and noise checks reduce but cannot eliminate that uncertainty.

Full HTML/CSV reports and logs · attempt 1

@gitar-bot

gitar-bot Bot commented Oct 4, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

🟡 Medium risk · Worker publication preparation retries on catalog/content changes before applying results.

Moves catalog loading and ranking outside application locks, reducing lock-holding time from 602.2 ms to 52.4 ms (91.3% reduction) on synthetic production-scale data. Workers now prepare candidate results before acquiring locks, verify catalog revisions under locks, and reuse prepared data for validity and publication checks. Comprehensive testing confirms identical published results with no regressions across 1,120 integration tests.

Review coverage

📋 Rules No rules evaluated

🧪 Functional validation Not enabled · Set up

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

@abhi1693
abhi1693 merged commit 94ca922 into master Oct 4, 2026
57 checks passed
@abhi1693
abhi1693 deleted the fix/catalog-publication-locks branch October 4, 2026 11:16
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