mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-02 10:17:10 +01:00
S3: track manifest blob ownership through multipart completion (#10386)
* s3: track manifest blob ownership through multipart completion Manifest blobs made three orphan paths. A partial fold that failed midway kept its earlier batches on volume servers while the write fell back to flat chunks; the fold now records each saved blob and deletes them on error. A completion that failed after preparing left its fresh manifests behind on every retry; the completion state now owns them and deletes them unless a failed rollback left the version entry still holding them. And a completed upload removes its parts metadata-only, which stranded the part-manifest blobs superseded by flattening; those are collected during flattening and deleted once the completion commits. Two shared-chunk hazards nearby: the version-file rollback deleted its data, destroying the still-registered parts (worse once manifests resolve to inner chunks), and the idempotent-replay cleanup data-deleted leftover parts whose chunks the live object references. Both are metadata-only now. * s3: trim chunk manifest comments * s3: test manifest fold rollback and part range selection The fold-with-rollback and the boundary-to-byte-range logic were only exercised by hand against a live server; give both an injectable seam and cover the fold, the below-threshold and SSE no-ops, the midway failure deleting the blobs it saved, and offset-vs-legacy-index range selection including indexes that no longer address the chunk list.
This commit is contained in:
1 parent
5a54beac80
commit
68a4e3347f
7 files changed
+235
-68
No files matched your search
@@ -853,21 +853,14 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
|
||||
// Note: ETag is NOT overridden - AWS S3 returns the complete object's ETag
|
||||
// even when requesting a specific part via PartNumber
|
||||
var startOffset, endOffset int64
|
||||
if partInfo != nil && partInfo.EndOffset > partInfo.StartOffset {
|
||||
// Byte-offset boundaries: valid regardless of how the entry's
|
||||
// chunk list is laid out (flat or manifest chunks)
|
||||
startOffset = partInfo.StartOffset
|
||||
endOffset = partInfo.EndOffset - 1
|
||||
} else if partInfo != nil {
|
||||
// Legacy boundaries carry chunk indexes into the flat chunk list
|
||||
if partInfo.StartChunk < 0 || partInfo.EndChunk <= partInfo.StartChunk || partInfo.EndChunk > len(objectEntryForSSE.Chunks) {
|
||||
if partInfo != nil {
|
||||
var ok bool
|
||||
startOffset, endOffset, ok = partRange(partInfo, objectEntryForSSE.Chunks)
|
||||
if !ok {
|
||||
glog.Errorf("GetObject: part %d boundary chunks [%d,%d) out of range (chunks: %d)", partNumber, partInfo.StartChunk, partInfo.EndChunk, len(objectEntryForSSE.Chunks))
|
||||
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
||||
return
|
||||
}
|
||||
startOffset = objectEntryForSSE.Chunks[partInfo.StartChunk].Offset
|
||||
lastChunk := objectEntryForSSE.Chunks[partInfo.EndChunk-1]
|
||||
endOffset = lastChunk.Offset + int64(lastChunk.Size) - 1
|
||||
} else {
|
||||
// Fallback: assume 1:1 part-to-chunk mapping (backward compatibility)
|
||||
chunkIndex := partNumber - 1
|
||||
@@ -3047,13 +3040,26 @@ type PartBoundaryInfo struct {
|
||||
StartChunk int `json:"start"`
|
||||
EndChunk int `json:"end"` // exclusive
|
||||
ETag string `json:"etag"`
|
||||
// Byte offsets of the part within the object; preferred over the chunk
|
||||
// indexes, which stop matching once the entry's chunk list is folded
|
||||
// into manifest chunks. Zero EndOffset means a legacy boundary record.
|
||||
// Byte offsets of the part; zero EndOffset means a legacy record carrying
|
||||
// chunk indexes only.
|
||||
StartOffset int64 `json:"startOffset,omitempty"`
|
||||
EndOffset int64 `json:"endOffset,omitempty"` // exclusive
|
||||
}
|
||||
|
||||
// partRange returns the inclusive byte range of a part, preferring byte
|
||||
// offsets; ok is false when a legacy record's chunk indexes do not address
|
||||
// the entry's chunk list.
|
||||
func partRange(partInfo *PartBoundaryInfo, chunks []*filer_pb.FileChunk) (startOffset, endOffset int64, ok bool) {
|
||||
if partInfo.EndOffset > partInfo.StartOffset {
|
||||
return partInfo.StartOffset, partInfo.EndOffset - 1, true
|
||||
}
|
||||
if partInfo.StartChunk < 0 || partInfo.EndChunk <= partInfo.StartChunk || partInfo.EndChunk > len(chunks) {
|
||||
return 0, 0, false
|
||||
}
|
||||
lastChunk := chunks[partInfo.EndChunk-1]
|
||||
return chunks[partInfo.StartChunk].Offset, lastChunk.Offset + int64(lastChunk.Size) - 1, true
|
||||
}
|
||||
|
||||
// rc is a helper type that wraps a Reader and Closer for proper resource cleanup
|
||||
type rc struct {
|
||||
io.Reader
|
||||
|
||||
Reference in new issue
Block a user