Object storage vendors publish pricing pages and marketing copy. They rarely publish concurrency curves, error rates under load, time to first byte, or what happens when 500 clients read the same file simultaneously. Carolina Cloud decided to get those numbers themselves, renting a bare-metal box with a 100 Gbps network interface in Ashburn and hammering Cloudflare R2 until something broke.
The headline: from one box, one account, R2 reads at roughly 55 Gbps and writes at roughly 36 Gbps. Both are hard ceilings. Neither moved no matter how many connections, keys, or buckets the team threw at it. The entire benchmark cost under $30, most of it for the server rental. R2 egress charges were zero.
Proving the ceiling is R2's, not the test machine's
The path every byte travels has five hops: R2's drives, R2's NIC, the internet, the test machine's NIC, and the test machine's RAM. The team wanted to measure the first two. To rule out their own hardware as the bottleneck, they ran a combined test: pulling from R2 at full tilt while simultaneously pulling unrelated non-R2 traffic, watching the kernel's NIC receive counter. The box received 82.75 Gbps combined, 44% more than an R2 read alone. During a later test phase, the same box handled 91.5 Gbps. If the NIC, kernel, or uplink were the limit, neither result is possible.
Three separate load generators agreed: a custom Go loader, s5cmd, and MinIO's warp tool landed at 52.7, 51.2, and 54.2 Gbps respectively. Three codebases, three authors, one number. Application-level and NIC-level byte counters matched to within 0.2% throughout.
The team deliberately wrote to RAM instead of disk for the entire benchmark. A modern SSD writes at around 5000 MiB/s, or roughly 40 Gbps. Had they written everything to disk, their own hardware would have been the bottleneck, making the benchmark meaningless.
What didn't throttle
The team tested along every dimension they could think of. Ten minutes sustained at 55 Gbps produced 0.3 Gbps of drift and zero errors. One key versus 32 keys made no difference: both hit 55.8 Gbps. One bucket versus two showed no gain on reads. IPv4 versus IPv6 across R2's anycast addresses gave IPv4 a consistent but marginal 1 to 3 Gbps edge.
Simultaneous reads and writes were the most interesting test. The team sustained 52 Gbps of reads alongside 34 Gbps of writes, totaling 86 Gbps. Each direction lost a couple of Gbps to the other, but there was no shared bandwidth budget between GET and PUT. That is a meaningful architectural detail for anyone running mixed workloads.
Time to first byte consistently hit a floor of about 40 milliseconds regardless of object size or concurrency. On a 0.525 millisecond round trip, that is roughly 70 times the network RTT, meaning the delay is server-side request handling. In practice, anything under about 256 KiB is bound by request rate, not bandwidth.
The one throttle they found, and the fix
R2 returns 503 SlowDown when more than 128 concurrent requests target the same part of the same object. This is a deliberate rejection, not a saturated disk. The team notes that Wasabi handled the same test by degrading gracefully with zero errors, and says it is not obvious which approach is better. Carolina Cloud prefers to fail loudly so users know what is happening rather than silently degrading.
The access pattern that triggers this is common in genomics and finance: multiple processes reading the same index, header, manifest, or reference file simultaneously. samtools and htslib tools do index-seek reads into CRAM and BAM files, and when a batch of tasks starts at once, every one of them reads the same header region of the same reference before doing anything else.
The fix is cheap and verified: replicate the hot object under multiple duplicate keys and round-robin across them. The 503s vanish, even at 2048 concurrent requests. Across the entire benchmark, this was the only non-200 or 206 response the team ever saw from R2.
Writes are slower, and the client matters more than you think
Writes plateau at 36 to 37 Gbps on a single bucket. Part size matters: 32 MiB parts peak at 26.8 Gbps at 192 connections, while 8 MiB and 128 MiB parts both land under 19. A single key sustains about 68% of what the same load spread across 32 keys achieves. Two buckets get you to 58 Gbps combined, so write-heavy workloads can benefit from spreading output across buckets. Reads showed no such gain from multiple buckets.
The client mattered more than the provider. s5cmd, warp, and the custom loader all pulled past 50 Gbps. The AWS CLI pulled from R2 at about 3 Gbps, and raising its concurrency and part size barely moved it. The same CLI reaches about 5 Gbps against Wasabi and S3. R2 will serve a single key at 55 Gbps to anything that opens enough concurrent ranged GETs. Check whether your tool does that before blaming the provider.
What remains unknown, and why this matters
Everything ran from one box and one account, so the team cannot say whether the ceiling is per account or per source IP. Both are R2's limit, not the user's. They differ on whether a second box would raise it. If it is per source IP, a second NIC on the same machine might help. The team had two and only used one.
For teams running Nextflow pipelines, genomics workloads, or finance data processing, the numbers are concrete. R2 at $15 per TB per month with zero egress is a serious alternative to AWS at $90 per TB of egress. At 55 Gbps from a single box, the throughput supports supercomputing-scale work. The benchmark moved roughly 10 TiB of data in a couple of hours for under $30, most of it for the server rental. The same egress on AWS would have cost around $700.
Carolina Cloud describes R2 as an object storage offering that can hang with the established providers, noting that Cloudflare stumbled into a strong storage product from their CDN and traffic-serving roots. Part 2 of the benchmark series covers Wasabi, using the same box and same tests, with different results driven by a billing model that changes the answer for short-lived pipeline data.