Performance

Benchmark results

Measured 2026-04-26 on c6in.4xlarge (x86_64, 16 vCPUs, 32 GiB, Up to 50 Gigabit). Full corpus: 39 images, 51 tags. Cold sync to ECR us-east-2. All traffic routed through bench-proxy for byte-accurate measurement.

Metricocyncdregsyregsync
Wall clock3m 49s15m 16s12m 57s
Peak RSS65.2 MB331.0 MB28.0 MB
Requests7373113019532
Response bytes55.4 GB55.3 GB65.3 GB
Source blob GETs145113721695
Source blob bytes55.4 GB55.3 GB65.3 GB
Mounts (success/attempt)448/454325/3250/0
Duplicate blob GETs010
CDN hits/misses1045/11971/5988/0
Rate-limit 429s000

Artifact sync (OCI 1.1 referrers). ocync issues GET /v2/<repo>/referrers/... to discover attestations (SBOM, SLSA) and transfers any referenced artifacts. Comparable tools do not implement this. The Source blob GETs and Source blob bytes columns above include this artifact content for ocync.

Referrers calls — dregsy: 0, regsync: 0, ocync: 51.

How tools compare

Upload strategy

ToolStrategyRequests/blobBuffering
ocyncPOST + streaming PUT2None (streamed)
containerdPOST + streaming PUT2None (io.Pipe)
regsync (regclient)POST + PUT2None (streamed)
crane (go-containerregistry)POST + streaming PATCH + PUT3None (streamed)
skopeo (containers/image)POST + PATCH + PUT3None (streamed)

Concurrency and rate limiting

ToolDefault concurrencyAdaptive backoffRate limit strategy
ocync5 initial, 50 cap (AIMD adaptive)Yes (per-registry, per-action)AIMD congestion control on 429
containerd3 layersNoLock-based dedup, retries on 429 (no backoff)
regsync3 per registryNoReads RateLimit-Remaining header, pauses proactively
crane4 jobsRetry with backoff (0.1s-0.9s transport, 1s-9s operation)Retry on 408/5xx, 3 attempts
skopeo6 layersYes (exponential, 2s-60s)Retries 429 for GET/HEAD; uploads not retried

Blob deduplication

ToolCross-image push dedupCross-image pull dedupPersistent cache
ocyncYes (TransferStateCache)Yes (BlobStage)Yes (postcard binary)
containerdYes (StatusTracker)NoNo
regsyncNoNoNo (in-memory only)
craneYes (sync.Map by digest)NoNo
skopeoNoNoSQLite BlobInfoCache (mount hints)

Warm sync efficiency

Measured 2026-04-26 on c6in.4xlarge (x86_64, 16 vCPUs, 32 GiB, Up to 50 Gigabit). Full corpus: 39 images, 51 tags. Warm sync (no changes) to ECR us-east-2. All traffic routed through bench-proxy for byte-accurate measurement.

Metricocyncdregsyregsync
Wall clock2s2m 4s12s
Peak RSS18.5 MB34.2 MB21.0 MB
Requests1473145170
Response bytes135.1 KB1.9 MB124.5 KB
Source blob GETs02270
Source blob bytes125.7 KB1.9 MB120.4 KB
Mounts (success/attempt)0/00/00/0
Duplicate blob GETs000
CDN hits/misses0/0165/00/0
Rate-limit 429s000

ocync skips tag enumeration when exact tags are configured (no wildcard, semver, or latest filters), using the configured tag names directly instead of paginating through the registry’s full tag list. Combined with the transfer state cache and source manifest HEAD checks, this reduces a no-op warm sync to ~150 requests regardless of how many tags exist at the source.

Why ocync is fast

Pipelined architecture

Discovery and execution overlap. While images are being transferred, ocync is already discovering the next batch of work. There is no idle time between phases.

Global blob deduplication

Container images share layers heavily. A sync run touching 39 images may reference only a fraction as many unique blobs. ocync tracks every blob globally and transfers each unique blob exactly once.

Cross-repo blob mounting

When a blob already exists in another repository on the same registry, ocync mounts it (a server-side copy) instead of uploading. Images are elected as leaders using a greedy set-cover algorithm; each leader uploads all its blobs and commits its manifest. Followers mount shared blobs from leader repositories, with mount sources restricted to repos that have committed manifests.

Adaptive rate limiting

Per-(registry, action) AIMD (additive increase, multiplicative decrease) concurrency windows discover actual registry capacity through feedback, using the same algorithm TCP uses for congestion control. ECR has 9 independent rate limits (one per API action), and ocync tracks each independently. This avoids under-utilization and minimizes 429 errors through capacity discovery.

Transfer state cache

A persistent cache records which blobs exist at each target. Subsequent sync runs skip redundant HEAD checks for known-good blobs, reducing API calls on warm runs.

Streaming transfers

In single-target mode, bytes flow directly from source to target with no intermediate disk buffering. In multi-target mode, blobs stage to disk once and push to all targets in parallel.

Tuning

ocync auto-discovers registry capacity through AIMD feedback. Manual tuning is rarely needed. The key settings (see configuration for full reference):

  • Concurrency: starts conservatively and ramps up. Each registry action has its own window. Override with global.max_concurrent_transfers or per-registry max_concurrent.
  • Platform filter: sync only the architectures you deploy. linux/amd64 alone halves transfer volume for multi-arch images.
  • Tag filter: use latest: N and semver ranges to sync only what you need.
  • Transfer state cache: enabled by default. Warm runs transfer only new or changed blobs. Control TTL with global.cache_ttl.