The previous post detailed how Rclone can reliably upload large files with their checksums to Backblaze unlike other programs. This post will outline the workflow and some gotchas to keep in mind when doing massive data loads over the internet.

With trial and error, I was able to archive 8 TB of footage from my Synology NAS to Backblaze B2 in about a month.

To Keep in Mind

First, the overall workflow.

Remote to Remote is Possible

Keep in mind Rsync supports copying between two remotes directly. The computer running Rclone will stream data in RAM as it shuttles data between the two.

In fact that’s what I mainly did: transferred assets from a personal B2 bucket to the organization’s new B2 bucket. Pretty neat!

List Folders Syntax: lsd

After setting up your remote with rclone config, use the list directory command lsd to double check your source/target folders.

For example, if the B2 remote name is called b2-remote1 then the command to list the root is:

rclone lsd b2-remote1:

Note the : at the end.

If a folder contains spaces, you use double quotes like this rather than backticks \.

rclone lsd b2-remote1:Videos/"Subfolder With Spaces/"

Also use trailing forward slashes / instead of asterisks * to indicate the files inside.

Consider copy instead of sync

From the docs1:

  • rclone copy - Copy files from source to dest, skipping already copied.
  • rclone sync - Make source and dest identical, modifying destination only.

Depending on your intention, copy may be better.

Expect Errors and Verify

Although Rclone automatically retries upload errors (by default up to 10 times) there are few reasons why files never get uploaded. See the appendix for various scenarios.

Therfore, in a nutshell, always verify your transfer after (see below).

Beware Quota Restrictions

Unexpected EOF (end of file) errors can occur when streaming from a remote because of Backblaze quota restrictions.

2018/08/09 16:53:44 DEBUG : CAM A/private/M4ROOT/CLIP/C0001.MP4: Cancelling large file upload due to error: unexpected EOF
2018/08/09 16:53:45 DEBUG : CAM A/private/M4ROOT/CLIP/C0001.MP4: Received error: unexpected EOF - low level retry 1/10

Double Check the Source Supports (and has) Checksums

Since Backblaze only supports SHA-1 checksums, the Rclone docs indicate the source must also support SHA-1 checksums.2

For a large file to be uploaded with an SHA1 checksum, the source needs to support SHA1 checksums. The local disk supports SHA1 checksums so large file transfers from local disk will have an SHA1. See the overview for exactly which remotes support SHA1.

So B2 to B2 syncs should always populate checksums, right? Wrong. It will only if the source B2 bucket had checksums.

As detailed in the previous post, that means if the large files were copied with Rclone would they have checksums.

Rclone Browser is Great (but Deprecated) for Local <-> Remote

Rclone Browser is a wrapper that the same config as the CLI. Rclone Browser does not support direct remote to remote syncs, but it is good for normal use. Unfortunately the program deprecated in favor of the WebGUI, but the latter doesn’t let you yet upload things. 🤷🏾‍♂️

On Mac, Rclone Browser can be installed with Homebrew via brew cask install rclone-browser

⬆︎ Reliability by ⬆︎ Chunk Size (using ⬆︎ RAM)

The default settings seem to be optimized for small files, like webpages.

  • Single part upload cutoff of 200 MB
  • Chunk size of 96 MB
  • Four concurrent transfers

For whatever reason, the error rate with these defaults was higher than I expected (see below).

Instead, I found better stability for large video files with:

  • Cutoff of 1G
  • 1G <= chunk size <=4G
  • Two concurrent transfers

Note that all concurrent chunks are buffered into memory, so there is significantly more RAM usage with larger chunk sizes. Hence the downgrade to two transfers.

More specifics in the sync section below.

Measure Twice, Cut Once: dryrun

Before discussing the sync command, it’s imperative mention the --dryrun flag for the following reasons.

  • Backblaze bills by usage/throughput
  • B2 doesn’t support renaming files after they are uploaded

Therefore, when running rclone sync always use the --dryrun option first.

The sync command

My goto sync (orcopy) command is:

rclone sync <source> <dest> --exclude .DS_Store -vv --b2-upload-cutoff 1G --b2-chunk-size 1G --transfers 2

Explanation of Flags

  • --exclude .DS_Store to excluding Mac specific files
  • -vv to enable DEBUG logging for visibility into chunk retries, etc.
  • --b2-upload-cutoff files above this size will switch to a multipart chunked transfer
  • --b2-chunk-size the size of the chunks, buffered in memory
  • --transfers number of simulatenous transfers. b2-chunk-size x transfers must fit in RAM

Phased Approach with --max-size

Sometimes I found it helpful to transfer all files under a certain size limit first, say 1 GB, and then re-run the command for larger files.

To do so, add --max-size 1G to the rclone sync command.

The check command

Always verify after a sync. Even if you think you don’t need to. The command is straightforward:

rclone check <source> <dest> --exclude .DS_Store

If there are discrepancies the output will look like:

2018/09/05 07:45:04 ERROR : CAM 2/AVF_INFO/AVIN0001.BNP: File not in Local file system at /Volumes/Scratch/ToB2
2018/09/05 07:45:04 ERROR : CAM 2/AVF_INFO/AVIN0001.INP: File not in Local file system at /Volumes/Scratch/ToB2
2018/09/05 07:45:04 ERROR : CAM 2/AVF_INFO/AVIN0001.INT: File not in Local file system at /Volumes/Scratch/ToB2
2018/09/05 07:45:04 ERROR : CAM 2/AVF_INFO/PRV00001.BIN: File not in Local file system at /Volumes/Scratch/ToB2

Use error output to create diff file

By massaging the rclone check standard output into a new file with just the file names, it is possible to re-sync just these files. This saves us Backblaze read transactions on the files already copied.

Assuming a file mydiff.txt:


the sync command is:

rclone sync <source> <dest> --files-from mydiff.txt <other flags>

Then, run rclone check again on all the files.

The cleanup command

If your buckets are created with default settings, the file lifecyle is set to Keep all versions.

To purge deleted files, use a similar syntax to the lsd command.

rclone lsd b2-remote1:Videos/"Subfolder With Spaces/"

Also note that3:

Note that cleanup will remove partially uploaded files from the bucket if they are more than a day old.


Performance Logs

The exact command I used at first was

rclone sync b2-krish:Footage/"SD Card Archives/" b2-org:RawFiles/"Offloaded Video" -vv --exclude .DS_Store

and it completed, roughly 3 days later with a 5% error rate.

2018/08/12 19:46:55 INFO  : 
Transferred:   1.674 TBytes (6.491 MBytes/s)
Errors:                63
Checks:              2173
Transferred:         1123
Elapsed time:   75h6m1.4s
 *   CAM 1/PRIVATE/XDROOT/Clip/Clip0026.MXF: 99% /53.023G, 4.727M/s, 42s
2018/08/12 19:47:23 ERROR : Attempt 3/3 failed with 63 errors and: unexpected EOF
2018/08/12 19:47:23 Failed to sync: unexpected EOF

Instead, by using a chunk size 1G and two max transfers (total 2G in RAM at a time) transfers were noticeably more stable.

 2018/09/01 22:40:13 INFO  : 
Transferred:   52.430 GBytes (5.752 MBytes/s)
Errors:                 0
Checks:               232
Transferred:          776
Elapsed time:  2h35m33.8s

2018/09/01 22:40:13 DEBUG : 14 go routines active
2018/09/01 22:40:13 DEBUG : rclone: Version "v1.42" finishing with parameters ["rclone" "copy" "b2-org:RawFiles/Offloaded Video/" "b2-org:RawFiles/SD Cards/" "--exclude" ".DS_Store" "-vv" "--transfers" "2" "--b2-chunk-size" "1G" "--b2-upload-cutoff" "1G" "--max-size" "1G"]

Upload cutoffs of “5G”

During my experiments, I once tried a 5G single-part cutoff: --b2-chunk-size 2G --b2-upload-cutoff 5G --max-size 5G. The docs state This value should be set no larger than 4.657GiB (== 5GB) however it threw this error.

2018/09/03 13:56:01 Failed to copy: File size too big: 5022908174 (400 bad_request)

So apparently 5G is too high. 4G worked fine though.

500 Internal Server Error

Something is wrong with Backblaze, usually a transient problem. Rclone will retry, by default up to 10 times with built-in rate limiting (pacer) as shown with the incident a7691a3d7f71-e47fc872d7ba below.

2018/08/09 16:53:12 DEBUG : CAM A/private/M4ROOT/CLIP/C0002.MP4: Error sending chunk 2 (retry=true): incident id a7691a3d7f71-e47fc872d7ba (500 internal_error): &api.Error{Status:500, Code:"internal_error", Message:"incident id a7691a3d7f71-e47fc872d7ba"}
2018/08/09 16:53:12 DEBUG : CAM A/private/M4ROOT/CLIP/C0002.MP4: Clearing part upload URL because of error: incident id a7691a3d7f71-e47fc872d7ba (500 internal_error)
2018/08/09 16:53:12 DEBUG : pacer: Rate limited, increasing sleep to 20ms
2018/08/09 16:53:12 DEBUG : pacer: low level retry 1/10 (error incident id a7691a3d7f71-e47fc872d7ba (500 internal_error))
2018/08/09 16:53:12 DEBUG : CAM A/private/M4ROOT/CLIP/C0002.MP4: Sending chunk 2 length 100663296