mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 22:40:39 +02:00
* feat(volume): X-Seaweedfs-Original-Size hint skips redundant gunzip The full-chunk gzip pass-through (#9425) fixed source-volume decompression but moved the cost to the destination volume: parseUpload still ran util.DecompressData on the forwarded gzipped bytes, just to learn the uncompressed length so it could record OriginalDataSize in the needle metadata. For 6-way concurrent 64 MiB UploadPartCopy that decompress-and-discard pass dominated the remaining heap profile after the streaming chain landed (~297 MiB inuse via bytes.Buffer.ReadFrom inside util.GunzipStream). Add an X-Seaweedfs-Original-Size header on the multipart part. When the upstream sets it (the s3 chunk-copy fast path always knows the uncompressed size — it's the source chunk's logical size) and no Content-MD5 verification is requested (which would require decompressed bytes to compute against), parseUpload uses the hint directly and skips the decompress. Header is X-* prefixed (not Seaweed-*) so it doesn't get auto-stored as a needle pair by PairNamePrefix. Backward compatible: - old s3 servers don't set the header, parseUpload decompresses as before - new s3 servers talking to old volumes: header is ignored, volume decompresses - bad header values (non-numeric, negative, garbage) fall back to the existing decompress path End-to-end repro impact (512 MiB src, 6 parallel UploadPartCopy, post-#9420/#9421/#9422/#9424/#9425 baseline): RSS, round 2: 1149 MiB → 594 MiB heap inuse_space: 545 MiB → 349 MiB HeapSys: 1.35 GiB → 777 MiB TotalAlloc cum: ~9 GiB → 3.5 GiB Total reduction from pre-#9420 baseline: 3134 → 594 MiB (-81%). Test exercises the four matrix corners (hint+no-MD5, hint+part-MD5, hint+req-MD5, no-hint, garbage-hint) and bounds allocation per case so a regression that re-introduces the unconditional decompress fails the hint-present-no-MD5 case. * review: bound X-Seaweedfs-Original-Size by sizeLimit and uint32 CodeQL on PR 9426 traced a new taint flow: the strconv.Atoi(hint) value flows into pu.OriginalDataSize -> originalSize -> the existing uint32(originalSize) cast in volume_server_handlers_write.go:73. The cast was always there but its input was previously bounded by the ParseUpload read path (capped at sizeLimit). Adding a user-controlled hint bypassed that bound, so a malicious header could overflow the uint32 silently. Bound the hint at parse time by sizeLimit (the largest needle this volume will accept anyway) and by math.MaxUint32 (belt-and-suspenders in case sizeLimit is configured > 4 GiB).
Needle Layout Format
This document describes the binary layout of the Needle structure as used in SeaweedFS storage, for all supported versions (v1, v2, v3).
A Needle represents a file or data blob stored in a volume file. The layout determines how the Needle is serialized to disk for efficient storage and retrieval.
Common Field Sizes
| Field | Size (bytes) |
|---|---|
| Cookie | 4 |
| NeedleId | 8 |
| Size | 4 |
| DataSize | 4 |
| Flags | 1 |
| NameSize | 1 |
| MimeSize | 1 |
| LastModified | 5 |
| Ttl | 2 |
| PairsSize | 2 |
| Checksum | 4 |
| Timestamp | 8 |
Needle Layouts by Version
Version 1
| Offset | Field | Size (bytes) | Description |
|---|---|---|---|
| 0 | Cookie | 4 | Random number to mitigate brute force lookups |
| 4 | Id | 8 | Needle ID |
| 12 | Size | 4 | Length of Data |
| 16 | Data | N | File data (N = Size) |
| 16+N | Checksum | 4 | CRC32 of Data |
| 20+N | Padding | 0-7 | To align to 8 bytes |
Version 2
| Offset | Field | Size (bytes) | Description |
|---|---|---|---|
| 0 | Cookie | 4 | Random number |
| 4 | Id | 8 | Needle ID |
| 12 | Size | 4 | Total size of the following fields |
| 16 | DataSize | 4 | Length of Data (N) |
| 20 | Data | N | File data |
| 20+N | Flags | 1 | Bit flags |
| 21+N | NameSize | 1 (opt) | Optional, if present |
| 22+N | Name | M (opt) | Optional, if present (M = NameSize) |
| ... | MimeSize | 1 (opt) | Optional, if present |
| ... | Mime | K (opt) | Optional, if present (K = MimeSize) |
| ... | LastModified | 5 (opt) | Optional, if present |
| ... | Ttl | 2 (opt) | Optional, if present |
| ... | PairsSize | 2 (opt) | Optional, if present |
| ... | Pairs | P (opt) | Optional, if present (P = PairsSize) |
| ... | Checksum | 4 | CRC32 |
| ... | Padding | 0-7 | To align to 8 bytes |
Version 3
| Offset | Field | Size (bytes) | Description |
|---|---|---|---|
| 0 | Cookie | 4 | Random number |
| 4 | Id | 8 | Needle ID |
| 12 | Size | 4 | Total size of the following fields |
| 16 | DataSize | 4 | Length of Data (N) |
| 20 | Data | N | File data |
| 20+N | Flags | 1 | Bit flags |
| 21+N | NameSize | 1 (opt) | Optional, if present |
| 22+N | Name | M (opt) | Optional, if present (M = NameSize) |
| ... | MimeSize | 1 (opt) | Optional, if present |
| ... | Mime | K (opt) | Optional, if present (K = MimeSize) |
| ... | LastModified | 5 (opt) | Optional, if present |
| ... | Ttl | 2 (opt) | Optional, if present |
| ... | PairsSize | 2 (opt) | Optional, if present |
| ... | Pairs | P (opt) | Optional, if present (P = PairsSize) |
| ... | Checksum | 4 | CRC32 |
| ... | Timestamp | 8 | Append time in nanoseconds |
| ... | Padding | 0-7 | To align to 8 bytes |
- Offsets marked with
...depend on the presence and size of previous optional fields. - Fields marked
(opt)are optional and only present if the corresponding size or flag is non-zero. - N = DataSize, M = NameSize, K = MimeSize, P = PairsSize.
Field Explanations
- Cookie: 4 bytes, random value for security.
- Id: 8 bytes, unique identifier for the Needle.
- Size: 4 bytes, total size of the Needle data section (not including header, checksum, timestamp, or padding).
- DataSize: 4 bytes, length of the Data field.
- Data: File data (variable length).
- Flags: 1 byte, bit flags for Needle properties.
- NameSize/Name: 1 byte + variable, optional file name.
- MimeSize/Mime: 1 byte + variable, optional MIME type.
- LastModified: 5 bytes, optional last modified timestamp.
- Ttl: 2 bytes, optional time-to-live.
- PairsSize/Pairs: 2 bytes + variable, optional key-value pairs.
- Checksum: 4 bytes, CRC32 checksum of the Needle data.
- Timestamp: 8 bytes, append time (only in v3).
- Padding: 0-7 bytes, to align the total Needle size to 8 bytes.
Version Comparison Table
| Field | v1 | v2 | v3 |
|---|---|---|---|
| Cookie | ✔ | ✔ | ✔ |
| Id | ✔ | ✔ | ✔ |
| Size | ✔ | ✔ | ✔ |
| DataSize | ✔ | ✔ | |
| Data | ✔ | ✔ | ✔ |
| Flags | ✔ | ✔ | |
| NameSize/Name | ✔ | ✔ | |
| MimeSize/Mime | ✔ | ✔ | |
| LastModified | ✔ | ✔ | |
| Ttl | ✔ | ✔ | |
| PairsSize/Pairs | ✔ | ✔ | |
| Checksum | ✔ | ✔ | ✔ |
| Timestamp | ✔ | ||
| Padding | ✔ | ✔ | ✔ |
Flags Field Details
The Flags field (present in v2 and v3) is a bitmask that encodes several boolean properties of the Needle. Each bit has a specific meaning:
| Bit Value | Name | Meaning |
|---|---|---|
| 0x01 | FlagIsCompressed | Data is compressed (isCompressed) |
| 0x02 | FlagHasName | Name field is present (NameSize/Name) |
| 0x04 | FlagHasMime | Mime field is present (MimeSize/Mime) |
| 0x08 | FlagHasLastModifiedDate | LastModified field is present |
| 0x10 | FlagHasTtl | Ttl field is present |
| 0x20 | FlagHasPairs | Pairs field is present (PairsSize/Pairs) |
| 0x80 | FlagIsChunkManifest | Data is a chunk manifest (for large files) |
- If a flag is set, the corresponding field(s) will appear in the Needle layout at the appropriate position.
- The
Flagsfield is always present in v2 and v3, immediately after the Data field.
Optional Fields
- Fields marked as optional in the layout tables are only present if the corresponding flag in the
Flagsfield is set (except for Name/Mime/Pairs, which also depend on their size fields being non-zero). - The order of optional fields is fixed and matches the order of their flags.
Special Notes
- isCompressed: If set, the Data field is compressed (typically using gzip). This is indicated by the lowest bit (0x01) in the Flags byte.
- isChunkManifest: If set, the Data field contains a manifest describing chunks of a large file, not raw file data.
- All multi-byte fields are stored in big-endian order.
- Padding is always added at the end to align the total Needle size to 8 bytes.
- N = DataSize, M = NameSize, K = MimeSize, P = PairsSize in the layout tables above.
For more details, see the implementation in the corresponding Go files in this directory.