blob: 2c18ede403fffeda0fb1b9ed7fb5bad9efc8d8fb [file]
use crate::compat::storage_conversion::persistency::partitions;
use crate::configs::system::SystemConfig;
use crate::streaming::partitions::partition::Partition;
use crate::streaming::topics::consumer_group::ConsumerGroup;
use crate::streaming::topics::topic::Topic;
use anyhow::Context;
use iggy::compression::compression_algorithm::CompressionAlgorithm;
use iggy::error::IggyError;
use iggy::locking::IggySharedMut;
use iggy::locking::IggySharedMutFn;
use iggy::utils::byte_size::IggyByteSize;
use iggy::utils::duration::{IggyDuration, SEC_IN_MICRO};
use iggy::utils::expiry::IggyExpiry;
use iggy::utils::timestamp::IggyTimestamp;
use serde::{Deserialize, Serialize};
use sled::Db;
use std::path::Path;
use tokio::fs;
use tokio::sync::RwLock;
use tracing::{error, info};
#[derive(Debug, Serialize, Deserialize)]
struct ConsumerGroupData {
id: u32,
name: String,
}
pub async fn load(config: &SystemConfig, db: &Db, topic: &mut Topic) -> Result<(), IggyError> {
info!("Loading topic {} from disk...", topic);
if !Path::new(&topic.path).exists() {
return Err(IggyError::TopicIdNotFound(topic.topic_id, topic.stream_id));
}
let key = get_topic_key(topic.stream_id, topic.topic_id);
let topic_data = match db
.get(&key)
.with_context(|| format!("Failed to load topic with key: {}", key))
{
Ok(data) => {
if let Some(topic_data) = data {
let topic_data = rmp_serde::from_slice::<TopicData>(&topic_data)
.with_context(|| format!("Failed to deserialize topic with key: {}", key));
if let Err(err) = topic_data {
return Err(IggyError::CannotDeserializeResource(err));
} else {
topic_data.unwrap()
}
} else {
return Err(IggyError::ResourceNotFound(key));
}
}
Err(err) => {
return Err(IggyError::CannotLoadResource(err));
}
};
topic.name = topic_data.name;
topic.created_at = topic_data.created_at;
topic.message_expiry = match topic_data.message_expiry {
Some(expiry) => {
IggyExpiry::ExpireDuration(IggyDuration::from(expiry as u64 * SEC_IN_MICRO))
}
None => IggyExpiry::NeverExpire,
};
topic.compression_algorithm = topic_data.compression_algorithm;
topic.max_topic_size = topic_data.max_topic_size.into();
topic.replication_factor = topic_data.replication_factor;
let dir_entries = fs::read_dir(&topic.partitions_path).await
.with_context(|| format!("Failed to read partition with ID: {} for stream with ID: {} for topic with ID: {} and path: {}",
topic.topic_id, topic.stream_id, topic.topic_id, &topic.partitions_path));
if let Err(err) = dir_entries {
return Err(IggyError::CannotReadPartitions(err));
}
let mut dir_entries = dir_entries.unwrap();
while let Some(dir_entry) = dir_entries.next_entry().await.unwrap_or(None) {
let metadata = dir_entry.metadata().await;
if metadata.is_err() || metadata.unwrap().is_file() {
continue;
}
let name = dir_entry.file_name().into_string().unwrap();
let partition_id = name.parse::<u32>();
if partition_id.is_err() {
error!("Invalid partition ID file with name: '{}'.", name);
continue;
}
let partition_id = partition_id.unwrap();
let mut partition = Partition::create(
topic.stream_id,
topic.topic_id,
partition_id,
false,
topic.config.clone(),
topic.storage.clone(),
topic.message_expiry,
topic.messages_count_of_parent_stream.clone(),
topic.messages_count.clone(),
topic.size_of_parent_stream.clone(),
topic.size_bytes.clone(),
topic.segments_count_of_parent_stream.clone(),
IggyTimestamp::zero(),
);
partitions::load(config, db, &mut partition).await?;
topic
.partitions
.insert(partition.partition_id, IggySharedMut::new(partition));
}
let consumer_groups = load_consumer_groups(db, topic).await?;
topic.consumer_groups = consumer_groups
.into_iter()
.map(|group| (group.group_id, RwLock::new(group)))
.collect();
info!("Loaded topic {topic}");
Ok(())
}
pub async fn load_consumer_groups(db: &Db, topic: &Topic) -> Result<Vec<ConsumerGroup>, IggyError> {
info!("Loading consumer groups for topic {} from disk...", topic);
let key_prefix = get_consumer_groups_key_prefix(topic.stream_id, topic.topic_id);
let mut consumer_groups = Vec::new();
for data in db.scan_prefix(format!("{}:", key_prefix)) {
let consumer_group = match data.with_context(|| {
format!(
"Failed to load consumer group when searching for key: {}",
key_prefix
)
}) {
Ok((_, value)) => {
match rmp_serde::from_slice::<ConsumerGroupData>(&value).with_context(|| {
format!(
"Failed to deserialize consumer group with key: {}",
key_prefix
)
}) {
Ok(user) => user,
Err(err) => {
return Err(IggyError::CannotDeserializeResource(err));
}
}
}
Err(err) => {
return Err(IggyError::CannotLoadResource(err));
}
};
let consumer_group = ConsumerGroup::new(
topic.topic_id,
consumer_group.id,
&consumer_group.name,
topic.get_partitions_count(),
);
consumer_groups.push(consumer_group);
}
info!(
"Loaded {} consumer groups for topic {}",
consumer_groups.len(),
topic
);
Ok(consumer_groups)
}
#[derive(Debug, Serialize, Deserialize)]
struct TopicData {
name: String,
created_at: IggyTimestamp,
message_expiry: Option<u32>,
compression_algorithm: CompressionAlgorithm,
max_topic_size: Option<IggyByteSize>,
replication_factor: u8,
}
fn get_topic_key(stream_id: u32, topic_id: u32) -> String {
format!("streams:{}:topics:{}", stream_id, topic_id)
}
fn get_consumer_groups_key_prefix(stream_id: u32, topic_id: u32) -> String {
format!("streams:{stream_id}:topics:{topic_id}:consumer_groups")
}