g-cloudflare: large single statement (~124KB) reliably kills the connection on Cloudflare Workers - "Connection terminated unexpectedly"
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- javascript, postgresql
- 領域
- backend, databases, networking
調査の方向性
まず、issue に記載されている pg-cloudflare の CloudflareSocket.write() と destroy() のパスを調査します。~124KB の statement を使ってデプロイ済みの Worker で再現し、その後、成功する ~25KB のチャンクと比較して、基盤となる write エラーが表面化するかを確認します。大きな書き込みが Workers streams API で成功するか、または実際のエラーがアプリケーションに到達すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Environment
- pg: 8.16.3 (bundled pg-cloudflare 1.3.0; the socket write path is unchanged in 1.4.0)
- Runtime: Cloudflare Workers, production (
connect()TCP sockets, TLS) - Consumer: Prisma
@prisma/adapter-pg6.x →pg.Pool - Server: Supabase Postgres behind Supavisor transaction pooler (port 6543)
Summary
A single parameterized statement whose wire message is large (~124KB of bind parameters in our case) deterministically fails from a deployed Worker with:
Error: Connection terminated unexpectedly
at <bundle>
at async PgTransaction.performIO
at async PgTransaction.executeRaw
Small statements on the same connection, milliseconds earlier, succeed. The failure reproduces 100% of the time, including on a freshly opened connection (new pool, single connection, TLS handshake and auth complete, BEGIN and a small SELECT set_config(...) succeed first).
What the server sees
This is the part that localizes the bug to the client side:
-
Postgres logs: nothing at all at the failure timestamps (no error, no crash, no statement logged).
-
Supavisor (pooler) logs:
ClientHandler: (ECLIENTSOCKETCLOSED) Client socket closed while state was busy (transaction)i.e. from the server's perspective, the client closed the socket mid-statement. The connection is not being dropped by the pooler or the database — pg (or the Workers socket underneath it) is aborting its own connection while flushing the large message, and then reporting the resulting close as "Connection terminated unexpectedly". The underlying write error, if any, is never surfaced to the application.
Reproduction
Observed shape (via Prisma's pg adapter, but the adapter is a thin passthrough to pg):
// Deployed Worker (not wrangler dev), pg.Pool({ max: 5 }), TLS to Supabase pooler
await client.query('BEGIN')
await client.query("SELECT set_config('request.jwt.claims', $1, TRUE)", [claimsJson]) // ~600B — OK
await client.query(bigInsertSql, bigParams) // ~124KB of array params — connection dies in ~100-200ms
The failing statement is a CTE INSERT ... SELECT FROM unnest($1::uuid[], $2::text[], ...) with ~30 array parameters totaling ~124KB (about 650 rows across 5 tables). Nothing exotic about the SQL itself:
- The identical statement pattern with smaller arrays succeeds.
- The identical data split into chunked statements of ~25KB each (200 rows per
unnest), inside the same interactive transaction, succeeds 100% of the time. This is our current workaround. - Retrying on a brand-new connection fails identically (~100-200ms after the statement is sent), so it is not a stale/idle-socket problem.
I don't have an exact byte threshold; payloads that historically stayed well under ~64KB never failed, ~124KB fails every time.
Suspected cause
pg-cloudflare's CloudflareSocket.write() forwards the entire buffer to a WritableStreamDefaultWriter.write() and returns true immediately — there is no backpressure handling:
write(data, encoding, callback) {
...
this._cfWriter!.write(data).then(
() => callback(),
(err) => callback(err)
)
return true
}
and destroy() tears down via end() → _cfSocket.close(), which produces exactly the abrupt client-side close the pooler logs. If the runtime's writer rejects or aborts on a large write, the error is only visible to the app as the generic post-close "Connection terminated unexpectedly", which made this quite painful to diagnose.
Expected behavior
Either the large write succeeds (with backpressure/chunking as needed for the Workers streams API), or the actual write error is propagated so the application can see why the connection died.
Notes
- Reproduced only in production Workers; I have not verified whether local
wrangler devbehaves the same. - Happy to provide more logs/timings or test patches.
- 主要言語
- JavaScript
- スター
- 13.2k
- フォーク
- 1.4k
- 平均マージ
- 6日 15時間
- マージ済み PR(30日)
- 6
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
brianc/node-postgres のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
brianc/node-postgres#3770 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
brianc/node-postgres#3716 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
brianc/node-postgres#3631 · コメント 1 件 ·
-
難易度 1/5 1〜3時間 初心者へのやさしさ 62/100
brianc/node-postgres#2857 ·
-
難易度 1/5 1時間未満 初心者へのやさしさ 68/100
brianc/node-postgres#2433 ·
brianc/node-postgres の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
antfu-collective/icones#398 ·
-
ECmail.com オープン
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
wesbos/burner-email-providers#554 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
radiantearth/stac-browser#1023 ·
-
HMR stops working オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
components-web-app/docs#92 ·