Skip to content

fix(upload): let storage pace the block body instead of buffering it - #275

Open
chenrui333 wants to merge 1 commit into
falcondev-oss:devfrom
chenrui333:fix/upload-backpressure
Open

chenrui333 wants to merge 1 commit into
falcondev-oss:devfrom
chenrui333:fix/upload-backpressure

Conversation

@chenrui333

Copy link
Copy Markdown

Fixes the upload half of #274.

The block PUT route read its body with h3's getRequestWebStream. That function enqueues every data event into a ReadableStream that has no pull and never pauses the request. When a client sends faster than the storage adapter consumes (lib-storage reads one part, then waits on UploadPart), the rest of the block piles up in process memory, once for each concurrent block upload.

This PR hands event.node.req to Storage.uploadPart instead, so the adapter's reads pace the client through TCP flow control. uploadPart now takes a Readable, which also drops the Readable.fromWeb round trip. The "body is not a stream" branch is removed because it can no longer be reached: getRequestWebStream only returned undefined for methods without a payload, and this route is PUT-only.

Test

tests/proxy-backpressure.test.ts mounts the real upload route on an in-process h3 app, stalls adapter.uploadStream, PUTs a 512 MiB block, and asserts that less than 64 MiB is read off the socket while storage is busy:

  • before: 536,871,021 bytes read off the socket (the whole block)
  • after: 131,072 bytes

It is deterministic (identical numbers over 3 runs) and takes about 0.75 s with the default sqlite + filesystem setup. pnpm test:run: 16 files / 44 tests pass (1 file / 4 tests skipped, same as on dev). pnpm type-check and lint are clean. I have not run the postgres/mysql/s3/gcs matrix locally. The changed code is driver-independent apart from uploadStream, which already took a Readable.

Notes

The block PUT route read its body through h3's `getRequestWebStream`, which
enqueues every `data` event into a ReadableStream that has no `pull` and never
pauses the request. When the client sends faster than the storage adapter
consumes (lib-storage reads one part, then waits for UploadPart), the rest of
the block piles up in process memory. Concurrent uploads multiply it.

Hand the Node request stream to `uploadPart` instead, so the adapter's reads
pace the client through TCP flow control. `uploadPart` now takes a `Readable`,
which also drops the web-stream round trip.

tests/proxy-backpressure.test.ts mounts the real upload route on an in-process
h3 app, stalls the storage adapter and asserts that less than 64 MiB of a
512 MiB block is read off the socket meanwhile. Before this change the route
reads the whole 512 MiB.

Refs falcondev-oss#274

Signed-off-by: Rui Chen <rui@chenrui.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant