juicedata/juicefs · error

UploadPart

Error message

UploadPart: %s

What it means

Documentation placeholder for ufile UploadPart errors: the PUT request uploading one multipart part to UFile failed; UFile additionally requires part numbers to start from zero and be continuous, which the client adjusts for before this request.

Solutions

  1. Check the wrapped parseError message for the actual UFile error code
  2. Ensure non-final parts meet UFile's minimum part size (e.g. 4 MiB)
  3. Verify access/secret keys and bucket binding
  4. Retry the failed part; if ETag missing, check intermediaries/proxies stripping headers

Example fix

// before
u.UploadPart(ctx, key, uploadID, num, 1<<20) // 1MB part
// after
u.UploadPart(ctx, key, uploadID, num, 8<<20) // >= 4MB part
Defensive patterns

Strategy: try-catch

Validate before calling

func validPartSize(sz int) error { if sz < 4<<20 { return errors.New("part too small for UFile") }; return nil }

Try / catch

p, err := store.UploadPart(ctx, key, uploadID, num, data)
if err != nil && strings.HasPrefix(err.Error(), "UploadPart:") {
	// backoff and retry this single part
}

Prevention

When it happens

Trigger: PUT of a part to UFile fails: signature error, part size below minimum (UFile requires parts >4MB except last), quota exceeded, network drop producing 5xx, or server omits the Etag header.

Common situations: Uploading large files where middle parts are too small; wrong secret key; UFile bucket storage class rejecting the upload; proxy stripping the Etag header.

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/8145dc6a3f03e04a. Report an issue: GitHub.

Appendix: source

Thrown at pkg/object/ufile.go:296

	}
	var out ufileCreateMultipartUploadResult
	if err := u.parseResp(resp, &out); err != nil {
		return nil, err
	}
	return &MultipartUpload{UploadID: out.UploadId, MinPartSize: int64(out.BlkSize), MaxCount: 1000000}, nil
}

func (u *ufile) UploadPart(ctx context.Context, key string, uploadID string, num int, data []byte) (*Part, error) {
	// UFile require the PartNumber to start from 0 (continuous)
	num--
	path := fmt.Sprintf("%s?uploadId=%s&partNumber=%d", key, uploadID, num)
	resp, err := u.request(ctx, "PUT", path, bytes.NewReader(data), nil)
	if err != nil {
		return nil, err
	}
	defer cleanup(resp)
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("UploadPart: %s", parseError(resp).Error())
	}
	etags := resp.Header["Etag"]
	if len(etags) < 1 {
		return nil, errors.New("No ETag")
	}
	return &Part{Num: num, Size: len(data), ETag: strings.Trim(etags[0], "\"")}, nil
}

func (u *ufile) AbortUpload(ctx context.Context, key string, uploadID string) {
	_, _ = u.request(ctx, "DELETE", key+"?uploads="+uploadID, nil, nil)
}

func (u *ufile) CompleteUpload(ctx context.Context, key string, uploadID string, parts []*Part) error {
	etags := make([]string, len(parts))
	for i, p := range parts {
		etags[i] = p.ETag
	}
	resp, err := u.request(ctx, "POST", key+"?uploadId="+uploadID, bytes.NewReader([]byte(strings.Join(etags, ","))), nil)

View on GitHub (pinned to c9a67b23e8)