mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-12 08:06:54 +00:00
fix(object-data-cache): make the GET key write-unique and dedup the lookup (#4693)
fix(object-data-cache): make GET body cache key write-unique and dedup lookups Address four object-data-cache GET-path findings (backlog#1107 batch): ODC-06 (backlog#1111): the cache key was content-unique, not write-unique. Extend ObjectDataCacheKey with the resolved version's modification time (i128 unix nanoseconds, None -> 0), derived once in the shared planner so the ecstore hook and the usecase layer produce an identical key. An unversioned overwrite advances mod_time, so a stale node can no longer serve old bytes for up to the TTL under an MD5 collision; etag + size stay as belt-and-braces. ODC-16 (backlog#1121): every cacheable GET planned and looked up twice (once in the ecstore hook, once in the usecase layer), double-counting hits, hit_bytes and lookups. GetObjectReader now carries a GetObjectBodySource marker (Unprobed / HookMissed / HookServed); the hook stamps it, and build_get_object_body_with_cache serves a hook-served body directly and skips its lookup whenever the hook already probed. One hook-served GET now records exactly one lookup. ODC-19 (backlog#1124): ENABLE=true with no explicit mode defaulted to HitOnly, which never fills and keeps a permanent 0% hit rate. Default to FillBufferedOnly, log the resolved mode at startup, and warn when HitOnly is selected explicitly. ODC-24 (backlog#1129): max_entry_bytes above the in-memory GET fill limits was silently inert. Clamp the planner's size eligibility to min(max_entry_bytes, seek-support threshold, 64 MiB buffer cap) so ineligible sizes plan SkipTooLarge instead of being reported eligible, and warn at startup when the excess is inert. Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -275,10 +275,49 @@ impl PutObjReader {
|
||||
}
|
||||
}
|
||||
|
||||
/// Provenance of a [`GetObjectReader`] with respect to the app-layer object
|
||||
/// data cache hook, so the app layer can avoid repeating the cache lookup the
|
||||
/// ecstore GET probe already ran after fresh metadata resolution (backlog#1121
|
||||
/// / ODC-16). One hook-served GET must record exactly one lookup, not two.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum GetObjectBodySource {
|
||||
/// The cache hook did not probe this read: the hook is unregistered, or the
|
||||
/// read is ineligible under the fail-closed allow-list. The app layer runs
|
||||
/// its own cache lookup, as before.
|
||||
#[default]
|
||||
Unprobed,
|
||||
/// The hook probed after fresh metadata resolution and did not serve a body
|
||||
/// (a genuine miss, or a length-defensive rejection). Its miss is
|
||||
/// authoritative, so the app layer must skip the lookup and only build a
|
||||
/// plan to fill.
|
||||
HookMissed,
|
||||
/// `buffered_body` is exactly the body the hook served from the cache. The
|
||||
/// app layer serves it directly as the object-data-cache source, with no
|
||||
/// second lookup and no re-fill.
|
||||
HookServed,
|
||||
}
|
||||
|
||||
pub struct GetObjectReader {
|
||||
pub stream: Box<dyn AsyncRead + Unpin + Send + Sync>,
|
||||
pub object_info: ObjectInfo,
|
||||
pub buffered_body: Option<Bytes>,
|
||||
/// Cache-hook provenance; defaults to [`GetObjectBodySource::Unprobed`] for
|
||||
/// every reader that never passed through the app-layer cache probe.
|
||||
pub body_source: GetObjectBodySource,
|
||||
}
|
||||
|
||||
impl GetObjectReader {
|
||||
/// True when `buffered_body` is the body the cache hook served. The app
|
||||
/// layer serves it as the object-data-cache source without a second lookup.
|
||||
pub fn is_cache_hook_served(&self) -> bool {
|
||||
matches!(self.body_source, GetObjectBodySource::HookServed)
|
||||
}
|
||||
|
||||
/// True when the cache hook probed this read (whether it served a body or
|
||||
/// missed). The app layer must not repeat the lookup in either case.
|
||||
pub fn cache_hook_probed(&self) -> bool {
|
||||
!matches!(self.body_source, GetObjectBodySource::Unprobed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -532,6 +571,7 @@ impl ReadPlan {
|
||||
stream: reader,
|
||||
object_info: oi.clone(),
|
||||
buffered_body: None,
|
||||
body_source: GetObjectBodySource::Unprobed,
|
||||
},
|
||||
self.storage_offset,
|
||||
self.storage_length,
|
||||
@@ -586,6 +626,7 @@ impl ReadPlan {
|
||||
stream: final_reader,
|
||||
object_info,
|
||||
buffered_body: None,
|
||||
body_source: GetObjectBodySource::Unprobed,
|
||||
},
|
||||
self.storage_offset,
|
||||
self.storage_length,
|
||||
@@ -687,6 +728,7 @@ impl ReadPlan {
|
||||
stream: final_reader,
|
||||
object_info,
|
||||
buffered_body: None,
|
||||
body_source: GetObjectBodySource::Unprobed,
|
||||
},
|
||||
self.storage_offset,
|
||||
self.storage_length,
|
||||
|
||||
Reference in New Issue
Block a user