Skip to content

Server fails behind PgBouncer transaction pooling while reporting ready #524

Closed
durable-workflow/durable-workflow.github.io
#311

Description

@rmcdaniel

Problem

The self-hosting guide says a connection proxy such as PgBouncer between the Server and its database "is permitted; it does not change any guarantee". Transaction pooling is PgBouncer's common production mode, and the Server does not work under it. The Server's pgsql connection uses PDO server-side prepared statements and has no setting to turn them off. The Server still reports itself ready while every workflow fails.

Reproduction

Published durableworkflow/server:2.9.7 from docker-compose.published.yml on Postgres 16, with PgBouncer 1.26.1 in front of every Server process. Two PyPI durable-workflow==2.6.1 workers ran concurrent one-activity workflows.

Configuration Result
Direct to Postgres 10/10 and 20/20 completed, no SQL errors
Session pooling 10/10 and 20/20 completed, no SQL errors
Transaction pooling, max_prepared_statements=0 Migrations work. 0/10 completed. Hundreds of prepared statement "pdo_stmt_…" does not exist / already exists errors, cascading 25P02 current transaction is aborted, and twice 08P01 bind message supplies 8 parameters, but prepared statement … requires 2, meaning one client's execute reached another client's statement. Starts returned 503 workflow_v2_blocked or 500.
Transaction pooling, max_prepared_statements=200 (the 1.26 default) Bootstrap fails at the first migration. pdo_pgsql's SQL DEALLOCATE fails on the server about 2,000 times in 30 s, which aborts transactions. 0/10 workflows started.

In every failing configuration the container health check (/api/ready) stayed healthy.

Acceptance criteria

  • The Server runs correctly behind PgBouncer in transaction mode, under both prepared-statement settings. It never sends one client's statement to another client's connection.
  • /api/ready fails, with a reason, when the database path cannot serve workflow queries.
  • The self-hosting guide states which pooler modes and settings are supported, verified against a published image.
  • A published-image test covers direct, session-pooled and transaction-pooled Postgres with concurrent workers.

Activity

  1. added
    bugSomething isn't working
    kind:defectA public product behavior is incorrect
    priority:P1High-priority product or release risk
    status:readyReady for implementation
    on Oct 20, 2026
  2. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Accepted as P1 product correctness work, including readiness, rather than a documentation-only caveat. The active #518 correction is now in draft #525; this is the next PHP database-path priority while its normal gates finish.

    I will first preserve the reported direct/session/transaction-pooling counterfactual on the published image, including both prepared-statement settings, then qualify the smallest PDO/database-path correction with concurrent workers, bootstrap/migrations, typed stored values and transaction rollback. Readiness must exercise the relevant configured database path and report its actual scope. The existing connection-only check does not prove that path can execute workflow queries.

    PgBouncer's current prepared-statement documentation explicitly distinguishes protocol-level tracking from SQL DEALLOCATE, which it forwards. That supports investigating the PDO statement lifecycle; it does not establish the eventual fix by itself. No correction or supported published tuple is claimed yet. The self-hosting guidance will follow the verified product behavior.

  3. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Source correction and local qualification for #524 are ready in draft #531.

    Final source is 2e59f1b38275581d58e0aeb86e4ff8fba858342b / tree c11d5ccb68e013dc5c89e87ec243d3574083ee8e, based on main 1638681911a. Native/shared/runner/OpenAPI/Composer/version/catalog/frozen files are untouched.

    The PostgreSQL PDO connection now uses native parameter binding without session-scoped statement names. /api/ready additionally executes a bound read through the primary connection and reports its actual scope; failed reads receive database_query_failed. Write capacity remains explicitly unverified. The lightweight runtime bootstrap guard gains no query on its hot path.

    Local counterfactual: PHP8.3.35 / PostgreSQL17.11 / PgBouncer1.26.0. Direct and session controls pass on unchanged source. Transaction pooling with tracking0 fails with SQLSTATE26000; tracking200 fails inside a real multi-query transaction with25P02. Official PHP source explains its named statement's SQLDEALLOCATE lifecycle; the correction instead uses native bound execution without that session state. This is a separate tuple from the report's PostgreSQL16/PgBouncer1.26.1 observation.

    Final source controls PASS: 50 readiness/ownership-protocol/CI-contract tests,649 assertions; eight actual direct/session/transaction0/transaction200 controls,430 assertions, zero skips. Transaction cells prove that one logical client's physical backend PID actually changes while preserving text/int64/null values, mixed parameter arity and transaction rollback. Four separate ownership process controls pass (SQLite+PostgreSQL),72 assertions; four unavailable MySQL/MariaDB cases are explicit skips. The ordinary PHP feature job now runs all four pooler configurations with its existing PostgreSQL service, pinned PgBouncer image and scoped always-cleanup.

    The PDO component also passes40 fresh normally installed Python2.6.1 one-activity originals, ten per direct/session/transaction0/transaction200 endpoint. Each cell uses two real Worker processes and a separate cold Client; all eight Workers exit0 and all40 cold results preserve int64 maximum/null/Unicode. Fresh pooled databases each bootstrap through their own pooler endpoint. Original run30s/execution60s budgets are unchanged. Stopped read-only SQL per cell proves10 completed runs/activities,30 completed first-attempt tasks,60 events/10 completions,zero repairs/registrations and exact stored deadlines. Data-only fingerprints before/after inspection match. This component evidence is distinct from the final source's fresh normal CI and eventual published-image qualification.

    Unsuccessful originals stay unsuccessful: the first20-workflow direct observation hit its local20s result wait; a separate ten-workflow original completed business work but its cold observer called the wrong public method. Corrected fresh originals pass without changing runtime source or original workflow budgets. The readiness fixture's initial subclass signature and read-only audit's initial column/table guesses were observer failures corrected before their confirming controls. The PHP built-in API launcher required137/noOOM after SIGTERM; graceful API shutdown is not qualified.

    The issue stays open. Final normal CI/Sonar, combined-source qualification and the required exact published-image direct/session/transaction acceptance remain ahead. No published fix, unrestricted proxy support, Cloud adoption or historical common cause is claimed.

  4. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Source checkpoint: #531 merged normally at 180019992a52845a79d201fc36b7ec5f38d88b4b, whole tree 9f16701bf259f28d112324de3bebcdf310141129, exactly the reviewed/candidate-qualified 4e837448. Parents are current-main 5084d240 and 4e837448; native/conformance bytes remain unchanged. The completed remote head is deleted and absent.

    All applicable candidate PHP/repository/performance/boundary/Sonar checks pass. I independently read the final PHP job:2,911 tests,65,226 assertions,one skip,six deprecations; the pinned PgBouncer cells are provisioned in that job, and its extra release guards pass9/27 and1/12. The original fixture transaction leak and stale expectation remain failed observations, corrected without claiming a production database cause. Sonar's five reviewed locations are resolved by source changes; no quality-profile or threshold change.

    Fresh main PHP39418873080, performance39418873098, Sonar39418873138 and boundary39418873275 are independent and pending. Source merge is not published-image delivery. Next is the routine2.9.8/Helm0.1.184 metadata preparation with unchanged Core2.7.2/package references, exact-source qualification and immutable-image direct/session/transaction acceptance, including the reported PostgreSQL16/PgBouncer1.26.1 tuple. #524 stays open. No Server518 routing release, native/shared/frozen adoption or Cloud action is implied.

  5. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Reopened until released delivery is verified. The earlier source PR's automatic closure preceded publication; source qualification alone does not give existing users an installable fix.

    The source correction #531 and its fresh-main checks pass. Release metadata #537 is now merged at 361a96b905ef927c06bbc85238b34b857e875feb / tree cb8ee0c4c383a7ed0692a06217e2f955f8a52bae, identical to its independently qualified candidate. Its fresh-main checks are running separately. Server2.9.8 / Helm0.1.184 are not yet published.

    The next required outcomes are immutable publication and actual installed acceptance on direct PostgreSQL and session/transaction poolers, including the reported PostgreSQL16/PgBouncer1.26.1 tuple, before closing this issue as delivered. The existing source/local controls and all unsuccessful original observations remain scoped to their recorded artifacts.

  6. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    The independent main checks for 361a96b905ef927c06bbc85238b34b857e875feb / tree cb8ee0c4c383a7ed0692a06217e2f955f8a52bae have all passed, including PHP feature/performance, readiness/topology, rolling, Sonar and protected chart/boundary checks. Conditional cancellation/prepared-local qualification remains skipped.

    Immutable tag 2.9.8 now points to that exact commit. The protected release workflow is publishing the image; this is not yet an installed-image acceptance claim. The remaining #524 acceptance is the published image against PostgreSQL 16 / PgBouncer 1.26.1 in direct, session and both transaction-pooling prepared-statement modes. The original failing controls remain retained. #524 stays open until that verification completes.

    The PHP release preserves the qualified native/shared/frozen bytes; Workflow remains the existing locked 2.7.2 dependency. A later package adoption is a separate source window and qualification.

  7. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Published Server 2.9.8 acceptance

    Exact source 361a96b905ef927c06bbc85238b34b857e875feb, whole tree cb8ee0c4c383a7ed0692a06217e2f955f8a52bae. Independent main checks and protected release 39434334201 pass, including immutable Helm 0.1.184 publication. The PHP package lock remains Workflow 2.7.2; this image does not adopt separately published 2.7.3.

    • OCI index: sha256:d84df4a0baed50204e821cfe458523aff22f9a3949d48851044442386840e7db
    • amd64 child: sha256:76c5b6b7cc78aeb02dd1ff9f87016662991c11f9af7dba4aaae7748bc87a1ad1
    • arm64 child: sha256:c509e67817382833471a7a257b4664dd124e511d148c8104b818e81cd964c20d

    The actual acceptance used the immutable amd64 child; its image labels independently match source/version. No product-source overlay or patched package was mounted.

    PostgreSQL 16.15 / PgBouncer 1.26.1 path Normally installed Python 2.6.1 originals
    Direct PostgreSQL 10/10 pass
    Session pooling 10/10 pass
    Transaction pooling, max_prepared_statements=0 10/10 pass
    Transaction pooling, max_prepared_statements=200 10/10 pass

    Each cell has two actual concurrent SDK workers exiting 0, ten exact int64/null/Unicode results and a separate cold reader. Stopped read-only SQL independently confirms 10 completed runs / 10 completed first-attempt activities / 30 completed first-attempt tasks / 60 events / 10 workflow completions / zero repairs or retained registrations, with exact original start+30-second run and start+60-second execution deadlines. No SQLSTATE/PDO/fatal error was found in the four retained API logs.

    All four actual readiness replies identify bound_read_query and explicitly leave write capacity unverified. A separate unavailable-database original returns 503 / not_ready / backend_unavailable. The first negative observer incorrectly expected a ready field and failed; the corrected separate readback asserts the real published envelope. No business original, request budget or test assertion was relaxed.

    All three PgBouncer roles and PostgreSQL exit 0 / no OOM. The four multi-process HTTP fixture roles require 137 / no OOM after the business clients and cold readers complete, so this is not graceful HTTP-shutdown qualification. The original published 2.9.7 failures and source counterfactuals remain separate and failed. No native runtime, frozen actor, TLS, performance or Cloud adoption claim follows.

    The remaining #524 follow-through is the self-hosting guide's precise supported-mode/version/readiness note and resource-retirement receipt. The exact source/image handoff is available for independently qualified combined integration; no pass transfers to a changed tree or dependency tuple.

  8. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Self-hosting documentation follow-through is PR Website #327. The original ddaac6efe0 candidate passed all six normal checks, including the eight-language MDX/link build at 14:37:29 UTC and Sonar with zero unresolved PR issues / 0.0% new duplication.

    After that original became terminal, the branch incorporated merged callback documentation main 3dbbf9a888 once. The new exact source is d5982f00c74875fe692b6dd76db95580a85ceab8, whole tree 408c34b6bd1fa1f758186a0c90dc967cacc11d28. Its diff from current main remains only Deployment EN+7 and the corresponding review keys (15 files). Six translation regressions and the full 105-guide reviewed-source/code inventory pass on this combined source; fresh whole-head normal CI is pending, without carrying the original candidate pass.

    The guide states the fixed Server 2.9.8+ compatibility floor, links the published PostgreSQL 16.15 / PgBouncer 1.26.1 acceptance, lists direct/session/transaction modes with both tested max_prepared_statements=0 and 200, requires ordinary bootstrap, and distinguishes /api/ready's bound read from write capacity. No version-manifest, product, runtime, SDK, schema, native, Cloud or pool-tuning change is included.

    No additional application/DB cohort was run. The focused ephemeral UID1000 Node checkers auto-removed; all temporary translation/check/posting helpers are gone. Exact-main checks, source publication, actual Pages and eight live Deployment routes remain required before this documentation follow-through is delivered. Server #524 remains open for that final acceptance.

  9. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    The documentation follow-through is source-merged through Website #327: 14:50:27 UTC, exact main e36d6c113fefda6c448b628270de396a46e7eec9, whole tree 408c34b6bd1fa1f758186a0c90dc967cacc11d28, identical to reviewed candidate d5982f00c7.

    All six fresh candidate checks pass, including full eight-language MDX/link qualification at 14:48:12. Independent Sonar PR review reports zero unresolved findings and 0.0% new duplication under unchanged settings. The original ddaac candidate's six passing checks remain separate; the combined head received its own qualification. Remote source head was deleted.

    Only the Deployment guide EN+7 and its review keys changed. The text now states the Server 2.9.8+ transaction-pooling compatibility floor, links the exact published PG16.15/PgBouncer1.26.1 direct/session/transaction0+200 acceptance, requires ordinary bootstrap and limits readiness to its bound read/configured checks without a write-capacity guarantee. No product, version manifest, SDK, schema, native or Cloud adoption is included. No new application/DB test stack was needed; ephemeral Node checkers and helpers are removed.

    Fresh exact-main checks, source publication, actual Pages and eight live Deployment routes remain pending. This is a source checkpoint, not the final #524 documentation acceptance. The issue stays open until those gates complete.

  10. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    PgBouncer self-hosting documentation delivered

    Website PR #327 is merged and publicly verified in all eight offered languages. Exact main e36d6c113fefda6c448b628270de396a46e7eec9, whole tree 408c34b6bd1fa1f758186a0c90dc967cacc11d28, matches qualified candidate d5982f00c7.

    • All six fresh candidate checks and seven independent main checks pass. Main eight-language Docs 39442911542 passed 14:57:36 UTC; source publication 39442911387 passed 14:55:54 UTC.
    • Generated Pages 3ac51f138ef5f341718c184c2c50fc518fc6c31d explicitly deploys that main. Actual Pages 39443730353 passed 14:56:54 UTC.
    • Actual public readback 14:58:47–14:58:48 UTC passes eight Deployment routes: English, German, Spanish, French, Japanese, Brazilian Portuguese, Ukrainian and Simplified Chinese. Every HTTP 200 HTML body matches its exact generated page, with correct HTML language, translated H1, canonical, SQL-budget anchor and no English-fallback marker. The fixed-version floor, exact PostgreSQL/PgBouncer tuple, both prepared-statement settings, bootstrap and bound_read_query scope are present in every rendered guide.

    The published self-hosting guide now states:

    • PgBouncer transaction pooling requires fixed Server 2.9.8 or later, a compatibility floor rather than a duplicated latest-version pin.
    • Actual immutable published acceptance covers PostgreSQL 16.15 / PgBouncer 1.26.1, direct PostgreSQL, session pooling and transaction pooling with either max_prepared_statements=0 or max_prepared_statements=200. Other combinations need their own qualification; this is not a capacity promise.
    • Ordinary server-bootstrap initialization is required before traffic. /api/ready exercises a parameter-bound read and configured checks; it leaves write capacity unverified and does not prove every workflow SQL operation.

    Scope is exactly Deployment EN+7 and its single review key per locale (15 files). Existing examples, budget assumptions and authoritative-writer requirements remain intact. Six translation regressions, the complete 105-guide reviewed-source/code inventory and focused all-eight tuple/settings/bootstrap/readiness/example checks pass. Independent English technical review approved the wording; exact PR Sonar had zero unresolved issues and 0.0% new duplication under unchanged settings.

    No product, Server/SDK source, version manifest, dependency, schema, native, Cloud or pool-tuning edit was made. No additional application/DB qualification stack was started: this follow-through uses the owning published acceptance's 40 genuine SDK originals, preserving its original failures and shutdown limits. Ephemeral UID1000 Node checkers and temporary translation tools are absent; live HTML scratch was removed after each route. The remote source branch is absent.

    This completes the required #524 self-hosting documentation acceptance.

  11. rmcdaniel commented on Oct 20, 2026

    @rmcdaniel
    MemberAuthor

    Delivered in Server 2.9.8, exact source 361a96b905ef927c06bbc85238b34b857e875feb / whole tree cb8ee0c4c383a7ed0692a06217e2f955f8a52bae, immutable image index sha256:d84df4a0baed50204e821cfe458523aff22f9a3949d48851044442386840e7db and Helm 0.1.184. Independent main, protected release and actual normally installed consumer acceptance pass. The exact original failed cases remain preserved.

    The installed PG16.15/PgBouncer1.26.1 cohort completes all 40/40 genuine workflows across direct, session, transaction/max_prepared_statements=0 and transaction/max_prepared_statements=200 modes. Eight workers and the cold readers exit0; stopped read-only SQL confirms original typed values/history/deadlines and zero repairs/registrations. Positive readiness performs an actual bound read; stopped-backend readiness returns503. This does not claim write-capacity or graceful API-shutdown verification. Full installed receipt.

    The required self-hosting guidance is now verified live in all eight offered languages, from exact Website e36d6c113fefda6c448b628270de396a46e7eec9 / tree 408c34b6: all candidate and seven independent main checks pass, actual Pages39443730353 passes, and all eight real Deployment pages match generated published bytes with the fixed-version floor, both transaction modes, bootstrap and readiness scope. Final live documentation receipt.

    All owned runtime/database/compiler/dependency/image/raw resources are retired, latest actual lifecycle14:43:40UTC; documentation helpers/checkouts are retired15:00:13UTC. Borrowed/other-owner resources are preserved. This completes #524; database TLS configuration #540 and service-worker evolution #518/#221 remain separate product obligations.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingkind:defectA public product behavior is incorrectpriority:P1High-priority product or release riskstatus:readyReady for implementation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions