94 lines
2.7 KiB
Rust
94 lines
2.7 KiB
Rust
/// 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/");
|
|
}
|
|
}
|