{"record":{"id":"e7777236c0eb7ef5","repo":"juicedata/juicefs","slug":"cannot-overwrite-uploaded-block-d-d","errorCode":null,"errorMessage":"Cannot overwrite uploaded block: %d < %d","messagePattern":"Cannot overwrite uploaded block: (.+?) < (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/chunk/cached_store.go","lineNumber":260,"sourceCode":"\t\twriteback: store.conf.Writeback,\n\t\ttierID:    tierID,\n\t}\n}\n\nfunc (s *wSlice) SetID(id uint64) {\n\ts.id = id\n}\n\nfunc (s *wSlice) SetWriteback(enabled bool) {\n\ts.writeback = enabled\n}\n\nfunc (s *wSlice) WriteAt(p []byte, off int64) (n int, err error) {\n\tif int(off)+len(p) > chunkSize {\n\t\treturn 0, fmt.Errorf(\"write out of chunk boundary: %d > %d\", int(off)+len(p), chunkSize)\n\t}\n\tif off < int64(s.uploaded) {\n\t\treturn 0, fmt.Errorf(\"Cannot overwrite uploaded block: %d < %d\", off, s.uploaded)\n\t}\n\n\t// Fill previous blocks with zeros\n\tif s.length < int(off) {\n\t\tzeros := make([]byte, int(off)-s.length)\n\t\t_, _ = s.WriteAt(zeros, int64(s.length))\n\t}\n\n\tfor n < len(p) {\n\t\tindx := s.index(int(off) + n)\n\t\tboff := (int(off) + n) % s.store.conf.BlockSize\n\t\tvar bs = pageSize\n\t\tif indx > 0 || bs > s.store.conf.BlockSize {\n\t\t\tbs = s.store.conf.BlockSize\n\t\t}\n\t\tbi := boff / bs\n\t\tbo := boff % bs\n\t\tvar page *Page","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/chunk/cached_store.go#L242-L278","documentation":"After part of a wSlice has been uploaded (s.uploaded marks the boundary), earlier bytes are considered immutable. WriteAt rejects any write whose offset is below the uploaded boundary to preserve consistency between staged data and already-persisted blocks. This prevents silently rewriting data that object storage already received.","triggerScenarios":"Calling wSlice.WriteAt with an off smaller than s.uploaded — e.g. re-writing an already-flushed prefix of the slice, a retry that re-issues an old write, or an offset-tracking bug where the caller's file position lags the uploaded boundary.","commonSituations":"Random-write workloads revisiting earlier regions of an append-only staging file; crash recovery or writeback replays reapplying stale writes; misordered internal upload/write pipelines in custom data paths.","solutions":["Only append beyond s.uploaded; compute write offsets relative to the current uploaded boundary.","Do not reuse a wSlice to rewrite uploaded regions — create a new slice/chunk for corrected data.","Check caller's offset bookkeeping (writeback/append position) to ensure it never goes backwards.","If triggered by retries, make retry logic idempotent by skipping writes below the uploaded boundary instead of reissuing them."],"exampleFix":"// before\nslice.WriteAt(data, oldOff) // oldOff < slice.uploaded\n// after\nif int64(oldOff) < int64(slice.uploaded) {\n    return fmt.Errorf(\"region already uploaded, skipping rewrite at %d\", oldOff)\n}\nslice.WriteAt(data, oldOff)","handlingStrategy":"validation","validationCode":"if int64(off) < int64(slice.uploaded) {\n    return errors.New(\"cannot write below uploaded boundary; use an append offset\")\n}","typeGuard":null,"tryCatchPattern":"n, err := slice.WriteAt(p, off)\nif err != nil && strings.Contains(err.Error(), \"Cannot overwrite uploaded block\") {\n    // advance offset or allocate a new slice; never retry same offsets\n}","preventionTips":["Track append position from the slice's uploaded boundary","Make retries idempotent — skip writes already covered by upload"],"tags":["state","write","chunk"],"backgroundTag":"invalid-state-transition","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}