Files
seaweedfs/weed/storage/needle
Chris Lu c917110124 fix(volume): pre-size ParseUpload buffer to request ContentLength (#9421)
* fix(volume): pre-size ParseUpload buffer to request ContentLength

The volume server's PostHandler reads the multipart upload body via
bytes.Buffer.ReadFrom inside parseUpload. The buffer comes from a
sync.Pool and may have cap=0 when the pool dropped the prior entry,
which makes ReadFrom geometric-grow on each chunk: a 64 MiB upload
allocates roughly 1+2+4+...+64 ≈ 128 MiB just to receive the body.
Under concurrent uploads (every s3 chunk-copy lands here on the
destination volume) this is one of the main contributors to the
runaway-RSS pattern in #6541 — pprof shows ~458 MiB cum in
parseUpload's bytes.Buffer.ReadFrom under Harbor-style assemble load.

Grow the buffer once up front, bounded by the existing sizeLimit so
a misreported Content-Length can't over-allocate. The receive then
fills in place.

Add a regression test that drives ParseUpload with a 16 MiB multipart
body and bounds TotalAlloc at 1.5x the chunk size (pre-fix measures
~4x, so the bound trips deterministically).

* fix(volume): guard ParseUpload pre-grow against int overflow on 32-bit

Address PR review feedback: r.ContentLength is int64, and on 32-bit
platforms int is 32 bits wide, so int(r.ContentLength) for a value above
math.MaxInt32 wraps negative and bytes.Buffer.Grow panics with
"bytes.Buffer.Grow: negative count". Skip the pre-grow optimization in
that range; the existing geometric-grow path remains correct, just
slightly more allocator pressure for that one call.

64-bit platforms (math.MaxInt == math.MaxInt64) are unaffected — the
guard only kicks in for 32-bit builds with very large sizeLimit.

* fix(volume): cap ParseUpload pre-grow at 4 MiB to bound DoS surface

Address PR review: pre-growing the receive buffer to r.ContentLength
trusts the header before any body bytes arrive. A bad header or slow /
idle client could declare a large Content-Length up to sizeLimit (256
MiB by default for volume writes) and force per-request preallocation
without sending data, turning many concurrent slow connections into
avoidable memory pressure.

Cap eager pre-grow at maxEagerPreGrow (4 MiB). Larger uploads still
benefit from the higher starting cap and fall back to ReadFrom's grow
path for the remainder. Per-request waste from a misreported
Content-Length is now bounded at 4 MiB regardless of sizeLimit.

Extract the policy as eagerPreGrow so the unit test can exercise the
gates structurally — replaces the prior TotalAlloc bound (which became
uninformative once savings were capped at 4 MiB).
2026-05-10 12:08:24 -07:00
..
2022-06-04 18:15:39 -07:00
2025-06-16 22:25:22 -07:00
2025-06-06 08:55:32 -07:00
2025-06-08 22:11:09 -07:00
2025-06-08 22:11:09 -07:00
2025-06-11 20:13:06 -07:00
2019-04-30 03:22:19 +00:00
2025-04-02 07:59:21 -07:00
2025-04-02 07:59:21 -07:00

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 Flags field 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 Flags field 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.