93
crates/sq-storage/src/object_store/layout.rs
Normal file
93
crates/sq-storage/src/object_store/layout.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
/// S3 key layout for shipped WAL segments.
|
||||
///
|
||||
/// Format: `{cluster_id}/{topic}/{partition}/{base_offset:020}-{end_offset:020}.sqseg`
|
||||
///
|
||||
/// The 020 zero-padding ensures lexicographic ordering matches offset ordering.
|
||||
pub fn segment_key(
|
||||
cluster_id: &str,
|
||||
topic: &str,
|
||||
partition: u32,
|
||||
base_offset: u64,
|
||||
end_offset: u64,
|
||||
) -> String {
|
||||
format!(
|
||||
"{}/{}/{}/{:020}-{:020}.sqseg",
|
||||
cluster_id, topic, partition, base_offset, end_offset
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a segment key back into its components.
|
||||
/// Returns (cluster_id, topic, partition, base_offset, end_offset).
|
||||
pub fn parse_segment_key(key: &str) -> Option<(String, String, u32, u64, u64)> {
|
||||
let parts: Vec<&str> = key.split('/').collect();
|
||||
if parts.len() != 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cluster_id = parts[0].to_string();
|
||||
let topic = parts[1].to_string();
|
||||
let partition: u32 = parts[2].parse().ok()?;
|
||||
|
||||
let filename = parts[3].strip_suffix(".sqseg")?;
|
||||
let offsets: Vec<&str> = filename.split('-').collect();
|
||||
if offsets.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let base_offset: u64 = offsets[0].parse().ok()?;
|
||||
let end_offset: u64 = offsets[1].parse().ok()?;
|
||||
|
||||
Some((cluster_id, topic, partition, base_offset, end_offset))
|
||||
}
|
||||
|
||||
/// S3 key prefix for listing segments of a topic-partition.
|
||||
pub fn topic_partition_prefix(cluster_id: &str, topic: &str, partition: u32) -> String {
|
||||
format!("{}/{}/{}/", cluster_id, topic, partition)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_segment_key_format() {
|
||||
let key = segment_key("cluster-1", "orders", 0, 0, 999);
|
||||
assert_eq!(
|
||||
key,
|
||||
"cluster-1/orders/0/00000000000000000000-00000000000000000999.sqseg"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_segment_key_lexicographic_order() {
|
||||
let k1 = segment_key("c", "t", 0, 0, 999);
|
||||
let k2 = segment_key("c", "t", 0, 1000, 1999);
|
||||
let k3 = segment_key("c", "t", 0, 2000, 2999);
|
||||
assert!(k1 < k2);
|
||||
assert!(k2 < k3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_segment_key() {
|
||||
let key = segment_key("cluster-1", "orders", 2, 1000, 1999);
|
||||
let parsed = parse_segment_key(&key).unwrap();
|
||||
assert_eq!(parsed.0, "cluster-1");
|
||||
assert_eq!(parsed.1, "orders");
|
||||
assert_eq!(parsed.2, 2);
|
||||
assert_eq!(parsed.3, 1000);
|
||||
assert_eq!(parsed.4, 1999);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_key() {
|
||||
assert!(parse_segment_key("invalid").is_none());
|
||||
assert!(parse_segment_key("a/b/c").is_none());
|
||||
assert!(parse_segment_key("a/b/c/d.txt").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topic_partition_prefix() {
|
||||
let prefix = topic_partition_prefix("cluster-1", "orders", 0);
|
||||
assert_eq!(prefix, "cluster-1/orders/0/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user