Pre-Migration Assessment: Why ping and iperf Aren't Enough
Synthetic benchmarks don't predict real migration performance. Here's how to assess network, storage, and parallelism before you start moving data.
iperf says you have 10 Gbps. Your migration runs at 200 MB/s. That’s 1.6 Gbps. Where did the other 8.4 go?
You ping the destination. 0.3ms. Beautiful. Your NFS transfers stall every 30 seconds anyway.
This is the gap between synthetic benchmarks and real migration performance. If you plan a cutover window based on iperf numbers, you will miss it. Here’s how to run an assessment that actually predicts what’s going to happen.
Why synthetic benchmarks lie
iperf measures raw TCP throughput between two hosts. It opens a socket, blasts data, and reports the rate. That number tells you the ceiling. It does not tell you what your migration will hit.
Real migrations are not raw TCP. They are NFS or SMB, which means:
- Protocol overhead. Every file open, stat, read, write, close, chmod, and chtimes is a separate RPC call. Each one has headers, authentication, and round-trip latency.
- Metadata operations. Creating directories, setting permissions, preserving timestamps. None of this shows up in iperf.
- Connection setup. NFS mounts, Kerberos tickets, session negotiation. These happen once but can take seconds.
- Attribute caching. The client caches metadata and sometimes serves stale data, which causes retries and verification failures.
- Small file penalty. iperf tests with large buffers. Your dataset has 400,000 files under 4KB. Those files spend more time on syscall overhead than on data transfer.
A realistic rule of thumb: expect 15% to 40% of your iperf throughput for NFS file transfers. For SMB, expect 10% to 30%. For workloads dominated by small files, expect even less.
iperf is necessary, not sufficient
Still run iperf. If your raw link can’t handle the bandwidth, nothing else matters. But don’t stop there. iperf tells you the upper bound. The rest of this guide tells you where your actual throughput will land.
Network jitter: the hidden killer
A network that averages 1ms latency sounds great. But if it spikes to 50ms every 10 seconds, your migration is in trouble.
NFS operations are synchronous. A single 50ms spike means one worker stalls for 50ms. If that happens during a metadata-heavy phase (creating thousands of directories), you get a cascade: the worker blocks, its queue backs up, the NFS server processes the delayed burst all at once, and throughput collapses.
Average latency hides this. You need to look at the tail.
Measure jitter, not just latency:
# 60-second ping with timing stats
ping -c 60 -i 0.5 nfs-server.local | tail -1
# Look at the max value in min/avg/max/mdev
# Better: run mtr for 100 probes
mtr -r -c 100 nfs-server.local
# Watch for packet loss and high std deviation
What the numbers mean:
| Jitter pattern | Impact on migration |
|---|---|
| avg 0.5ms, max 2ms | Excellent. Full speed. |
| avg 1ms, max 15ms | Noticeable. Some retries. |
| avg 1ms, max 50ms+ | Significant throughput loss. Investigate. |
| Any packet loss > 0.1% | TCP retransmits will tank your throughput. Fix first. |
If you see periodic spikes, check for competing traffic. Backup jobs, replication, VM snapshots. Anything that saturates the link or the storage controller for a few seconds will cause exactly this pattern.
Test at the right time
Run your network tests during the window you plan to migrate in. A network that’s clean at 2 AM might be saturated at 2 PM when backups run. Test conditions should match migration conditions.
Parallel stream testing: finding the sweet spot
One transfer stream will never saturate a 10Gbps link. NFS has per-operation latency, and a single thread can only have one operation in flight at a time. The answer is parallel streams. But how many?
Too few and you waste bandwidth. Too many and the NFS server thrashes. Its disk heads seek constantly, its CPU burns on lock contention, and total throughput actually drops.
The sweet spot depends on your hardware. You need to test it.
Quick parallel throughput test:
# Create a test directory with representative files
mkdir -p /tmp/parallel-test
# Generate 100 files of ~100MB each
for i in $(seq 1 100); do
dd if=/dev/urandom of=/tmp/parallel-test/file_$i bs=1M count=100 2>/dev/null
done
# Test with 1, 2, 4, 8, 16 parallel streams
for streams in 1 2 4 8 16; do
echo "--- $streams streams ---"
sync; echo 3 > /proc/sys/vm/drop_caches
time find /tmp/parallel-test -type f | \
xargs -P $streams -I {} cp {} /mnt/nfs-dest/
rm -rf /mnt/nfs-dest/*
done
Graph the results. You’ll see something like:
| Streams | Throughput | Notes |
|---|---|---|
| 1 | 180 MB/s | Single-threaded baseline |
| 2 | 340 MB/s | Nearly linear scaling |
| 4 | 580 MB/s | Good scaling |
| 8 | 720 MB/s | Diminishing returns |
| 16 | 650 MB/s | Server contention, throughput drops |
In this example, 8 streams is the sweet spot. Going to 16 actually makes things worse. Your numbers will be different. The point is to test instead of guess.
Test with your actual NFS server
The optimal stream count depends on the NFS server’s disk layout (SSD vs spinning), RAID config, CPU, and available memory. A modern all-flash NAS might scale linearly to 32 streams. An older spinning-disk array might peak at 4. There is no universal answer.
File sampling: test with realistic data
Don’t benchmark with a folder of 1KB config files and then migrate a NAS full of 500MB video renders. The performance characteristics are completely different.
Sample across your actual dataset:
# Sample 200 files from each size bracket
find /path/to/source -type f -size -128k | shuf -n 200 > /tmp/sample-small.txt
find /path/to/source -type f -size +128k -size -100M | shuf -n 200 > /tmp/sample-medium.txt
find /path/to/source -type f -size +100M | shuf -n 200 > /tmp/sample-large.txt
# Transfer each sample and measure
for bracket in small medium large; do
echo "--- $bracket files ---"
sync; echo 3 > /proc/sys/vm/drop_caches
time rsync -a --files-from=/tmp/sample-$bracket.txt / /mnt/nfs-dest/
rm -rf /mnt/nfs-dest/*
done
This gives you per-bracket transfer rates. Combine them with your size distribution (see our file count planning guide) and you get a real estimate.
Storage assessment: how much actually needs to move?
Before you spend a weekend on a cutover, ask: does all of it need to move?
Capacity and stale data:
# Total size
du -sh /path/to/source
# Files not accessed in 2+ years
find /path/to/source -type f -atime +730 -printf '%s\n' | \
awk '{ total += $1 } END { printf "Stale: %.1f GB\n", total/1073741824 }'
# Files modified in the last 30 days (active data)
find /path/to/source -type f -mtime -30 -printf '%s\n' | \
awk '{ total += $1 } END { printf "Active: %.1f GB\n", total/1073741824 }'
On a typical enterprise NAS, 30% to 60% of data hasn’t been touched in over a year. That changes your migration strategy. Maybe you move active data first, cut over, then migrate stale data in the background. Maybe some of it goes to cold storage instead.
Growth rate:
# Compare snapshots if available, or check quota reports
# Quick estimate from recent files:
find /path/to/source -type f -mtime -90 -printf '%s\n' | \
awk '{ total += $1 } END { printf "Last 90 days growth: %.1f GB\n", total/1073741824 }'
Growth rate determines whether you need a cutover window at all. If the dataset grows by 5GB/day and you can sustain 500MB/s, incremental syncs can keep up indefinitely. You do a bulk transfer once, then incremental updates until you flip DNS. No downtime window needed.
syncopio advantage
syncopio’s discovery scan gives you the full picture before you move a byte: file count by size bracket, stale data analysis, directory depth, and per-bracket transfer estimates. It tests the actual transfer path between source and destination, not synthetic benchmarks. You see realistic throughput numbers for your specific hardware and network before committing to a cutover window.
Planning template: bandwidth times time equals data moved (sort of)
Here’s the formula everyone uses:
transfer_time = total_bytes / throughput
Here’s why it’s wrong: it assumes constant throughput. In practice, you need overhead factors.
Realistic planning formula:
effective_throughput = iperf_throughput × protocol_factor × parallelism_factor × jitter_factor
transfer_time = total_bytes / effective_throughput
| Factor | Typical range | Notes |
|---|---|---|
| Protocol (NFS/SMB overhead) | 0.15 to 0.40 | Lower for small files, higher for large |
| Parallelism | 0.5 to 0.95 | Depends on stream count vs sweet spot |
| Jitter / contention | 0.7 to 0.95 | Worse during business hours |
| Combined | 0.05 to 0.35 | Multiply all three |
Example: 10 Gbps link, 50TB to move, mixed workload:
effective = 10 Gbps × 0.25 (combined factor) = 2.5 Gbps = 312 MB/s
50,000,000 MB / 312 MB/s = 160,256 seconds ≈ 44 hours
Add 20% buffer for retries, verification, and surprises: about 53 hours. That’s your cutover window. Not the 11 hours that raw iperf would suggest.
Always add buffer
Plan for 1.2x to 1.5x your calculated time. Networks degrade. Servers hiccup. Files get locked. The buffer is not pessimism. It’s realism.
The assessment checklist
Before you start any migration over 1TB, run through this:
- iperf3 between source and destination. This is your ceiling. If it’s not what you expect, fix the network first.
- Jitter test during your planned migration window. 60+ seconds of ping, check max latency and packet loss.
- Parallel stream test. Find the point where more streams stop helping.
- File size sampling. Test transfers with files from each size bracket in your dataset.
- Storage audit. Total size, stale data percentage, growth rate over 90 days.
- Calculate realistic transfer time. Use the overhead factors above, not raw throughput.
- Decide on strategy. Big bang cutover, incremental sync, or phased by priority?
Skip any of these and you’re guessing. Guessing is how you end up rebooking your cutover window at 3 AM on a Sunday.
Further reading: