Skip to content

test: stabilize queues worker-recovery and pgvector read-replica CI flakes - #18078

Merged
nathanlentz merged 5 commits into
mainfrom
fix/queues-worker-recovery-flaky-timing
Sep 10, 2026
Merged

test: stabilize queues worker-recovery and pgvector read-replica CI flakes#18078
nathanlentz merged 5 commits into
mainfrom
fix/queues-worker-recovery-flaky-timing

Conversation

@nathanlentz

@nathanlentz nathanlentz commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

I'm trying to fix some flakey int tests

Why

Two int suites failed intermittently in CI across different adapters:

  • Queues worker recovery (int-sqlite-uuidv7, and the same flake on mongodb-atlas at queues:1469). The "healthy long-running job" test set a 600ms lease with a 50ms safety buffer and waited 900ms. That left almost no room for heartbeats to renew the lease. On a loaded CI runner, one delayed heartbeat passed the margin. The worker's own completion update was then rejected as lease-expired (JobRunAbortedError) and removed from jobStatus, so the test saw undefined instead of success.
  • pgvector read replica (int-postgres-read-replica). The three vector-column tests read query results through the raw payload.db.drizzle handle right after payload.create(). That handle is wrapped by withReplicas(), so its reads go to a replica by default. Payload routes its own reads to the primary for 2s after a write, but a raw handle read skips that routing. The read hit the lagging replica and returned stale or empty rows (expected length 2, got 0/1).

How

  • Widen the worker-recovery lease margin from duration=600 / safetyBuffer=50 to duration=5000 / safetyBuffer=300, and wait 6000ms. This keeps the same duration-to-buffer ratio, so a single CI-scale scheduling stall no longer passes the margin. This resolves the flake on both sqlite-uuidv7 and mongodb-atlas.
  • Read the pgvector assertions through payload.db.primaryDrizzle, and fall back to payload.db.drizzle when no replica is configured. This gives the test read-after-write consistency without changing framework behavior. It mirrors how the shared reset helper already reads from the primary.

Validation

  • The pgvector flake reproduced locally on the first run against postgres-read-replica. After the fix, the file passed 8 out of 8 runs on postgres-read-replica and stayed green on plain postgres through the fallback.
  • The worker-recovery change keeps the original lease semantics and only increases the timing headroom.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 214.77 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.88 MB ✅ -18 B (-0.0%)
packages/payload/meta_shared.json esbuild/exports/shared.js 213.70 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 286.50 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 36.54 KB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.95 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▊ }}}$ 99.0%, 210.74 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 718 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.3%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.2%, 526 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 409 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████▌ }}}$ 74.3%, 1.39 MB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 46.72 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 44.42 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.02 KB
dist/auth/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 15.59 KB
dist/globals/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 14.30 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 14.29 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 13.65 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.88 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.72 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.95 KB
dist/cli/commands ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.92 KB
dist/config/orderable ${{\color{Goldenrod}{ }}}$ 0.4%, 8.10 KB
dist/database/migrations ${{\color{Goldenrod}{ }}}$ 0.4%, 7.99 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ }}}$ 0.4%, 7.89 KB
dist/index.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.79 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ }}}$ 0.4%, 7.65 KB
dist/utilities/entityInputSchema ${{\color{Goldenrod}{ }}}$ 0.4%, 7.34 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.07 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.3%, 6.17 KB
(other) ${{\color{Goldenrod}{ ██████▍ }}}$ 25.7%, 480.29 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▉ }}}$ 71.8%, 150.13 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▎ }}}$ 5.1%, 10.69 KB
dist/fields/config ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 5.83 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 4.45 KB
dist/collections/config ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 3.33 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.69 KB
dist/config/client.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.69 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.41 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ }}}$ 0.4%, 939 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.4%, 793 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.3%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.3%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.3%, 632 B
dist/errors/ValidationError.js ${{\color{Goldenrod}{ }}}$ 0.3%, 577 B
(other) ${{\color{Goldenrod}{ ███████ }}}$ 28.2%, 58.99 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▎ }}}$ 13.1%, 37.20 KB
dist/lexical/ui ${{\color{Goldenrod}{ ███ }}}$ 12.1%, 34.20 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.7%, 33.01 KB
dist/features/table ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 27.22 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.6%, 18.82 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 17.45 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 14.28 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.9%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▉ }}}$ 3.5%, 10.02 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 9.61 KB
dist/features/converters ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 8.36 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.14 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.64 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.28 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.88 KB
dist/lexical/nodes ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.66 KB
(other) ${{\color{Goldenrod}{ █████████████████████▋ }}}$ 86.9%, 246.09 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/exports/client ${{\color{Goldenrod}{ █████████████████████████ }}}$ 100.0%, 26.90 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.5%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▌ }}}$ 14.5%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.3%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.2%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/layouts/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 230 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 158 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
(other) ${{\color{Goldenrod}{ █████████████████▍ }}}$ 69.5%, 12.68 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@nathanlentz nathanlentz changed the title fix(queues): widen worker lease margin to fix CI flake in worker recovery test fix: widen worker lease margin to fix CI flake in worker recovery test Sep 1, 2026
…very test

The "healthy long-running job" worker recovery test used a 600ms lease
duration with only a 50ms safety buffer, leaving ~550ms for heartbeats
to renew the lease. On a loaded CI runner a single delayed heartbeat
can exceed that margin, causing the worker's own completion update to
be rejected as lease-expired (JobRunAbortedError), which gets silently
filtered out of jobStatus.

Widen the margin to 5000ms/300ms so a single CI-scale scheduling stall
can't exceed it, while keeping the same duration/safetyBuffer ratio.
@nathanlentz
nathanlentz force-pushed the fix/queues-worker-recovery-flaky-timing branch from 05c459f to 9d946ac Compare September 1, 2026 19:26
AlessioGr
AlessioGr previously approved these changes Sep 1, 2026
… flake

The vector column tests read query results through the raw payload.db.drizzle
handle right after payload.create(). That handle is wrapped by withReplicas(),
so its reads default to a replica and bypass payload's read-after-write routing,
returning stale/empty rows on the postgres-read-replica adapter. Read through
payload.db.primaryDrizzle (falling back to drizzle when no replicas are
configured) so the assertions see the just-written rows.
@nathanlentz nathanlentz changed the title fix: widen worker lease margin to fix CI flake in worker recovery test test: stabilize queues worker-recovery and pgvector read-replica CI flakes Sep 2, 2026
AlessioGr
AlessioGr previously approved these changes Sep 2, 2026
…overy-flaky-timing

# Conflicts:
#	test/database/postgres-vector.int.spec.ts
Replace the three duplicated read-through-primary blocks in the vector column
tests with a single primaryDb() helper, so the read-replica handling and its
explanatory comment live in one place.
@denolfe

denolfe commented Sep 10, 2026

Copy link
Copy Markdown
Member

/ai-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues

Minor (nice to have)

  • [test/database/postgres-vector.int.spec.ts:14] — The comment "Read through the primary. payload.db.drizzle is the withReplicas() handle whose reads default to a replica, which races replication lag right after a write." explains what the code does rather than why the constraint exists at a deeper level. It's reasonably informative, but "which races replication lag right after a write" could be tightened — the key "why" is that the test writes a record and immediately queries it, so reading from a replica risks returning stale data before replication catches up. The current wording conveys this adequately, so this is purely cosmetic.

Recommendations

The primaryDb helper in postgres-vector.int.spec.ts is a well-scoped fix for a genuine race condition: after a write, reading from a replica-routed handle can return stale data before replication catches up. Accessing adapter.primaryDrizzle ?? adapter.drizzle directly from tests does reach into an adapter internal, but there's no public API for this scenario and the fallback makes it safe when primaryDrizzle is absent.

The queue test timing changes (duration 600 → 5000, safetyBuffer 50 → 300, wait 900 → 6000) substantially lengthen a CI test (6 seconds of wait alone). If flakiness at the old values was the motivation, the change is justified, but it may be worth documenting in a comment why the specific values were chosen and whether they were the minimum needed to eliminate the flake.

Assessment

Ready to merge? Yes

Reasoning: Both changes are targeted bug/flakiness fixes with no correctness, security, or convention violations. The primaryDb helper correctly handles the replica-lag race condition, and the queue lease timing values are straightforward numerical increases to prevent test flakiness.

@nathanlentz
nathanlentz merged commit 06f05f7 into main Sep 10, 2026
283 checks passed
@nathanlentz
nathanlentz deleted the fix/queues-worker-recovery-flaky-timing branch September 10, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants