juicedata/juicefs · error

multipart: %s

Error message

multipart: %s

What it means

Wraps a failure of CompleteUpload after all parts of a multipart copy were uploaded. When dst.CompleteUpload fails 3 times (via try), the multipart upload is aborted and the error is reported as 'multipart: <err>'. The upload produced no object; the caller must retry the whole copy.

Source

Thrown at pkg/sync/sync.go:913

		parts[i], chksum, err = doUploadPart(src, dst, key, off+int64(i)*partSize, sz, tmpkey, up.UploadID, i, calChksum)
		if err != nil {
			dst.AbortUpload(ctx, tmpkey, up.UploadID)
			return nil, 0, fmt.Errorf("range(%d,%d): %s", off, size, err)
		}
		if calChksum {
			if first {
				tmpChksum = chksum
				first = false
			} else {
				tmpChksum = crc32combine.CRC32Combine(crc32.Castagnoli, tmpChksum, chksum, sz)
			}
		}
	}

	err = try(3, func() error { return dst.CompleteUpload(ctx, tmpkey, up.UploadID, parts) })
	if err != nil {
		dst.AbortUpload(ctx, tmpkey, up.UploadID)
		return nil, 0, fmt.Errorf("multipart: %s", err)
	}
	var part *object.Part
	err = try(3, func() error {
		part, err = dst.UploadPartCopy(ctx, key, upload.UploadID, num+1, tmpkey, 0, size)
		return err
	})
	_ = dst.Delete(ctx, tmpkey)
	return part, tmpChksum, err
}

func doCopyMultiple(src, dst object.ObjectStorage, key string, size int64, mtime time.Time, upload *object.MultipartUpload, calChksum bool, uploads multipartUploads) (uint32, error) {
	limits := dst.Limits()
	if size > limits.MaxPartSize*int64(upload.MaxCount) {
		return 0, fmt.Errorf("object size %d is too large to copy", size)
	}
	partSize := choosePartSize(upload, size)
	n := int((size-1)/partSize) + 1
	logger.Debugf("Copying data of %s as %d parts (size: %d): %s", key, n, partSize, upload.UploadID)

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Check the wrapped error for expired/aborted upload ID and rerun sync — a fresh upload will be started automatically.
  2. Remove or extend S3 'abort incomplete multipart upload' lifecycle rules on the destination bucket for large copies.
  3. Verify part ETags/validity: avoid concurrent writers to the same destination key.
  4. Retry during off-peak hours or with fewer --threads if the destination rejects the complete call under load.
Defensive patterns

Strategy: retry

Validate before calling

// ensure no lifecycle rule aborts incomplete multipart uploads too early
rules := getS3LifecycleRules(dstBucket)
for _, r := range rules {
  if r.AbortIncompleteMultipartUpload != nil && r.AbortIncompleteMultipartUpload.DaysAfterInitiation < expectedUploadHours*24 {
    return fmt.Errorf("lifecycle rule would abort upload")
  }
}

Try / catch

if err := sync(...); err != nil {
  if strings.HasPrefix(err.Error(), "multipart:") {
    logger.Warnf("multipart complete failed, retrying sync: %v", err)
    return retryWithBackoff(sync, 3)
  }
  return err
}

Prevention

When it happens

Trigger: dst.CompleteUpload(ctx, tmpkey, up.UploadID, parts) returns an error (retried 3 times) — typically because one or more uploaded parts are missing/invalid (ETag mismatch), the upload ID was expired, or the destination rejected the complete call.

Common situations: Long uploads where the destination expired the multipart upload ID (e.g. S3 lifecycle rule aborting incomplete multipart uploads); part list mismatch after a retried/aborted part; destination service outage during CompleteUpload.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/e814b829219a4ee8. Report an issue: GitHub.