Files
rustfs/docs/testing/ftps-upload-memory.md
T
Dae-Cheol Noh 1f04a12abf fix(ftps): bound upload memory with multipart streaming (#8064)
* fix(ftps): bound upload memory with multipart streaming

* fix(ftps): keep multipart upload within driver module
2026-09-22 12:15:20 +00:00

72 lines
3.3 KiB
Markdown

# FTPS upload memory regression
The old `FtpsDriver::put` copied the entire input into a `Vec` before calling
`PutObject`. Wrapping that completed allocation in `StreamingBlob` did not make
input consumption streaming. It also sent large files through the 5 GiB
single-PUT path.
## Automated regression
```sh
cargo test -p rustfs-protocols --no-default-features --features ftps --lib
```
`ftps_upload_streams_before_eof_and_preserves_part_bytes` uses a reader that
refuses to supply the next part until the previous part reaches the backend.
Restoring the driver from commit `9c30cc88513c5e1b5443b6ee86bf4bc7965e79e9`
while retaining the new test harness makes this test fail with
`read ahead of uploaded part`. The multipart implementation passes. Tests also
check exact bytes, empty files, part boundaries, part numbering, errors,
permissions, cancellation cleanup, and buffer capacity.
## Manual memory probe
The ignored test `ftps_large_upload_memory_probe` creates concurrent synthetic
readers. Each reader yields between reads so all uploads are active together.
It calls the real FTPS storage driver with a scripted storage backend that
returns successful S3 responses and discards the payload. It neither stores
files nor opens network connections. No large fixture is required.
Build the test executable first; measuring Cargo would include compiler memory:
```sh
export CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 CARGO_INCREMENTAL=0
cargo test -p rustfs-protocols --no-default-features --features ftps --lib --no-run
```
Use the executable path printed by that command as `test_binary`, then on macOS:
```sh
/usr/bin/time -l "$test_binary" --ignored --exact \
ftps::driver::upload::tests::ftps_large_upload_memory_probe --nocapture
RUSTFS_FTPS_TEST_BYTES=10737418240 RUSTFS_FTPS_TEST_CONCURRENCY=10 \
/usr/bin/time -l "$test_binary" --ignored --exact \
ftps::driver::upload::tests::ftps_large_upload_memory_probe --nocapture
```
On Linux use `/usr/bin/time -v` (its maximum RSS is reported in KiB). The default
probe uses 64 MiB per upload and 10 uploads. These environment variables are
**test-only**, not server configuration.
Observed on macOS arm64 with Rust 1.98.1, debug information disabled, one run per
case (maximum resident set size reported in bytes):
| Driver | Bytes per upload | Concurrent uploads | Maximum RSS |
| --- | ---: | ---: | ---: |
| Original driver at `9c30cc8`, same probe | 67,108,864 | 10 | 710,033,408 (677.1 MiB) |
| Bounded multipart driver | 67,108,864 | 10 | 179,781,632 (171.5 MiB) |
| Bounded multipart driver | 10,737,418,240 | 10 | 187,465,728 (178.8 MiB) |
For the original-driver comparison, only `ftps/driver.rs` was replaced with its
base-commit version in the test checkout; the identical probe and dummy backend
were retained, the executable rebuilt, and the 64 MiB case rerun. The original
driver was not subjected to the 100 GiB aggregate case.
These measurements isolate protocol-driver buffering, not total server RSS,
persisted-data correctness, TLS behavior, or storage throughput. The large case
supplies 100 GiB of generated bytes without retaining them. A real server also
uses memory for TLS, storage, caches, and allocator overhead. Memory still scales
with active upload count. See [FTPS operations](../operations/ftps.md) for limits
and cleanup behavior.