Skip to content

Catalog area render fails entirely on transient PostgreSQL connection timeout in PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync #2876

Closed as not planned

Description

@meshweaver-cloud

What is failing

Rendering of the Catalog layout area fails with an NpgsqlException when PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync tries to open a PostgreSQL connection. The timeout occurs while Npgsql establishes a new connector (NpgsqlConnector.ConnectAsync / OpenNewConnector after a pool rent miss), i.e. the server could not be reached within the connect timeout — not a slow query. LayoutAreaHost surfaces this as a full area-render failure with no retry or graceful degradation.

Probable cause

High confidence on the mechanism: the connection pool had no idle connector and opening a fresh one timed out, so the database was briefly unreachable or too slow to connect from that pod (network blip, DB restart/unavailability, or pool exhaustion) around 05:26–05:27 UTC. Low confidence on which underlying condition — the log gives no database-side evidence, so the trigger (infra incident vs. pool misconfiguration) is unknown. Notably, a single transient connect timeout currently aborts the whole area render.

Impact

Low overall: 2 occurrences, one pod (memex-portal-deployment-9bf79b774-tk88b), within a 21-second window. Affected users would have seen the Catalog area fail to render during that window; if the condition recurs (DB outage), the same code path would fail every area render on every pod, since GetSchemasWithTableAsync runs at composition time.

Where to look

  • plugins-repo/src/MeshWeaver.Hosting.PostgreSql/PostgreSqlCrossSchemaQueryProvider.cs:269 — GetSchemasWithTableAsync, which opens the connection; the only application frame in the trace.
  • MeshWeaver.Layout.Composition.LayoutAreaHost — its render/error path logs the failure; assess whether a retry or degraded rendering is appropriate for transient connection failures.
  • Operationally: check PostgreSQL availability and pod-to-DB connectivity around 2026-08-31T05:26:51Z, and the Npgsql pool settings (Max Pool Size / Connect Timeout) in the memex-cloud deployment.

Evidence

Fingerprint a7064759dd903d9d
Category MeshWeaver.Layout.Composition.LayoutAreaHost
Severity Error
Top frame MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken ct)
Namespace memex-cloud
Pods memex-portal-deployment-9bf79b774-tk88b
Occurrences 2
First seen 2026-08-31 05:26:51Z
Last seen 2026-08-31 05:27:12Z
Recent log lines
2026-08-31 05:26:51Z memex-portal-deployment-9bf79b774-tk88b fail: MeshWeaver.Layout.Composition.LayoutAreaHost[0]
      Rendering failed for area Catalog
      Npgsql.NpgsqlException (0x80004005): The operation has timed out
       ---> System.TimeoutException: The operation has timed out.
         at Npgsql.ThrowHelper.ThrowNpgsqlExceptionWithInnerTimeoutException(String message)
         at Npgsql.Util.NpgsqlTimeout.Check()
         at Npgsql.Util.NpgsqlTimeout.CheckAndGetTimeLeft()
         at Npgsql.Internal.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.PoolingDataSource.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
         at Npgsql.NpgsqlDataSourceCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken ct) in /home/runner/work/MeshWeaver/MeshWeaver/pl…[truncated]
2026-08-31 05:27:12Z memex-portal-deployment-9bf79b774-tk88b fail: MeshWeaver.Layout.Composition.LayoutAreaHost[0]
      Rendering failed for area Catalog
      Npgsql.NpgsqlException (0x80004005): The operation has timed out
       ---> System.TimeoutException: The operation has timed out.
         at Npgsql.ThrowHelper.ThrowNpgsqlExceptionWithInnerTimeoutException(String message)
         at Npgsql.Util.NpgsqlTimeout.Check()
         at Npgsql.Util.NpgsqlTimeout.CheckAndGetTimeLeft()
         at Npgsql.Internal.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.PoolingDataSource.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
         at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
         at Npgsql.NpgsqlDataSourceCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken ct) in /home/runner/work/MeshWeaver/MeshWeaver/plugins-repo/src/MeshWeaver.Hosting.PostgreSql/PostgreSqlCrossSchemaQueryProvider.cs:line 269
         at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken…[truncated]

Opened automatically from Admin/_LogIncident/a7064759dd903d9d. Recurrences are folded into this issue rather than opening new ones.

Activity

  1. rbuergi commented on Sep 1, 2026

    @rbuergi
    Contributor

    Before anyone adds a retry here: this is plausibly a SYMPTOM of #2640, and a retry would treat it as a cause

    I came to this issue intending to add a bounded retry around the connection open, and stopped after measuring the surrounding system. Recording why, because "wrap it in a retry" is the obvious move and I think it is the wrong first one.

    What the issue says the trigger might be

    The body already lists the candidates and is honest that it cannot choose between them: "the connection pool had no idle connector and opening a fresh one timed out, so the database was briefly unreachable or too slow to connect from that pod (network blip, DB restart/unavailability, or pool exhaustion)". It explicitly marks this low confidence.

    A measurement that makes pool exhaustion the leading candidate

    Sampled memex-cloud's own [CrossSchema] SLOW lines today (14:21Z, 3.0.0-rc9.ci.7231):

    • 1,030 slow cross-schema queries in one log window
    • 2,272.9 s — about 38 minutes — of cumulative query wall time
    • top shapes average 1.9–2.6 s each, max 6.5 s
    • every one fans across 192 of 192 partition schemas, and every one returns 0/0 rows

    A steady stream of multi-second queries, each holding a pooled connector for its duration, is exactly the condition under which a fresh OpenNewConnector finds no idle connector and races the connect timeout. That is the mechanism this issue observed, arriving from the direction the issue could not see from its own log.

    Why that changes the fix

    GetSchemasWithTableAsync (PostgreSqlCrossSchemaQueryProvider.cs:252) is itself part of the fan-out machinery — it is the discovery step that enumerates which schemas carry a table, and it runs at composition time on the render path. So the same subsystem that is exhausting the pool is the one failing to get a connection from it.

    Adding a retry there would:

    • make the render survive one blip, and
    • increase the load on an already-saturated pool at exactly the moment it is saturated, and
    • remove the signal that made this diagnosable.

    That is the shape of a band-aid: a bound raised to make a symptom pass, where the question is not "how do I get another connection" but "why are 1,030 queries holding connections for 2 s each to return nothing".

    What I suggest instead

    1. Sequence this behind Every page render fires unanchored cross-schema queries (access, notifications, threads, mesh_nodes) — ~2 s each over 186 partition schemas #2640. If the fan-out elimination lands, re-measure. If connect timeouts persist afterwards, the trigger really is infra and a retry is then the right, well-founded fix rather than a guess.
    2. If a change is wanted now, prefer making the failure legible over making it disappear: LayoutAreaHost currently treats every render exception identically, so a transient DB-connect fault and a genuine render defect are indistinguishable to whoever reads the log. Classifying it — and letting the area report "temporarily unavailable, retryable" rather than a hard failure — improves the operator's information without adding load. Note core A freshly uploaded content file 503s for minutes (10s timeout per request) before it starts serving — /api/content settle window #2901 landed today establishing the three-state pattern (granted / denied / undetermined-retryable) for exactly this kind of distinction.
    3. Keep the impact assessment honest. Two occurrences on one pod in a 21-second window is genuinely low impact. This is not urgent — which is precisely why it is worth fixing in the right order rather than quickly.

    Not making the change; leaving the analysis so the next person does not spend the hour I did arriving at it.

  2. systemorph commented on Sep 17, 2026

    @systemorph
    Contributor

    Reopened — this fault is happening again.

    Still occurring: 9 more since the last update (11 total).

    • Last seen: 2026-09-02 14:39:36Z
    • Pods: memex-portal-deployment-9bf79b774-tk88b, memex-portal-deployment-578b7bc84b-msvh5, memex-portal-deployment-58c88885f8-h5hfs
    Recent log lines
    2026-09-02 14:33:41Z memex-portal-deployment-58c88885f8-h5hfs fail: MeshWeaver.Layout.Composition.LayoutAreaHost[0]
          Area Comments could not render: the data store was unreachable and the query fan-in's bounded transient-connect retry did not recover it
          Npgsql.NpgsqlException (0x80004005): The operation has timed out
           ---> System.TimeoutException: The operation has timed out.
             at Npgsql.ThrowHelper.ThrowNpgsqlExceptionWithInnerTimeoutException(String message)
             at Npgsql.Internal.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlDataSourceCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
             at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken ct) in /home/runner/work/MeshWeaver/MeshWeaver/plugins-repo/src/MeshWeaver.Hosting.PostgreSql/PostgreSqlCrossSchemaQueryProvider.cs:line 269
             at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSchemasWithTableAsync(String tableName, CancellationToken ct…[truncated]
    2026-09-02 14:39:36Z memex-portal-deployment-58c88885f8-h5hfs fail: MeshWeaver.Layout.Composition.LayoutAreaHost[0]
          Area Overview could not render: the data store was unreachable and the query fan-in's bounded transient-connect retry did not recover it
          Npgsql.NpgsqlException (0x80004005): The operation has timed out
           ---> System.TimeoutException: The operation has timed out.
             at Npgsql.ThrowHelper.ThrowNpgsqlExceptionWithInnerTimeoutException(String message)
             at Npgsql.Util.NpgsqlTimeout.Check()
             at Npgsql.Util.NpgsqlTimeout.CheckAndGetTimeLeft()
             at Npgsql.Internal.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlDataSourceCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
             at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSche…[truncated]
    2026-09-02 14:39:36Z memex-portal-deployment-58c88885f8-h5hfs fail: MeshWeaver.Layout.Composition.LayoutAreaHost[0]
          Area Comments could not render: the data store was unreachable and the query fan-in's bounded transient-connect retry did not recover it
          Npgsql.NpgsqlException (0x80004005): The operation has timed out
           ---> System.TimeoutException: The operation has timed out.
             at Npgsql.ThrowHelper.ThrowNpgsqlExceptionWithInnerTimeoutException(String message)
             at Npgsql.Util.NpgsqlTimeout.Check()
             at Npgsql.Util.NpgsqlTimeout.CheckAndGetTimeLeft()
             at Npgsql.Internal.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.PoolingDataSource.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
             at Npgsql.NpgsqlDataSourceCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
             at MeshWeaver.Hosting.PostgreSql.PostgreSqlCrossSchemaQueryProvider.GetSche…[truncated]
    
  3. rbuergi commented on Sep 18, 2026

    @rbuergi
    Contributor

    Closing — the answer this issue converged on is in origin/main, and the fault has been quiet 16 days

    Dormancy. Newest occurrence Last seen: 2026-09-02 14:39:36Z (the bot's reopen comment above). Measured 2026-09-18T05:52Z that is 16 days quiet, with live log ingest (incidents updated 2026-09-18T05:42Z) and no portal roll in between.

    The suggestion recorded on this issue on 2026-09-01 — "prefer making the failure legible over making it disappear" — was implemented, and the retry it warned against was not added. Checked against origin/main at 229930bf2f29845323e0f350e73829b1bdb4928e:

    $ git -C <core> grep -n 'IsStorageUnavailable' origin/main -- 'src/**/*.cs'
    src/MeshWeaver.Layout/AreaErrorClassifier.cs:270:        public static bool IsStorageUnavailable(Exception? ex)
    src/MeshWeaver.Layout/Composition/LayoutAreaHost.cs:851:        else if (AreaErrorClassifier.IsStorageUnavailable(ex))
    src/MeshWeaver.Layout/Composition/LayoutAreaHost.cs:956:        if (AreaErrorClassifier.IsStorageUnavailable(ex))
    

    LayoutAreaHost no longer treats every render exception identically. A store-unreachable fault now logs as what it is —

    Area {Area} could not render: the data store was unreachable and the query fan-in's bounded transient-connect retry did not recover it

    — instead of Rendering failed for area Catalog, which is the wording this ticket's reader-cost complaint was about, and line 956 gives the viewer a "temporarily unavailable / retryable" frame rather than the generic panel carrying the driver's text and the database host it could not reach.

    Pinned by a test that names this issue and states the boundary explicitly:

    $ git -C <core> show origin/main:test/MeshWeaver.Layout.Test/StorageUnavailableRenderTest.cs | head -45
    /// 🚨 <b>#2876 — a render that could not reach the DATA STORE is an availability report, not a
    /// broken view.</b>
    /// <b>Why the existing retry did not cover it.</b> It DID run. ... TransientStorageFaults.
    /// RetryTransientConnect (#2521 ...): 250 → 500 → 1000 ms of backoff ... #2876 is not a missing
    /// retry — it is the missing answer to "what does the area SHOW when the bounded retry is
    /// honestly spent".
    

    The load-side root cause this ticket was sequenced behind (#2640, the cross-schema fan-out that was holding the connectors) has its own census and elimination programme in Doc/Architecture/CrossSchemaFanOutElimination, which is where any remaining connect-timeout work belongs — not here. GetSchemasWithTableAsync itself lives in MeshWeaver.Plugins, so it is not core's to close either way.

    Closing as redundant: the question this ticket was reduced to — what does the area show when the retry is spent — is answered and tested. If the fault recurs, the recurrence bot reopens this issue with fresh evidence — the designed behaviour, and why closing a dormant incident is safe.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions