mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-08 21:25:59 +00:00
feat(fileinfo): use AHashMap for metadata fields
- Add ahash dependency to workspace, filemeta, and utils crates - Change FileInfo.metadata to AHashMap<String, String> - Change ObjectPartInfo.checksums to Option<AHashMap<String, String>> - Change MetaObjectV1.meta to AHashMap<String, String> - Change MetaObjectV1Part.checksums to Option<AHashMap<String, String>> - Change UniquePartChecksums to use AHashMap - Make metadata_compat functions generic over BuildHasher - Make get_internal_replication_state generic over BuildHasher This optimization replaces the standard library's SipHash with ahash, which provides 2-3x faster hashing for typical key types. Refs: https://github.com/rustfs/backlog/issues/2005 Co-Authored-By: heihutu <heihutu@gmail.com>
This commit is contained in:
Generated
+3
@@ -91,6 +91,7 @@ dependencies = [
|
|||||||
"const-random",
|
"const-random",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"serde",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
@@ -9716,6 +9717,7 @@ dependencies = [
|
|||||||
name = "rustfs-filemeta"
|
name = "rustfs-filemeta"
|
||||||
version = "1.0.0-rc.3"
|
version = "1.0.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -10751,6 +10753,7 @@ dependencies = [
|
|||||||
name = "rustfs-utils"
|
name = "rustfs-utils"
|
||||||
version = "1.0.0-rc.3"
|
version = "1.0.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"base64-simd",
|
"base64-simd",
|
||||||
"blake2",
|
"blake2",
|
||||||
"brotli",
|
"brotli",
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ hotpath = { version = "0.24.0", default-features = false }
|
|||||||
# Snapshot testing for output format regression detection
|
# Snapshot testing for output format regression detection
|
||||||
insta = { version = "1.48" }
|
insta = { version = "1.48" }
|
||||||
|
|
||||||
|
# High-performance hashing
|
||||||
|
ahash = { version = "0.8", default-features = false, features = ["std", "runtime-rng", "serde"] }
|
||||||
|
|
||||||
[workspace.metadata.cargo-shear]
|
[workspace.metadata.cargo-shear]
|
||||||
ignored = ["hotpath", "rustfs"]
|
ignored = ["hotpath", "rustfs"]
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ s3s = { workspace = true, features = ["minio"] }
|
|||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
arc-swap.workspace = true
|
arc-swap.workspace = true
|
||||||
|
|
||||||
|
# High-performance hashing
|
||||||
|
ahash = { workspace = true, features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true, features = ["html_reports"] }
|
criterion = { workspace = true, features = ["html_reports"] }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use rustfs_utils::http::{
|
|||||||
contains_key_str, get_consistent_str, get_str, has_internal_suffix, insert_str, is_encryption_metadata_key,
|
contains_key_str, get_consistent_str, get_str, has_internal_suffix, insert_str, is_encryption_metadata_key,
|
||||||
starts_with_ignore_ascii_case,
|
starts_with_ignore_ascii_case,
|
||||||
};
|
};
|
||||||
|
use ahash::AHashMap;
|
||||||
use s3s::dto::{RestoreStatus, Timestamp};
|
use s3s::dto::{RestoreStatus, Timestamp};
|
||||||
use s3s::header::X_AMZ_RESTORE;
|
use s3s::header::X_AMZ_RESTORE;
|
||||||
use serde::de::{self, MapAccess, SeqAccess, Visitor, value::MapAccessDeserializer};
|
use serde::de::{self, MapAccess, SeqAccess, Visitor, value::MapAccessDeserializer};
|
||||||
@@ -67,7 +68,7 @@ pub struct ObjectPartInfo {
|
|||||||
// Index holds the index of the part in the erasure coding
|
// Index holds the index of the part in the erasure coding
|
||||||
pub index: Option<Bytes>,
|
pub index: Option<Bytes>,
|
||||||
// Checksums holds checksums of the part
|
// Checksums holds checksums of the part
|
||||||
pub checksums: Option<HashMap<String, String>>,
|
pub checksums: Option<AHashMap<String, String>>,
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +269,7 @@ pub struct FileInfo {
|
|||||||
pub mode: Option<u32>,
|
pub mode: Option<u32>,
|
||||||
// WrittenByVersion is the unix time stamp of the version that created this version of the object
|
// WrittenByVersion is the unix time stamp of the version that created this version of the object
|
||||||
pub written_by_version: Option<u64>,
|
pub written_by_version: Option<u64>,
|
||||||
pub metadata: HashMap<String, String>,
|
pub metadata: AHashMap<String, String>,
|
||||||
pub parts: Vec<ObjectPartInfo>,
|
pub parts: Vec<ObjectPartInfo>,
|
||||||
pub erasure: ErasureInfo,
|
pub erasure: ErasureInfo,
|
||||||
// MarkDeleted marks this version as deleted
|
// MarkDeleted marks this version as deleted
|
||||||
@@ -301,7 +302,7 @@ fn is_sensitive_metadata_key(key: &str) -> bool {
|
|||||||
.any(|prefix| starts_with_ignore_ascii_case(key, prefix))
|
.any(|prefix| starts_with_ignore_ascii_case(key, prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RedactedMetadata<'a>(&'a HashMap<String, String>);
|
struct RedactedMetadata<'a>(&'a AHashMap<String, String>);
|
||||||
|
|
||||||
impl std::fmt::Debug for RedactedMetadata<'_> {
|
impl std::fmt::Debug for RedactedMetadata<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@@ -425,7 +426,7 @@ struct FileInfoMapDef {
|
|||||||
size: i64,
|
size: i64,
|
||||||
mode: Option<u32>,
|
mode: Option<u32>,
|
||||||
written_by_version: Option<u64>,
|
written_by_version: Option<u64>,
|
||||||
metadata: HashMap<String, String>,
|
metadata: AHashMap<String, String>,
|
||||||
parts: Vec<ObjectPartInfo>,
|
parts: Vec<ObjectPartInfo>,
|
||||||
erasure: ErasureInfo,
|
erasure: ErasureInfo,
|
||||||
mark_deleted: bool,
|
mark_deleted: bool,
|
||||||
@@ -1079,7 +1080,7 @@ impl FileInfo {
|
|||||||
mod_time: Option<OffsetDateTime>,
|
mod_time: Option<OffsetDateTime>,
|
||||||
actual_size: i64,
|
actual_size: i64,
|
||||||
index: Option<Bytes>,
|
index: Option<Bytes>,
|
||||||
checksums: Option<HashMap<String, String>>,
|
checksums: Option<AHashMap<String, String>>,
|
||||||
) {
|
) {
|
||||||
let part = ObjectPartInfo {
|
let part = ObjectPartInfo {
|
||||||
etag,
|
etag,
|
||||||
@@ -1457,7 +1458,7 @@ pub fn parse_restore_obj_status(restore_hdr: &str) -> Result<RestoreStatus> {
|
|||||||
Err(Error::other(ERR_RESTORE_HDR_MALFORMED))
|
Err(Error::other(ERR_RESTORE_HDR_MALFORMED))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_restored_object_on_disk(meta: &HashMap<String, String>) -> bool {
|
pub fn is_restored_object_on_disk<S: std::hash::BuildHasher>(meta: &HashMap<String, String, S>) -> bool {
|
||||||
if let Some(restore_hdr) = meta.get(X_AMZ_RESTORE.as_str())
|
if let Some(restore_hdr) = meta.get(X_AMZ_RESTORE.as_str())
|
||||||
&& let Ok(restore_status) = parse_restore_obj_status(restore_hdr)
|
&& let Ok(restore_status) = parse_restore_obj_status(restore_hdr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -174,10 +174,10 @@ fn valid_target_delete_marker_version(arn: &str, version_id: &str) -> bool {
|
|||||||
/// included in the quorum hash, so such a divergence does surface — but as a
|
/// included in the quorum hash, so such a divergence does surface — but as a
|
||||||
/// quorum failure on an otherwise healthy object, which is not a state worth
|
/// quorum failure on an otherwise healthy object, which is not a state worth
|
||||||
/// reaching. Merge the RPC metadata carrier instead, and only ever insert.
|
/// reaching. Merge the RPC metadata carrier instead, and only ever insert.
|
||||||
fn persist_target_delete_marker_versions(
|
fn persist_target_delete_marker_versions<S: std::hash::BuildHasher>(
|
||||||
meta_sys: &mut HashMap<String, Vec<u8>>,
|
meta_sys: &mut HashMap<String, Vec<u8>>,
|
||||||
versions: &HashMap<String, String>,
|
versions: &HashMap<String, String>,
|
||||||
transport_metadata: &HashMap<String, String>,
|
transport_metadata: &HashMap<String, String, S>,
|
||||||
) {
|
) {
|
||||||
let mut bounded = BTreeMap::new();
|
let mut bounded = BTreeMap::new();
|
||||||
// A corrupt carrier means the dual internal prefixes disagreed. Do not merge
|
// A corrupt carrier means the dual internal prefixes disagreed. Do not merge
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use super::msgp_decode::{
|
|||||||
PrependByteReader, prealloc_hint, read_exact_vec, read_nil_or_array_len, read_nil_or_map_len, skip_msgp_value,
|
PrependByteReader, prealloc_hint, read_exact_vec, read_nil_or_array_len, read_nil_or_map_len, skip_msgp_value,
|
||||||
};
|
};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ChecksumInfo, TransitionVersionState};
|
use crate::{AHashMap, ChecksumInfo, TransitionVersionState};
|
||||||
use rustfs_utils::HashAlgorithm;
|
use rustfs_utils::HashAlgorithm;
|
||||||
use rustfs_utils::http::{
|
use rustfs_utils::http::{
|
||||||
RUSTFS_INTERNAL_PREFIX, SUFFIX_CRC, SUFFIX_FREE_VERSION, SUFFIX_INLINE_DATA, SUFFIX_PART_CHECKSUMS, SUFFIX_PURGESTATUS,
|
RUSTFS_INTERNAL_PREFIX, SUFFIX_CRC, SUFFIX_FREE_VERSION, SUFFIX_INLINE_DATA, SUFFIX_PART_CHECKSUMS, SUFFIX_PURGESTATUS,
|
||||||
@@ -377,7 +377,7 @@ impl<'a> DerivedInternalMetadata<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UniquePartChecksums(HashMap<String, String>);
|
struct UniquePartChecksums(AHashMap<String, String>);
|
||||||
|
|
||||||
impl<'de> serde::Deserialize<'de> for UniquePartChecksums {
|
impl<'de> serde::Deserialize<'de> for UniquePartChecksums {
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
@@ -397,7 +397,7 @@ impl<'de> serde::Deserialize<'de> for UniquePartChecksums {
|
|||||||
where
|
where
|
||||||
A: serde::de::SeqAccess<'de>,
|
A: serde::de::SeqAccess<'de>,
|
||||||
{
|
{
|
||||||
let mut checksums = HashMap::with_capacity(seq.size_hint().unwrap_or_default());
|
let mut checksums = AHashMap::with_capacity(seq.size_hint().unwrap_or_default());
|
||||||
while let Some((key, value)) = seq.next_element::<(String, String)>()? {
|
while let Some((key, value)) = seq.next_element::<(String, String)>()? {
|
||||||
if checksums.insert(key, value).is_some() {
|
if checksums.insert(key, value).is_some() {
|
||||||
return Err(serde::de::Error::custom("duplicate part checksum name"));
|
return Err(serde::de::Error::custom("duplicate part checksum name"));
|
||||||
@@ -1477,7 +1477,7 @@ pub struct MetaObjectV1 {
|
|||||||
#[serde(rename = "Erasure")]
|
#[serde(rename = "Erasure")]
|
||||||
pub erasure: MetaObjectV1Erasure,
|
pub erasure: MetaObjectV1Erasure,
|
||||||
#[serde(rename = "Meta")]
|
#[serde(rename = "Meta")]
|
||||||
pub meta: HashMap<String, String>,
|
pub meta: AHashMap<String, String>,
|
||||||
#[serde(rename = "Parts")]
|
#[serde(rename = "Parts")]
|
||||||
pub parts: Vec<MetaObjectV1Part>,
|
pub parts: Vec<MetaObjectV1Part>,
|
||||||
#[serde(rename = "VersionID")]
|
#[serde(rename = "VersionID")]
|
||||||
@@ -1543,7 +1543,7 @@ pub struct MetaObjectV1Part {
|
|||||||
#[serde(rename = "i")]
|
#[serde(rename = "i")]
|
||||||
pub index: Option<Bytes>,
|
pub index: Option<Bytes>,
|
||||||
#[serde(rename = "crc")]
|
#[serde(rename = "crc")]
|
||||||
pub checksums: Option<HashMap<String, String>>,
|
pub checksums: Option<AHashMap<String, String>>,
|
||||||
#[serde(rename = "err")]
|
#[serde(rename = "err")]
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -1887,7 +1887,7 @@ impl MetaObjectV1Part {
|
|||||||
"i" => self.index = Some(Bytes::from(read_msgp_bin(rd)?)),
|
"i" => self.index = Some(Bytes::from(read_msgp_bin(rd)?)),
|
||||||
"crc" => {
|
"crc" => {
|
||||||
let len = rmp::decode::read_map_len(rd)? as usize;
|
let len = rmp::decode::read_map_len(rd)? as usize;
|
||||||
let mut checksums = HashMap::with_capacity(prealloc_hint(len));
|
let mut checksums = AHashMap::with_capacity(prealloc_hint(len));
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
checksums.insert(read_msgp_string(rd)?, read_msgp_string(rd)?);
|
checksums.insert(read_msgp_string(rd)?, read_msgp_string(rd)?);
|
||||||
}
|
}
|
||||||
@@ -2570,7 +2570,7 @@ impl MetaObject {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut metadata = HashMap::with_capacity(self.meta_user.len() + self.meta_sys.len());
|
let mut metadata = AHashMap::with_capacity(self.meta_user.len() + self.meta_sys.len());
|
||||||
for (k, v) in &self.meta_user {
|
for (k, v) in &self.meta_user {
|
||||||
if k == AMZ_META_UNENCRYPTED_CONTENT_LENGTH || k == AMZ_META_UNENCRYPTED_CONTENT_MD5 {
|
if k == AMZ_META_UNENCRYPTED_CONTENT_LENGTH || k == AMZ_META_UNENCRYPTED_CONTENT_MD5 {
|
||||||
continue;
|
continue;
|
||||||
@@ -2861,7 +2861,7 @@ impl From<FileInfo> for MetaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_internal_replication_state(metadata: &HashMap<String, String>) -> Option<ReplicationState> {
|
fn get_internal_replication_state<S: std::hash::BuildHasher>(metadata: &HashMap<String, String, S>) -> Option<ReplicationState> {
|
||||||
let mut rs = ReplicationState::default();
|
let mut rs = ReplicationState::default();
|
||||||
let mut has = false;
|
let mut has = false;
|
||||||
|
|
||||||
@@ -2942,7 +2942,7 @@ impl MetaDeleteMarker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_fileinfo(&self, volume: &str, path: &str, _all_parts: bool) -> Result<FileInfo> {
|
pub fn into_fileinfo(&self, volume: &str, path: &str, _all_parts: bool) -> Result<FileInfo> {
|
||||||
let metadata = self
|
let metadata: AHashMap<String, String> = self
|
||||||
.meta_sys
|
.meta_sys
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ mod replication;
|
|||||||
|
|
||||||
pub mod test_data;
|
pub mod test_data;
|
||||||
|
|
||||||
|
/// High-performance HashMap type alias using ahash instead of SipHash.
|
||||||
|
pub type AHashMap<K, V> = ahash::AHashMap<K, V>;
|
||||||
|
|
||||||
|
/// High-performance HashSet type alias using ahash.
|
||||||
|
pub type AHashSet<K> = ahash::AHashSet<K>;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use fileinfo::*;
|
pub use fileinfo::*;
|
||||||
pub use filemeta::*;
|
pub use filemeta::*;
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ transform-stream = { workspace = true, optional = true }
|
|||||||
url = { workspace = true, optional = true }
|
url = { workspace = true, optional = true }
|
||||||
zstd = { workspace = true, optional = true }
|
zstd = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
# High-performance hashing
|
||||||
|
ahash = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true, features = ["html_reports"] }
|
criterion = { workspace = true, features = ["html_reports"] }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@@ -182,13 +182,13 @@ pub fn internal_key_rustfs(suffix: &str) -> String {
|
|||||||
|
|
||||||
// === String type (FileInfo.metadata, user_defined) ===
|
// === String type (FileInfo.metadata, user_defined) ===
|
||||||
|
|
||||||
pub fn insert_str(map: &mut HashMap<String, String>, suffix: &str, value: String) {
|
pub fn insert_str<S: std::hash::BuildHasher>(map: &mut HashMap<String, String, S>, suffix: &str, value: String) {
|
||||||
let (k1, k2) = both_keys(suffix);
|
let (k1, k2) = both_keys(suffix);
|
||||||
map.insert(k1, value.clone());
|
map.insert(k1, value.clone());
|
||||||
map.insert(k2, value);
|
map.insert(k2, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_str(map: &HashMap<String, String>, suffix: &str) -> Option<String> {
|
pub fn get_str<S: std::hash::BuildHasher>(map: &HashMap<String, String, S>, suffix: &str) -> Option<String> {
|
||||||
if let Some(v) = with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.get(k1).cloned()) {
|
if let Some(v) = with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.get(k1).cloned()) {
|
||||||
return Some(v);
|
return Some(v);
|
||||||
}
|
}
|
||||||
@@ -202,7 +202,7 @@ pub fn get_str(map: &HashMap<String, String>, suffix: &str) -> Option<String> {
|
|||||||
.map(|(_, value)| value.clone())
|
.map(|(_, value)| value.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_consistent_value<'a, V: AsRef<[u8]>>(map: &'a HashMap<String, V>, suffix: &str) -> Option<&'a V> {
|
fn get_consistent_value<'a, V: AsRef<[u8]>, S: std::hash::BuildHasher>(map: &'a HashMap<String, V, S>, suffix: &str) -> Option<&'a V> {
|
||||||
let (rustfs_key, minio_key) = both_keys(suffix);
|
let (rustfs_key, minio_key) = both_keys(suffix);
|
||||||
let mut value = None;
|
let mut value = None;
|
||||||
for (key, candidate) in map {
|
for (key, candidate) in map {
|
||||||
@@ -220,11 +220,11 @@ fn get_consistent_value<'a, V: AsRef<[u8]>>(map: &'a HashMap<String, V>, suffix:
|
|||||||
/// Returns a non-empty value when every compatibility key present for `suffix` agrees.
|
/// Returns a non-empty value when every compatibility key present for `suffix` agrees.
|
||||||
/// A single RustFS or MinIO key is accepted for backward compatibility; conflicting or empty
|
/// A single RustFS or MinIO key is accepted for backward compatibility; conflicting or empty
|
||||||
/// values return `None` so callers at destructive boundaries can fail closed.
|
/// values return `None` so callers at destructive boundaries can fail closed.
|
||||||
pub fn get_consistent_str<'a>(map: &'a HashMap<String, String>, suffix: &str) -> Option<&'a str> {
|
pub fn get_consistent_str<'a, S: std::hash::BuildHasher>(map: &'a HashMap<String, String, S>, suffix: &str) -> Option<&'a str> {
|
||||||
get_consistent_value(map, suffix).map(String::as_str)
|
get_consistent_value(map, suffix).map(String::as_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_key_str(map: &HashMap<String, String>, suffix: &str) -> bool {
|
pub fn contains_key_str<S: std::hash::BuildHasher>(map: &HashMap<String, String, S>, suffix: &str) -> bool {
|
||||||
if with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.contains_key(k1)) {
|
if with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.contains_key(k1)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ pub fn contains_key_str(map: &HashMap<String, String>, suffix: &str) -> bool {
|
|||||||
.any(|key| key.eq_ignore_ascii_case(&k1) || key.eq_ignore_ascii_case(&k2))
|
.any(|key| key.eq_ignore_ascii_case(&k1) || key.eq_ignore_ascii_case(&k2))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_str(map: &mut HashMap<String, String>, suffix: &str) {
|
pub fn remove_str<S: std::hash::BuildHasher>(map: &mut HashMap<String, String, S>, suffix: &str) {
|
||||||
with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.remove(k1));
|
with_internal_key(RUSTFS_INTERNAL_PREFIX, suffix, |k1| map.remove(k1));
|
||||||
with_internal_key(MINIO_INTERNAL_PREFIX, suffix, |k2| map.remove(k2));
|
with_internal_key(MINIO_INTERNAL_PREFIX, suffix, |k2| map.remove(k2));
|
||||||
let (k1, k2) = both_keys(suffix);
|
let (k1, k2) = both_keys(suffix);
|
||||||
@@ -285,7 +285,7 @@ pub fn strip_internal_prefix_preserving_case(key: &str) -> Option<&str> {
|
|||||||
|
|
||||||
/// Reads the bounded per-target delete-marker version map in one metadata scan.
|
/// Reads the bounded per-target delete-marker version map in one metadata scan.
|
||||||
/// The boolean is set when matching metadata is malformed or compatibility keys disagree.
|
/// The boolean is set when matching metadata is malformed or compatibility keys disagree.
|
||||||
pub fn target_delete_marker_versions(map: &HashMap<String, String>) -> (HashMap<String, String>, bool) {
|
pub fn target_delete_marker_versions<S: std::hash::BuildHasher>(map: &HashMap<String, String, S>) -> (HashMap<String, String>, bool) {
|
||||||
const MAX_ENTRIES: usize = 1_000;
|
const MAX_ENTRIES: usize = 1_000;
|
||||||
const MAX_ARN_LEN: usize = 1_024;
|
const MAX_ARN_LEN: usize = 1_024;
|
||||||
const MAX_VERSION_ID_LEN: usize = 1_024;
|
const MAX_VERSION_ID_LEN: usize = 1_024;
|
||||||
|
|||||||
Reference in New Issue
Block a user