Summary
Production relation-resolution jobs exceed PGQueuer's five-minute execution budget because Core resolves each unique unresolved target serially. A production-shaped project had 9,913 unique unresolved targets; the database had the expected indexes, so this is an algorithmic round-trip problem rather than a missing-index incident.
The root fix is bulk/batched resolution or durable bounded chunks with resumable progress. Extending the job timeout would only move the failure threshold.
Production evidence
Observed release: Cloud release 837b8c258ddb9ca78d0d9d897cec9202f7650c28, pinning Core revision de4589d24155332002709f5529c9bdd4937861a4.
Logfire fingerprints:
- #2350 —
cd690865-6a74-4d91-88b3-db2193a2f8c5
- #2347 —
e328fa25-b51a-4a11-91eb-b1a61917fad5
- #2341 —
0c586488-853b-4203-96ab-1eabc059208e
Representative trace: five-minute terminal relation-resolution job.
The representative job terminated at five minutes. Read-only production database inspection found:
- 1,483 entities
- 46,988 relation rows
- 17,062 unresolved relation rows
- 9,913 unique unresolved target strings
- the expected B-tree/trigram entity indexes and unresolved-relation index
Tenant and project identifiers are intentionally omitted.
Causal chain
RepositoryRelationResolutionRuntime.resolve_relations caches the set of unique target strings.
- It still awaits
LinkResolver.resolve_link once per target, serially.
- Each resolution can call
_resolve_in_project and EntityRepository.get_by_permalink, producing another database round trip.
- Roughly 10,000 sequential resolutions cannot complete inside the worker budget.
- The job is terminated and the graph retains unresolved work.
The existing indexes support the individual lookups; they do not compensate for the serial N-target control flow.
Required outcome
Resolve target strings in bulk/batches while preserving the existing link contract, or process them as durable bounded chunks with explicit resumable progress. The implementation may combine both: bulk-fetch candidate entities per chunk, apply normal ambiguity/qualification rules in memory, persist resolved targets, and checkpoint progress.
Core owns these semantics because the same resolver serves local and hosted runtimes.
Non-solutions
- Increasing the PGQueuer timeout
- Adding another retry around the same five-minute algorithm
- Dropping unresolved relations
- Replacing precise link semantics with a fuzzy first-match shortcut
Acceptance criteria
Summary
Production relation-resolution jobs exceed PGQueuer's five-minute execution budget because Core resolves each unique unresolved target serially. A production-shaped project had 9,913 unique unresolved targets; the database had the expected indexes, so this is an algorithmic round-trip problem rather than a missing-index incident.
The root fix is bulk/batched resolution or durable bounded chunks with resumable progress. Extending the job timeout would only move the failure threshold.
Production evidence
Observed release: Cloud release
837b8c258ddb9ca78d0d9d897cec9202f7650c28, pinning Core revisionde4589d24155332002709f5529c9bdd4937861a4.Logfire fingerprints:
cd690865-6a74-4d91-88b3-db2193a2f8c5e328fa25-b51a-4a11-91eb-b1a61917fad50c586488-853b-4203-96ab-1eabc059208eRepresentative trace: five-minute terminal relation-resolution job.
The representative job terminated at five minutes. Read-only production database inspection found:
Tenant and project identifiers are intentionally omitted.
Causal chain
RepositoryRelationResolutionRuntime.resolve_relationscaches the set of unique target strings.LinkResolver.resolve_linkonce per target, serially._resolve_in_projectandEntityRepository.get_by_permalink, producing another database round trip.The existing indexes support the individual lookups; they do not compensate for the serial N-target control flow.
Required outcome
Resolve target strings in bulk/batches while preserving the existing link contract, or process them as durable bounded chunks with explicit resumable progress. The implementation may combine both: bulk-fetch candidate entities per chunk, apply normal ambiguity/qualification rules in memory, persist resolved targets, and checkpoint progress.
Core owns these semantics because the same resolver serves local and hosted runtimes.
Non-solutions
Acceptance criteria