perf(get): enable inline direct-read by default + versioned buckets (#1802) (#5966)

A small object whose data shards are inlined in xl.meta can be reassembled
straight from the already-resolved metadata, skipping the Erasure reconstruct
pipeline. The fast path existed but was opt-in
(`RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY`, default off), so every deployment
paid the full shard-read fan-out for eligible small GETs by default.

- Flip `DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY` to `true`. The path is
  correctness-neutral on a miss: `try_get_object_direct_data_shards_*` returns
  None when the inline reassembly cannot satisfy the read, and the GET then
  proceeds through the normal shard-read pipeline. The env stays as a kill
  switch (`=false` restores the legacy path).
- Drop the bucket-level `versioned` / `version_suspended` exclusions. The
  decision is made on `fi` — the already-resolved target version — so
  reassembling its inlined data is correct on a versioned bucket too. An
  explicit versionId GET still falls back (`opts.version_id`), and a
  delete-marker latest is still rejected. The two now-unused fallback reasons
  and their metric labels are removed.
- Tests updated: a versioned latest-version object is now eligible / `Use`
  (covers the newly allowed path); the removed reasons' label assertions are
  dropped.

cargo check --lib --tests and the direct_memory unit tests pass on macOS
(the change is fully cross-platform).

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-12 10:06:39 +08:00
committed by GitHub
parent 8d34b4d101
commit 968ec4a8be
+18 -15
View File
@@ -671,7 +671,13 @@ const ENV_RUSTFS_GET_CODEC_STREAMING_DATA_BLOCKS_FIRST_MAX_SIZE: &str = "RUSTFS_
const DEFAULT_RUSTFS_GET_CODEC_STREAMING_DATA_BLOCKS_FIRST_MAX_SIZE: usize = 512 * 1024; const DEFAULT_RUSTFS_GET_CODEC_STREAMING_DATA_BLOCKS_FIRST_MAX_SIZE: usize = 512 * 1024;
const ENV_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY: &str = "RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY"; const ENV_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY: &str = "RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY";
const DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY: bool = false; // On by default (rustfs/backlog#1802): a small object whose data shards are
// inlined in xl.meta is reassembled straight from the already-resolved
// metadata, skipping the Erasure reconstruct pipeline. The path has a complete
// fallback — if the inline reassembly returns None, the GET proceeds through
// the normal shard-read pipeline, so a miss is correctness-neutral. Set to
// `false` to force the legacy path (kill switch).
const DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY: bool = true;
const ENV_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD: &str = "RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD"; const ENV_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD: &str = "RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD";
const DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD: usize = 128 * 1024; const DEFAULT_RUSTFS_GET_SMALL_OBJECT_DIRECT_MEMORY_THRESHOLD: usize = 128 * 1024;
@@ -1614,8 +1620,6 @@ enum GetDirectMemoryFallbackReason {
Range, Range,
PartNumber, PartNumber,
VersionId, VersionId,
Versioned,
VersionSuspended,
InclFreeVersions, InclFreeVersions,
SkipFreeVersion, SkipFreeVersion,
DataMovement, DataMovement,
@@ -1641,8 +1645,6 @@ impl GetDirectMemoryFallbackReason {
Self::Range => "range", Self::Range => "range",
Self::PartNumber => "part_number", Self::PartNumber => "part_number",
Self::VersionId => "version_id", Self::VersionId => "version_id",
Self::Versioned => "versioned",
Self::VersionSuspended => "version_suspended",
Self::InclFreeVersions => "incl_free_versions", Self::InclFreeVersions => "incl_free_versions",
Self::SkipFreeVersion => "skip_free_version", Self::SkipFreeVersion => "skip_free_version",
Self::DataMovement => "data_movement", Self::DataMovement => "data_movement",
@@ -1766,12 +1768,11 @@ fn get_small_object_direct_memory_decision_with_threshold(
if opts.version_id.is_some() { if opts.version_id.is_some() {
return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::VersionId); return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::VersionId);
} }
if opts.versioned { // Bucket-level versioning no longer blocks the inline path (rustfs/backlog#1802):
return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::Versioned); // `fi` here is the already-resolved target version, so reassembling its inlined
} // data shards is correct whether the bucket is versioned or not. This direct-memory
if opts.version_suspended { // decision still falls back for an explicit versionId (the `version_id` check above);
return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::VersionSuspended); // a delete-marker latest is rejected below.
}
if opts.incl_free_versions { if opts.incl_free_versions {
return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::InclFreeVersions); return GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::InclFreeVersions);
} }
@@ -8681,9 +8682,11 @@ mod tests {
128 * 1024 128 * 1024
)); ));
// Bucket-level versioning no longer blocks the inline path (rustfs/backlog#1802):
// a latest-version read on a versioned bucket is eligible.
let mut versioned_opts = opts.clone(); let mut versioned_opts = opts.clone();
versioned_opts.versioned = true; versioned_opts.versioned = true;
assert!(!is_get_small_object_direct_memory_eligible_with_threshold( assert!(is_get_small_object_direct_memory_eligible_with_threshold(
&None, &None,
&object_info, &object_info,
&fi, &fi,
@@ -8744,11 +8747,13 @@ mod tests {
GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::Range) GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::Range)
); );
// Bucket-level versioning no longer falls back (rustfs/backlog#1802): the
// latest version on a versioned bucket is served inline like any other.
let mut versioned_opts = opts.clone(); let mut versioned_opts = opts.clone();
versioned_opts.versioned = true; versioned_opts.versioned = true;
assert_eq!( assert_eq!(
get_small_object_direct_memory_decision_with_threshold(&None, &object_info, &fi, &versioned_opts, true, 128 * 1024), get_small_object_direct_memory_decision_with_threshold(&None, &object_info, &fi, &versioned_opts, true, 128 * 1024),
GetDirectMemoryDecision::Fallback(GetDirectMemoryFallbackReason::Versioned) GetDirectMemoryDecision::Use { object_size: 1024 }
); );
let mut encrypted = object_info.clone(); let mut encrypted = object_info.clone();
@@ -8796,8 +8801,6 @@ mod tests {
assert_eq!(GetDirectMemoryFallbackReason::Range.as_str(), "range"); assert_eq!(GetDirectMemoryFallbackReason::Range.as_str(), "range");
assert_eq!(GetDirectMemoryFallbackReason::PartNumber.as_str(), "part_number"); assert_eq!(GetDirectMemoryFallbackReason::PartNumber.as_str(), "part_number");
assert_eq!(GetDirectMemoryFallbackReason::VersionId.as_str(), "version_id"); assert_eq!(GetDirectMemoryFallbackReason::VersionId.as_str(), "version_id");
assert_eq!(GetDirectMemoryFallbackReason::Versioned.as_str(), "versioned");
assert_eq!(GetDirectMemoryFallbackReason::VersionSuspended.as_str(), "version_suspended");
assert_eq!(GetDirectMemoryFallbackReason::InclFreeVersions.as_str(), "incl_free_versions"); assert_eq!(GetDirectMemoryFallbackReason::InclFreeVersions.as_str(), "incl_free_versions");
assert_eq!(GetDirectMemoryFallbackReason::SkipFreeVersion.as_str(), "skip_free_version"); assert_eq!(GetDirectMemoryFallbackReason::SkipFreeVersion.as_str(), "skip_free_version");
assert_eq!(GetDirectMemoryFallbackReason::DataMovement.as_str(), "data_movement"); assert_eq!(GetDirectMemoryFallbackReason::DataMovement.as_str(), "data_movement");