12 KiB
Single-Trip FastOpen GET - Design Note
Status: Implemented in this branch
Summary: Buckit's final FastOpen design does not add current/ shadow
directories, versions/<id>/ direct paths, or write-side shadow repair. Instead,
it adds a read-side storage protocol that lets each disk answer a GET with a
single streamed response:
- read that disk's
xl.meta; - encode the chosen object version into a compact FastOpen frame;
- immediately continue with the shard body bytes from the same stream when the object is streamable.
The landing node opens FastOpen streams on a first wave of disks, reads only the
frame headers to establish the winning object/version/layout, then reuses the
already-open body streams for decode. If the first wave is not sufficient, it
opens additional disks or falls back to canonical GET. xl.meta remains
canonical; FastOpen is an opportunistic read optimization only.
This note describes the shipped FastOpen design. It supersedes earlier
proposals based on current/part.1, versions/<versionId>/, and write-side
shadow copies.
Read request-flow.md first; this note assumes its vocabulary (xl.meta,
erasure sets, parts, FileInfo, quorum, distribution, local vs remote disk
access).
1. Goal
Canonical GET pays two logical storage phases:
- read
xl.metaenough times to establish the visible version and layout; - open shard data files and decode from the selected readers.
FastOpen keeps the same correctness rules but collapses those into one per-disk storage request. Each participating disk returns metadata and, when possible, body bytes from the same open stream.
This is a GET-only optimization. It does not change the canonical write format, object version layout, or healing authority model.
2. High-level architecture
2.1 Canonical state stays unchanged
FastOpen does not introduce a second on-disk object namespace.
xl.metaremains the source of truth for visible versions, delete markers, transition state, erasure layout, and object metadata.- The ordinary shard files under the version's existing
DataDirremain the only physical shard bytes. - PUT/DELETE do not create
current/shadow copies orversions/<id>/direct paths for FastOpen.
FastOpen is therefore always allowed to fall back to the normal xl.meta path
without any repair or reconciliation step.
2.2 New storage primitive
Each disk implements:
StorageAPI.FastOpenPart
This operation:
- reads the disk-local
xl.meta; - resolves the requested object version (
latestor an explicitVersionID); - converts the chosen object version into a compact
FastOpenGETMeta; - returns a stream whose first bytes are a checked FastOpen frame;
- appends either:
- shard bytes,
- inline bytes,
- no body for metadata-only outcomes,
- or a transitioned marker.
This works both locally and over storage REST. The landing node therefore uses the same abstraction for local and remote disks.
3. FastOpen frame protocol
The protocol is defined in cmd/fastopen-frame.go.
Each successful FastOpen stream begins with:
- a fixed prelude:
- magic
- protocol version
- payload length
- CRC
- body mode
- a compact encoded payload:
- object/version identity
- part metadata
- erasure metadata
- transition metadata when relevant
Body modes are:
FastOpenBodyShardFastOpenBodyInlineFastOpenBodyMetadataOnlyFastOpenBodyTransitioned
Frame statuses are:
FastOpenStatusOKFastOpenStatusDeleteMarkerFastOpenStatusNotFoundFastOpenStatusVersionNotFoundFastOpenStatusUnsupported
Important design point:
- FastOpen does not bypass
xl.metaon the storage node. - It bypasses a second request phase from the landing node by coalescing metadata and body into one stream per disk.
4. Request eligibility
FastOpen is intentionally narrow. The request-level gate is implemented in cmd/fastopen-get.go.
FastOpen is attempted only when:
BUCKIT_FAST_GET=1opts.FastGetObjInfois set by the caller- the bucket is not the internal metadata bucket
- the request is not a
PartNumberGET - the request is not a range GET
- the request is not a replication or proxy request
- SSE-C is not requested
At object/layout selection time, FastOpen may still reject the object and fall back. Important fallback cases include:
- unsupported frame status from any selected disk
- unsupported checksum algorithm
- inability to assemble enough valid readers
- body mode/layout combinations not handled by the FastOpen reader path
- non-zero body offset after
NewGetObjectReader
Delete markers, zero-byte objects, and transitioned objects are still handled via FastOpen metadata when possible, but they return metadata-only object-level results rather than streamed shard decode.
5. Landing-node read flow
The read path lives in cmd/fastopen-get.go.
5.1 First wave
tryFastOpenGET calls openFastOpenGETInfo, which:
- chooses an initial wave of online disks;
- opens
FastOpenParton each selected disk; - reads only the FastOpen frame from each stream;
- leaves the stream positioned immediately after the frame.
Disk selection uses two modes:
- default: local-first, then stable disk order
- spread mode: rotate the first wave by object hash when
BUCKIT_FASTGET_SPREAD=1
The initial open count is based on the set's configured data/parity shape, not the object's eventual winning layout. If the object's actual layout needs more help than the first wave provides, FastOpen opens additional disks.
5.2 Quorum and winning object selection
FastOpen does not invent new correctness rules. After reading the per-disk
frames, it rebuilds FileInfo values and reuses the same quorum and version
selection rules as canonical GET:
objectQuorumFromMetareduceReadQuorumErrslistOnlineDiskspickValidFileInfofilterOnlineDisksInplace
That means:
NotFoundandVersionNotFoundare still object-level outcomes only after normal quorum rules are applied.- stale but individually valid disk metadata does not win the read.
xl.metasemantics remain canonical even though the landing node did not call the normal metadata fan-out helper.
5.3 Replacement wave
If the first wave is not enough before response commit:
- FastOpen records a replacement-path attempt.
- It opens the remaining online disks.
- It retries object selection with the larger set.
If selection still cannot succeed, the request falls back to canonical GET unless
BUCKIT_FASTGET_NO_FALLBACK=1, in which case the request returns an error.
6. Reader assembly and decode
6.1 Reusing the opened body streams
Once the winning FileInfo is known, FastOpen maps the already-open per-disk
streams into erasure-slot readers.
For directly usable body streams:
- shard mode becomes a
fastOpenStreamingBitrotReader - inline mode is also accepted when it can be validated safely
Readers are placed by Erasure.Index, not by response order, so they match the
same slot semantics canonical decode expects.
6.2 Lazy replacement readers
If the initially selected body streams do not cover enough slots, FastOpen can fill missing positions with lazy replacement readers.
The lazy replacement path:
- records which disks are already engaged;
- chooses the one disk that can satisfy each missing erasure slot;
- opens a new
FastOpenPartstream only when that slot is actually read; - validates that the replacement frame still matches the winning object/layout;
- resumes decode from the requested shard offset.
This allows FastOpen to recover some pre-commit selection failures and some decode-time missing-reader situations without abandoning the request before body streaming begins.
6.3 Streaming bitrot verification
The FastOpen body path uses HighwayHash256S only. Other algorithms currently
force fallback.
The streaming reader verifies the existing on-disk bitrot layout inline:
- read block hash
- read block bytes
- recompute hash
- compare
No new shard-body format is introduced. FastOpen only changes how the landing node gets to the shard bytes.
6.4 Range behavior
The current FastOpen path is effectively full-object only:
- request-level range GETs are rejected up front
- if
NewGetObjectReadercomputes a non-zero offset, FastOpen falls back
Multipart part selection is also not supported in the FastOpen path.
7. Fallback model
FastOpen is allowed to fail only before it commits to a streaming response as the request's object-selection path.
Pre-commit failures fall into two categories:
ok=false: abandon FastOpen and fall back to canonical GETok=true, err!=nil: FastOpen determined the object-level result, including delete marker or quorum failure cases
Once FastOpen has started the body goroutine and returned a GetObjectReader,
mid-stream failures are reported as stream errors, not by restarting through the
canonical path. This matches the fact that HTTP response commit has already
happened.
This is the central simplification versus the abandoned current/ design:
- no request-time repair
- no direct-path reconciliation
- no crash-state cleanup
- no write-side invalidation rules
Fallback is always safe because the canonical layout was never changed.
8. Observability
FastOpen observability is exposed under
/minio/metrics/v3/api/requests in
cmd/metrics-v3-api.go.
Counters include:
fast_open_attempted_totalfast_open_hits_totalfast_open_unsupported_totalfast_open_replacement_path_totalfast_open_streams_opened_totalfast_open_replacement_opens_totalfast_open_selected_set_failures_totalfast_open_stream_cancellations_totalfast_open_final_errors_totalfast_open_httptrace_connections_totalfast_open_httptrace_reused_connections_totalfast_open_httptrace_fresh_connections_totalfast_open_httptrace_was_idle_connections_total
Timing metrics include:
fast_open_try_seconds_totalfast_open_try_seconds_countfast_open_open_info_seconds_totalfast_open_open_info_seconds_countfast_open_body_decode_seconds_totalfast_open_body_decode_seconds_count
The old BUCKIT_FASTOPEN_PROFILE stderr logging path has been removed. Timing is
now metrics-based.
9. What the final implementation is not
The current FastOpen implementation does not do any of the following:
- no
current/part.Nshadow copy - no
versions/<versionId>/part.Ndirect namespace - no
renameat2(RENAME_EXCHANGE)-based latest swap - no write-side direct-path install/invalidate protocol
- no crash-state repair for direct data paths
- no scanner-driven direct-path reconciliation
- no arbitrary range or multipart FastOpen read path
- no support for all checksum algorithms
Those ideas belonged to earlier design exploration. They are not part of the implemented FastOpen shipped in this branch.
10. Expected behavior and tradeoffs
FastOpen improves the healthy full-object GET path by removing one landing-node
request phase to each participating disk. It does not remove the disk-local
xl.meta lookup itself.
Benefits:
- lower first-byte latency when the request stays on the FastOpen path
- fewer landing-node storage RPC phases
- opportunistic reuse of already-open body streams
- no write-path format migration
Costs and limitations:
- narrow eligibility
- fallback is still common for unsupported objects/requests
- some recovery cases require opening additional disks
- mid-stream errors are not converted into a new canonical GET
- observability is required to know whether a benchmark actually hit the path
This is therefore best understood as a conservative read-path optimization layered on top of Buckit's existing metadata and erasure semantics.
11. References
- cmd/fastopen-frame.go - FastOpen frame protocol and compact metadata encoding
- cmd/fastopen-part.go - disk-side
FastOpenPartimplementation - cmd/fastopen-get.go - landing-node FastOpen GET path
- cmd/fastget-config.go - runtime flags
- cmd/metrics-v3-api.go - exported metrics
- docs/single-trip-get-phase1-implementation.md - historical prototype/implementation notes