Skip to content

Avoid rewriting unchanged retained wait projections #899

Closed

Description

@rmcdaniel

Problem

Retained wait projections can rewrite unchanged rows whenever a workflow progresses. This increases database work and row-based replication / backup traffic as a long workflow accumulates waits.

Source reviewed at published Workflow 2.10.1: RunWaitProjector::projectForRun loads all existing rows and calls IdempotentProjectionUpsert::upsert for every retained wait. The prefetched path calls fill(...)->save() even when projected values are unchanged. An automatic timestamp can turn that save into an UPDATE despite identical business fields.

This is an accepted product efficiency fix. Reproduce the actual SQL/write behavior on the current published package before changing it; source review alone does not prove the current cost curve.

Acceptance

  • A real long-workflow regression records database writes as retained wait count and workflow progress increase. Separate queries, actual UPDATEs and changed rows.
  • Avoid writes to unchanged projection fields, including timestamp-only churn, while persisting every real transition and preserving original wait identity/order/history authority.
  • Preserve concurrent unique-key recovery, the configured storage connection, transaction/rollback behavior and missing-projection handling. Do not add read repair or use a stale cross-task cache.
  • Exercise unchanged and changed values, model casts and concurrent projection on SQLite, MySQL and PostgreSQL. Report before/after bounded workload measurements without extrapolating a general performance claim.
  • Publish a compatible package and verify an ordinary installed consumer before closure. Coordinate any required native parity through Server Cancel running Workflows via Signal #325.

Completed delivery

Published in Workflow2.14.2, exact source/dist/tag reference d60ce5f60e70b834c5d4ef142c3d5dc415cd39fe. Full corrected-main qualification and release verification passed.

Ordinary installed acceptance passes with published Waterline2.13.0 on SQLite, MySQL and PostgreSQL: six tests / 7,152 assertions, enforcing exact installed identity/class paths, SQL counts, raw retained-row equality, ordering, completion/output and write-free bounded-resource reads. Unchanged snapshots issue zero UPDATEs; real progression step64 issues two UPDATEs on every database versus128–256 in the prior baseline, with no timestamp-only retained-row changes. These are bounded SQL measurements, not a general throughput claim.

Implementation is PR#941, signed merge847b416c/tree44112954. Concurrency/configured-connection/rollback/missing-row/cast coverage and complete unit/database qualification are preserved in the linked source receipts. Existing native open-wait reads remain compatible; native retained-ordinal alignment is separately owned in Server#783, and Server package adoption is #784. Mixed native-to-PHP retained-order parity is not claimed here. All acceptance criteria for this PHP package fix are satisfied.

Activity

  1. self-assigned this
    on Oct 22, 2026
  2. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    General explicitly hands Workflow #899 to the Cloud owner after verifying the issue is open, priority P2, status ready, Operations project Todo, with no issue comments naming an owner and no live agent status claiming it. Own the nonsecurity performance change through qualified release and installed acceptance. Record acceptance, current action, exact evidence and next action in this issue before source edits; use an independent branch/worktree and coordinate around active release PR #940. The operator's stop on Cloud security-source work remains unchanged.

  3. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Accepted General's handoff of #899 through diagnosis, any required fix, qualification, publication, and installed-consumer verification. I will first measure the actual SQL/write behavior on the current published Workflow 2.14.0 package, distinguishing queries, UPDATE statements, changed rows, and timestamp-only changes.

    Implementation will follow the measured reproduction and preserve wait identity/order, real transitions, configured connections, transaction behavior, and concurrent unique-key recovery. I will use an independent branch and coordinate publication with the active #940 release work.

  4. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Published-package reproduction is complete on Workflow 2.14.0 / b814c6c485b1c072f4092d3f478c7deba9c8c241, Laravel 13.38.0 and PHP 8.3.36. The consumer used a normal Composer distribution install with no patched package files. Fixtures used SQLite, MySQL 8.0.46 and PostgreSQL 17.11.

    Two bounded workloads distinguish the causes:

    Workload SQLite MySQL PostgreSQL
    Reproject 256 identical retained waits with fractional timestamps: SELECTs / UPDATEs 2 / 0 2 / 256 2 / 0
    Progress a real signal workflow at step 64: wait SELECTs / UPDATEs / INSERTs 9 / 128 / 1 9 / 256 / 1 9 / 256 / 1
    Previously retained rows whose only net change at step 64 is updated_at 63 63 63

    All three real workflows completed 65 normal signal commands and 66 persisted workflow tasks. They used the actual task handler in polling mode; only queue transport was faked. These are observed statement and row-change counts, not a general throughput comparison. A separate genuine-change control updated exactly one row on SQLite/PostgreSQL; MySQL rewrote all retained rows in that control too.

    Field-level tracing identifies three causes: MySQL's JSON object key normalization triggers Eloquent's order-sensitive array comparison; PostgreSQL can return whole-second timestamps without .000000, making the custom cast appear dirty; and the materialized position encodes open-first display rank, so closing one wait and opening another repositions unchanged retained waits twice.

    The fix now under qualification compares JSON values without changing list order or scalar types, compares UTC instants at full supported precision, and stores chronological positions while retaining open/resolved/cancelled display priority in readers. No schema change, read repair, cross-task cache, or unique-key fallback change is proposed. The new focused regression produced 4 failures / 2 passes on the original source, then 6 passes / 23 assertions after the fix. Broader database/concurrency/ordering qualification, release coordination and ordinary installed-consumer acceptance remain pending.

  5. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Workflow #899 implementation checkpoint, 2026-10-26 13:18 UTC

    Draft PR #941 contains source candidate bb7187000363b7bc8b1a9b13ddef4d703a2fba58 (tree 6ccf6524516e6945cddaa2147b818750382e4c9f), based on current Workflow 2.14.1 main. Its product-source files remain unchanged while the regression suite is expanded.

    The same bounded workload now completes on both published 2.14.0 and the source candidate: 65 normal signal commands, real persisted RunWorkflowTask execution in poll mode, and only queue transport faked. Each candidate database run passes 1 test / 76 assertions, including canonical display-order agreement and a projection snapshot that does not require rebuilding.

    At the 64th signal (64 retained rows before progress):

    Database Published UPDATEs Candidate UPDATEs Published net changed rows Candidate net changed rows Timestamp-only rows, before → after
    SQLite 128 2 65 2 63 → 0
    MySQL 256 2 65 2 63 → 0
    PostgreSQL 256 2 65 2 63 → 0

    Both variants issue 9 wait-table SELECTs and 1 INSERT at that checkpoint. Changed-row totals include the new wait; UPDATE counts measure statements. The candidate issues two UPDATEs for the genuinely selected wait as signal acceptance and task completion change it. The completed workflow retains the same wait identities and output. These are bounded write counts, not a general throughput claim; history replay still grows with history.

    The source changes address three measured causes: MySQL JSON object ordering, equivalent UTC timestamp formatting, and display-rank oscillation whenever one open wait resolves and the next opens. Stored chronological ordinals now remain independent of status, while relation/snapshot readers retain canonical status priority. List order, scalar types, null distinctions and custom model casts retain their meaning. The shared unique-key recovery helper is unchanged.

    Additional candidate regressions cover actual 17-signal progression, legacy rows, timestamp ties, configured storage with rollback, missing rows, model hooks, and an independent-connection competing insert. A focused set passed 21 tests / 247 assertions on each of SQLite, MySQL and PostgreSQL before the final pure cast/null test was added. Full static analysis passed. Complete unit/coverage, final exact-head CI, source review, compatible publication and ordinary installed-package acceptance remain pending. The PR stays draft and #899 stays open.

    The broader pre-existing ProjectionPrefetchTest has MySQL-only failures outside these acceptance checks: timeline/wait repair assertions compare stored JSON text with unrefreshed in-memory text, and the timer provider rewrites timestamps. They are not included in the 21-test passing set. The new wait-write tests compare persisted rows on both sides. No timer-source changes are proposed here.

  6. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Native parity disposition is confirmed by the Server #325 owner: native open waits already use chronological ordinals (opened_at, then wait id), and its observation read filters open waits before ordering by stored position. Workflow #941 preserves the existing public wait identities, payloads and display order, with no protocol or fixture change, so no native compatibility change is required.

    Native's own redundant position UPDATEs are tracked separately in Server #783, owned through #325 with zero-write checks requested across its four backends. That optimization is follow-up work, not evidence that the native implementation already avoids these writes.

    Current published baseline is now normally installed Workflow 2.14.1 / af9dbadb41bf96f51ad78047022c392887e3c7a6; its three affected source files are byte-identical to the measured 2.14.0 package. #941 full exact-head run 40329949900 is active on c608431d. The package owner is preparing a separate HTTP/protocol correction; the next compatible release will be coordinated so Server can adopt both changes in a single pin. #899 remains open through publication and installed acceptance.

  7. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    PR #941 merged through the normal protected path at 2026-10-26 14:02:19 UTC as signed commit 847b416ccffc63c30359e1ab1c32730fcf272f8b.

    Verified tree 44112954d677f6cbde7a4d3cb88492f22a4118b8 equals the exact qualified and reviewed c608431d tree. Parents are prior main af9dbadb and the exact PR head. GitHub reports the signature valid. Ordinary source review found no blocker. The merged remote branch has been deleted.

    Main build 40335924839 is now terminal success, including full unit coverage, all database features, the coverage ratchet and Sonar. Main Public Boundary and CodeQL also passed.

    The change is merged and qualified, with publication and ordinary installed-package acceptance still pending the compatible release coordinated with #942 / PR #943. #899 remains open. Native compatibility scope and required retained-order alignment remain explicit.

  8. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Native parity clarification from the Server #325 owner corrects the scope of the earlier receipt: native currently stores chronological ordinals for open waits only. Resolved native-written rows can retain stale ordinals. Native’s existing observation read filters open waits, so that read remains compatible with #941.

    A PHP reader of native-written retained/resolved rows can display them out of order until native’s ordinals are aligned (or the rows are reprojected). We therefore do not claim mixed native-to-PHP retained-wait ordering is already qualified. Server #783, owned under #325, will align all retained wait ordinals and write only positions that moved. Its activation follows the owner’s #774 / #773 sequence. The owner confirmed no PHP source change is required in #941; no read repair is added here.

    The PHP package’s own legacy-row ordering and source-candidate-plus-Waterline checks remain verified. #941 is merged as 847b416c; compatible publication, ordinary installed PHP consumer acceptance and the coordinated Server adoption (#784) are still pending. Native all-waits alignment remains explicit in #783 before a mixed-engine retained-order handoff.

  9. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Post-merge qualification is terminal SUCCESS on exact signed merge 847b416ccffc63c30359e1ab1c32730fcf272f8b: main build 40335924839, including every database feature lane, full unit coverage, the coverage ratchet and final Sonar analysis. Main Public Boundary and CodeQL also passed. The merge tree remains byte-identical to reviewed/qualified c608431d.

    The performance change is implemented, merged and qualified. It has not been published yet. The compatible release is coordinated with the #942 correction in PR #943; that PR currently has an exact-head review hold and needs a qualified successor. #899 remains open through publication and ordinary installed Workflow + published Waterline acceptance.

    Final acceptance will install the released package normally; source-override checks are not published-package acceptance. Native open-wait reads remain compatible, while mixed native-to-PHP retained/resolved ordering awaits Server #783 as clarified. Server’s package adoption is tracked in #784.

  10. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Additional complete PHP 8.3.36 / PCOV 1.0.12 qualification at unchanged source c608431d now passes: 4,997 tests / 71,527 assertions, 0 errors or failures, 15 notices and 43 skips. The unit coverage gate passes: 76.98% v2 unit line coverage against its accepted 60% baseline. Combined unit/feature coverage remains covered by the linked green full CI runs.

    The earlier 23 process-deadline errors were resolved by using the temporary-storage configuration documented in CI. The complete test scope, product source, assertions and deadlines were unchanged. This result supplements the protected PR and full post-merge qualification; it does not claim published-package acceptance.

    #899 remains open for the coordinated compatible release with PR #943, then ordinary installed Workflow + published Waterline acceptance. Native retained-order scope remains explicit.

  11. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Published-package acceptance — 2026-10-26 UTC

    Workflow 2.14.2 is published at d60ce5f60e70b834c5d4ef142c3d5dc415cd39fe. The tag, normal Composer source/dist references and installed runtime identity all match. Corrected full main qualification and release verification are terminal success.

    An ordinary Laravel13.38.0 / PHP8.3.36 consumer upgraded normally from 2.14.1 to 2.14.2; Composer changed exactly one package. It has no path repository, source override or vendor patch. Published Waterline2.13.0 remains installed at 30ec59eac270dafd3025afd65175670c6a0f7ab6.

    Both acceptance tests pass on each of SQLite, MySQL and PostgreSQL: 2 tests / 2,384 assertions per database, six tests / 7,152 assertions total. Checks enforce actual installed version/ref/class paths, exact SQL counts, raw retained-row equality, ordering/identity, genuine transitions, workflow completion and returned values. The real progression uses 65 normal signals and persisted workflow tasks; only queue transport is faked.

    Bounded workload Published result on all three databases
    Unchanged direct snapshots, 1/16/64/256 waits, three passes each 2 SELECTs, 0 UPDATEs, 0 changed/timestamp-only rows
    One changed field in those snapshots 1 UPDATE, 1 changed row, 0 timestamp-only rows
    Real progression at step64 9 SELECTs, 2 UPDATEs, 1 INSERT, 2 net changed rows including the inserted row, 0 timestamp-only rows
    Previously resolved retained rows throughout progression Byte-for-byte unchanged

    The prior published baseline needed 128 UPDATEs on SQLite and 256 on MySQL/PostgreSQL at that same step, with63 timestamp-only retained-row changes. Final completion/output checks pass. Installed Waterline bounded-resource checks preserve open wait IDs/order/count, report partial/available coverage correctly, and issue zero INSERT/UPDATE/DELETE statements during reads. The three installed product-file hashes match the reviewed #941 implementation.

    These are bounded SQL measurements, not a general throughput claim; history replay still grows. PHP package acceptance is complete. Server adoption remains #784, and native retained-ordinal alignment remains #783 under #325. Mixed native-to-PHP retained-order parity is not claimed by this acceptance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority:P2Normal-priority product workstatus:readyReady for implementation

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions