17 KiB
Resource Adaptation in Backup Tools
Which knobs exist (restic --limit-upload, borg --upload-ratelimit, kopia throttling, Time Machine low-priority I/O) and what are their defaults?
Takeaway
All Linux tools use static, opt-in, unlimited-by-default rate/priority knobs; Time Machine instead imposes mandatory kernel-level low-priority I/O (IOPOL_THROTTLE) with no per-backup user knob.
Cited Findings
- restic exposes global
--limit-upload rateand--limit-download ratein KiB/s, default unlimited (0) - Source; also documented in man pages as--limit-upload=0/--limit-download=0default unlimited - Source - restic documents that
--limit-uploadcannot be changed mid-run without restart; users request pv-like-Rdynamic adjustment and resort to killing/restarting or external shapers - Source - Borg 1.x exposes
--upload-ratelimit RATEin kiByte/s, default 0=unlimited (with--remote-ratelimitdeprecated alias) - Source; also--upload-buffersize in MiB, default no buffer - Source - Borg 2.x removed
--remote-ratelimit/--upload-ratelimit; bandwidth is limited viaBORGSTORE_BANDWIDTHin bits/sec, default 0=unlimited, plusBORGSTORE_LATENCYdelay per call, or viapv -LProxyCommand / rclone--bwlimit- Source - Older Borg 1.x FAQ documents upload-only
--remote-ratelimitpluspv-wrapper +BORG_RSHfor download shaping with on-the-flypv -R $(pidof pv) -Lchanges - Source - Borg's limiter is a token-bucket
SleepingBandwidthLimiterwithRATELIMIT_PERIOD = 0.1, capping burst quota at 2x period allowance - Source - Kopia exposes
repository throttle setflags--upload-bytes-per-second,--download-bytes-per-second,--concurrent-reads/writes,--read/write-requests-per-second,--list-requests-per-second- Source; readable viarepository throttle get- Source; server-side equivalentserver throttle set- Source - Kopia direct-connect backends accept
--max-upload-speed/--max-download-speedbytes/sec atrepository connecttime (e.g. B2/S3), persisted asmaxUploadSpeedBytesPerSecondin repository.config - Source - Kopia
repository throttle setis rejected on server-connected repos ("operation supported only on direct repository"), and per-policy/UI global throttle was still missing as of 2024–2026 feature requests - Source; KopiaUI throttle exposure requested - Source - Kopia upload throttling was bursty (whole-object sleeps) until PR #2682 added per-read
DuringUploadthrottling - Source - Time Machine's
backupdruns atIOPOL_THROTTLE, defined as "long-running I/O intensive background work, such as backups" that "will be throttled to prevent impact on higher policy levels" - Source - Full
IOPOLladder is IMPORTANT (default) / STANDARD / UTILITY / THROTTLE / PASSIVE - Source - Global kill-switch
sudo sysctl debug.lowpri_throttle_enabled=0(re-enable with=1, lost on reboot unless persisted via/etc/sysctl.confor LaunchDaemon) removes throttle for all background I/O, not just backupd - Source; same command/LaunchDaemon recipe - Source; throttling is I/O not CPU - Source - Measured effect of disabling throttle: copying phase 193→332 MB/s, overall backup 160→276 MB/s (>10 GB test); pre-backup 50 MB probe writes unaffected - Source
- Duplicity has no native generic bandwidth-limit option (open bug #1291633); workarounds are
trickle -s -u/-d, WonderShaper/tc, or legacy--scp-command="scp -l N"(kbit/s, scp backend only, option later deprecated) - Source; scp-lthrottle Q&A - Source; router-QoS/DSCP attempts reported ineffective, per-machine Bandwidth Limiter used instead - Source - Neither restic, borg, kopia, nor duplicity ships battery- or metered-network-aware auto-pause; scheduling/power-awareness is delegated to systemd timers, DAS-CTS (macOS), or external shapers (see Gaps).
Inferences
- Rate limiting is consistently an operator-set static ceiling, not adaptive congestion control; only pv/WonderShaper/BORGSTORE allow out-of-band changes without restarting the backup.
- Borg 2.x shifts shaping out of borg CLI into the borgstore layer, unifying ssh/sftp/rclone paths but breaking old
--upload-ratelimitscripts.
Gaps
- No reliable source found for a built-in metered-WiFi or on-battery auto-suspend in restic/borg/kopia/duplicity as of 2026; Time Machine power/thermal inputs are internal to DAS scoring, not user flags.
Does any tool auto-tune from measured resources (disk size, RAM, link speed) rather than static config, and how?
Takeaway
No tool auto-tunes from measured disk/RAM/link speed; the only resource-derived defaults are CPU-count-derived worker counts, everything else is fixed static defaults.
Cited Findings
- restic defaults: file-read concurrency 2 ("sweet spot" from HDD experiments), blob-save concurrency =
runtime.NumCPU(), tree-save concurrency = 20x blob concurrency - Source - restic uses all available CPUs by default;
GOMAXPROCS=1pins to one core and slightly reduces memory - Source - restic backend connection limit defaults to 5 (2 for local backend), tunable via
-o rest.connections=5/-o local.connections=2; too-high values degrade performance - Source - restic
--read-concurrency/RESTIC_READ_CONCURRENCYraises parallel file reads for NVMe;--no-scanskips the pre-backup file-count/size scan that costs extra I/O on network/FUSE mounts - Source; read-concurrency flag added in 0.15.0 for fast storage - Source - Single-large-file chunking in restic is sequential per file (~450 MB/s) with parallel hash/compress/encrypt downstream; multi-file parallelism is what scales, which is why
cores/4-style read-concurrency guesses only hold for SSDs - Source - Kopia
--max-parallel-file-readsdefaults to number of logical CPU cores; lowering it lowers CPU at cost of time - Source - Kopia
--max-parallel-snapshotscontrols simultaneous snapshots (server/KopiaUI); s2default/bettercompressor concurrency equals logical core count,s2-parallel-4/8pins it - Source - Time Machine scheduling via DAS-CTS scores each due background activity every few seconds against temperature, load, and priority, dispatching
com.apple.backupd-autovia XPC only when score exceeds threshold - Source - On Apple Silicon, background-QoS
backupdthreads are confined to Efficiency cores at reduced frequency (~972–1332 MHz, ~90% residency on E cores), capping throughput at ~300–400 items/s regardless of queue depth - Source - No evidence any tool probes link speed, disk size, or free RAM to set pack size, connections, or limits automatically; restic 16 MiB pack size, Kopia parallelism, and Borg rate defaults are all static.
Inferences
- "Auto-tuning" in this space means CPU-count-proportional worker pools plus OS-scheduler deferral (DAS-CTS/QoS), not closed-loop adaptation to throughput or memory pressure.
- Raising concurrency without raising memory (restic 100×1 GB test hit 300 GB RAM at connections=16/reads=16 and OOMed) shows why static defaults stay conservative - Source.
Gaps
- No source found documenting link-speed probing or disk-size-derived chunk/pack sizing in any of the five tools; if it exists it is not in public docs/CLI help.
How do they behave on constrained boxes (low RAM SQLite/index handling, single-core compression choices)?
Takeaway
Constrained-box guidance is manual: pick cheap compression, lower parallelism/connections, enlarge packs, and accept slower runs; low-RAM index handling remains a known failure mode, not an auto-degraded mode.
Cited Findings
- Borg compression default is lz4 (very high speed, very low compression); alternatives
zstd[,L](default level 3),zlib,lzma,auto,,none- Source; usage examples recommendzlib,6for ratio at cost of speed - Source - Upstream Borg PR proposes moving default from
lz4tozstd,-4(multithreaded, as-fast-or-faster creates, slightly better ratio; large incompressible-image corpora stay ~11% slower) with MT workers capped at 4 - Source - Kopia compression is disabled by default and set per-policy via
kopia policy set [--global] --compression=<...>with min/max-size gates - Source; full option list includess2-default/better/parallel-4/8,zstd/zstd-fastest/better,gzip/pgzip/deflatevariants - Source - Kopia FAQ names compression + parallelism as the two main memory culprits; recommends disabling compression or using
s2/deflate/gzipon small files under low memory, and lowering--max-parallel-snapshots/--max-parallel-file-reads- Source - Kopia benchmark table (466 MiB corpus): s2-default 4 GiB/s at ~375 MiB RSS vs zstd 323 MiB/s at ~238 MiB vs zstd-best 19 MiB/s; on tiny files s2 stays fastest with ~2 MiB footprint - Source
- Kopia zstd levels map to upstream klauspost/compress: fastest≈1, default≈3, better≈7, best≈11; higher custom levels (e.g. -22 --ultra --long) require code change - Source
- restic on constrained HDD/NVMe: forum-tested recipe for 2.5 TB SMR-USB run is
--read-concurrency 1 --pack-size 128 --no-cache(pack default 16 MiB raised to reduce file count and per-pack latency), withlocal.connections=1suggested for vibration-sensitive HDDs - Source - restic large-pack tradeoff: bigger packs reduce file count and help HDD/Swift/Drive limits but need more
$TMPDIRstaging (64–384 MiB guidance) and longer single-pack uploads that wear SSDs - Source - Borg on near-full disks: 1.5 GiB-free VM case shows Borg needs headroom for segments/cache/index; workarounds discussed are extreme compression or
--upload-ratelimitpacing plus inotify/SIGSTOP hacks, with maintainer warning such boxes are unsuitable - Source - Single-core guidance converges:
GOMAXPROCS=1(restic),-C none|laz4(borg),--compression=s2-default|none+--max-parallel-file-reads=1(kopia) minimize CPU/RAM at cost of ratio/throughput.
Inferences
- Low-RAM behavior is fail-slow/fail-OOM rather than graceful: operators must pre-lower parallelism and compression before the run.
- s2-family (kopia) and lz4/zstd,-4 (borg) are the single-core-friendly choices; zlib/lzma/zstd-best are ratio-first and can dominate a weak CPU.
Gaps
- No citable SQLite/index RAM formula (bytes-per-file or per-GB-repo) found for current restic/borg/kopia versions; vendor docs describe symptoms and knobs, not a sizing equation.
What systemd-level controls (MemoryMax, CPUQuota, IOWeight) do shipped units use, and what do docs recommend for background backup daemons?
Takeaway
Upstream backup tools ship no restrictive resource-control units; all concrete CPU/memory/I/O caps come from downstream/community units and generic systemd resource-control docs, with Nice= + CPUQuota/MemoryMax/IOWeight as the recommended trio.
Cited Findings
- systemd
CPUQuota=sets a hard ceiling as % of one CPU (100%=1 core, 200%=2 cores) viacpu.max/cpu.cfs_quota_us;CPUWeight=(1–10000, default 100) is only relative under contention - Source; same semantics in Arch man - Source - systemd memory knobs:
MemoryHigh=soft throttle/reclaim,MemoryMax=hard OOM-kill limit (K/M/G/T or % of RAM,infinityto disable),MemorySwapMax=swap cap; Red Hat recommendsMemoryHighas main control,MemoryMaxas last defense - Source - Community restic timer units use only
Nice=17(low CPU priority) plus sandboxing (ProtectSystem=full,PrivateTmp=true,NoNewPrivileges=yes,RestrictAddressFamilies=,SystemCallFilter=,AmbientCapabilities=CAP_DAC_READ_SEARCH), withRandomizedDelaySec=300+Persistent=yeson the timer - Source - Community segmented-borg systemd design sets
CPUQuota=80%andMemoryMax=2Gon the backup service template - Source - Modern Debian guidance: background CPU →
nice -n 19; background disk →ionice -c 3(BFQ only); hard ceilings/group fairness → unit withCPUQuota=/MemoryMax=/IOWeight=/IOReadBandwidthMax=/IOWriteBandwidthMax=or a sharedbackup.slice; one-shots viasystemd-run --scope -p CPUQuota=50% -p MemoryMax=1G -p IOWeight=10- Source - Same guide warns
ioniceis silently ignored on defaultmq-deadlineSSD schedulers (only BFQ honors classes);MemoryMaxkills rather than slows (useMemoryHighfor pushback);CPUQuota=100%means one core - Source - Example backup slice caps restic at 80 MB/s read / 30 MB/s write via
IOReadBandwidthMax=/dev/sda 80M+IOWriteBandwidthMax=/dev/sda 30MwithIOWeight=10so foreground pools keep headroom - Source - No shipped upstream restic/borg/kopia unit found with
MemoryMax/CPUQuota/IOWeightpreset; ArchWiki/packaging ships timers/services without resource caps (report-writer: treat absence as finding, not oversight).
Inferences
- Recommended Linux server pattern:
Nice=17–19+ optionalIOSchedulingClass=idlefor soft priority, plus abackup.slicewithCPUQuota=50–80%,MemoryHigh/Max=1–4G,IOWeight=10and per-deviceIOWriteBandwidthMaxfor hard isolation. nice/ionicealone are insufficient for noisy-neighbor backups on cgroup-v2/BFQ-mixed fleets; systemd slice quotas are the enforceable layer.
Gaps
- Could not locate a distribution-shipped (Debian/Fedora/Arch package) backup unit that presets
MemoryMax/CPUQuota/IOWeight; all cited values are community/blog recommendations, not vendor defaults. - No citable official restic/borg/kopia doc page prescribing a canonical systemd resource-control stanza as of 2026.