perf: stream SFTP uploads/downloads instead of buffering whole file#195
Conversation
Upload (`upload_file`, `upload_dir_recursive`) used `tokio::fs::read` to load the entire local file into a `Vec<u8>` before calling `write_all`, and download (`download_file`, `download_dir_recursive`) used `read_to_end` into a pooled buffer + `clone()` to a separate `Vec` before writing locally. For multi-GB transfers this means peak RSS scales with file size and large files OOM the client. Replace each path with a small `stream_copy()` helper that loops on 256 KiB reads and writes through the existing `AsyncRead`/`AsyncWrite` implementations on `tokio::fs::File` and `russh_sftp::client::fs::File`. Buffer size matches the SFTP MAX_WRITE_LENGTH so each chunk maps to a single SFTP packet without further fragmentation. Verified locally on macOS arm64 against `bssh-server` v2.1.3 over loopback with a 1 GiB file: | Op | Build | real | RSS | |----------|------------|---------|----------| | upload | unpatched | 38.65s | 3.23 GB | | upload | streaming | 3.47s | 20 MB | | download | unpatched | 3.93s | 2.17 GB | | download | streaming | 3.41s | 16 MB | Peak RSS drops ~160x and uploads complete ~11x faster (a single multi-MB `write_all` apparently serializes much worse through the SFTP pipeline than 256 KiB chunked writes).
Match the streaming buffer to russh-sftp's 255 KiB packet limit so each chunk stays within one SFTP read/write request, explicitly close downloaded remote handles after successful copies, and cover the chunking behavior with a focused unit test.
SummaryUpload ( Replace each path with a small Review follow-up commit Measured impactVerified locally on macOS arm64 against
Peak RSS drops ~160x and uploads complete ~11x faster (a single multi-MB Review result
Test plan
|
SFTP transfer performance release. Bumps Cargo workspace version to 2.1.4 and refreshes README.md, CHANGELOG.md, debian/changelog, and the three man pages (bssh.1, bssh-server.8, bssh-keygen.1) to match. Three perf changes ship since v2.1.3: - Stream SFTP uploads/downloads in 255 KiB chunks instead of buffering whole files (#195) — peak RSS drops ~160x and uploads run ~11x faster on 1 GiB transfers, multi-GB transfers no longer OOM the client. - Pipeline up to 64 concurrent SFTP requests for upload/download (#196), with server-advertised read/write lengths capped against local maxima and the download reorder queue bounded across both in-flight and pending out-of-order responses. - Raise bssh-server SFTP MAX_READ_SIZE from 64 KiB to the 255 KiB SFTP standard (#197), cutting per-MiB request count on downloads from 16 to 4 when combined with client pipelining.
Summary
Upload (
upload_file,upload_dir_recursive) usedtokio::fs::readto load the entire local file into aVec<u8>before callingwrite_all, and download (download_file,download_dir_recursive) usedread_to_endinto a pooled buffer +clone()to a separateVecbefore writing locally. For multi-GB transfers this means peak RSS scales with file size and large files OOM the client.Replace each path with a small
stream_copy()helper that loops on 255 KiB reads and writes through the existingAsyncRead/AsyncWriteimplementations ontokio::fs::Fileandrussh_sftp::client::fs::File. The chunk size matches the russh-sftp defaultMAX_WRITE_LENGTH, so each chunk stays within a single SFTP read/write packet without the 255 KiB + 1 KiB split that a 256 KiB buffer would trigger.Review follow-up commit
ec04b839tightened the implementation by aligning the chunk size to the actual 255 KiB packet limit, explicitly closing remote SFTP file handles after successful downloads, adding focused chunk-boundary unit coverage, and documenting the streaming transfer behavior in the architecture docs.Measured impact
Verified locally on macOS arm64 against
bssh-serverv2.1.3 over loopback with a 1 GiB file:Peak RSS drops ~160x and uploads complete ~11x faster (a single multi-MB
write_allapparently serializes much worse through the SFTP pipeline than chunked writes).Review result
Test plan
cargo checkcleancargo build --releasecleancargo test --no-runcargo test ssh::tokio_client::file_transfer::tests::stream_copy_writes_sftp_sized_chunkscargo fmt --checkcargo clippy -- -D warningscargo test --lib --verbosecargo test --tests --verbose -- --skip integration_testec04b839