blob: 7e4c95104725e91fc855c207519c6565b59b44ac [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::stats::{StreamStats, TopicStats};
use crate::stm::StateHandler;
use crate::stm::snapshot::Snapshotable;
use crate::{collect_handlers, define_state, impl_fill_restore};
use ahash::AHashMap;
use iggy_common::create_partitions::CreatePartitions;
use iggy_common::create_stream::CreateStream;
use iggy_common::create_topic::CreateTopic;
use iggy_common::delete_partitions::DeletePartitions;
use iggy_common::delete_stream::DeleteStream;
use iggy_common::delete_topic::DeleteTopic;
use iggy_common::purge_stream::PurgeStream;
use iggy_common::purge_topic::PurgeTopic;
use iggy_common::update_stream::UpdateStream;
use iggy_common::update_topic::UpdateTopic;
use iggy_common::{CompressionAlgorithm, IggyExpiry, IggyTimestamp, MaxTopicSize};
use serde::{Deserialize, Serialize};
use slab::Slab;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
/// Partition snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PartitionSnapshot {
pub id: usize,
pub created_at: IggyTimestamp,
}
#[derive(Debug, Clone)]
pub struct Partition {
pub id: usize,
pub created_at: IggyTimestamp,
}
impl Partition {
pub fn new(id: usize, created_at: IggyTimestamp) -> Self {
Self { id, created_at }
}
}
/// Stats snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatsSnapshot {
pub size_bytes: u64,
pub messages_count: u64,
pub segments_count: u32,
}
/// Topic snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopicSnapshot {
pub id: usize,
pub name: String,
pub created_at: IggyTimestamp,
pub replication_factor: u8,
pub message_expiry: IggyExpiry,
pub compression_algorithm: CompressionAlgorithm,
pub max_topic_size: MaxTopicSize,
pub stats: StatsSnapshot,
pub partitions: Vec<PartitionSnapshot>,
pub round_robin_counter: usize,
}
#[derive(Debug, Clone)]
pub struct Topic {
pub id: usize,
pub name: Arc<str>,
pub created_at: IggyTimestamp,
pub replication_factor: u8,
pub message_expiry: IggyExpiry,
pub compression_algorithm: CompressionAlgorithm,
pub max_topic_size: MaxTopicSize,
pub stats: Arc<TopicStats>,
pub partitions: Vec<Partition>,
pub round_robin_counter: Arc<AtomicUsize>,
}
impl Default for Topic {
fn default() -> Self {
Self {
id: 0,
name: Arc::from(""),
created_at: IggyTimestamp::default(),
replication_factor: 1,
message_expiry: IggyExpiry::default(),
compression_algorithm: CompressionAlgorithm::default(),
max_topic_size: MaxTopicSize::default(),
stats: Arc::new(TopicStats::default()),
partitions: Vec::new(),
round_robin_counter: Arc::new(AtomicUsize::new(0)),
}
}
}
impl Topic {
pub fn new(
name: Arc<str>,
created_at: IggyTimestamp,
replication_factor: u8,
message_expiry: IggyExpiry,
compression_algorithm: CompressionAlgorithm,
max_topic_size: MaxTopicSize,
stream_stats: Arc<StreamStats>,
) -> Self {
Self {
id: 0,
name,
created_at,
replication_factor,
message_expiry,
compression_algorithm,
max_topic_size,
stats: Arc::new(TopicStats::new(stream_stats)),
partitions: Vec::new(),
round_robin_counter: Arc::new(AtomicUsize::new(0)),
}
}
}
/// Stream snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamSnapshot {
pub id: usize,
pub name: String,
pub created_at: IggyTimestamp,
pub stats: StatsSnapshot,
pub topics: Vec<(usize, TopicSnapshot)>,
}
#[derive(Debug)]
pub struct Stream {
pub id: usize,
pub name: Arc<str>,
pub created_at: IggyTimestamp,
pub stats: Arc<StreamStats>,
pub topics: Slab<Topic>,
pub topic_index: AHashMap<Arc<str>, usize>,
}
impl Default for Stream {
fn default() -> Self {
Self {
id: 0,
name: Arc::from(""),
created_at: IggyTimestamp::default(),
stats: Arc::new(StreamStats::default()),
topics: Slab::new(),
topic_index: AHashMap::default(),
}
}
}
impl Clone for Stream {
fn clone(&self) -> Self {
Self {
id: self.id,
name: self.name.clone(),
created_at: self.created_at,
stats: self.stats.clone(),
topics: self.topics.clone(),
topic_index: self.topic_index.clone(),
}
}
}
impl Stream {
pub fn new(name: Arc<str>, created_at: IggyTimestamp) -> Self {
Self {
id: 0,
name,
created_at,
stats: Arc::new(StreamStats::default()),
topics: Slab::new(),
topic_index: AHashMap::default(),
}
}
pub fn with_stats(name: Arc<str>, created_at: IggyTimestamp, stats: Arc<StreamStats>) -> Self {
Self {
id: 0,
name,
created_at,
stats,
topics: Slab::new(),
topic_index: AHashMap::default(),
}
}
}
define_state! {
Streams {
index: AHashMap<Arc<str>, usize>,
items: Slab<Stream>,
}
}
collect_handlers! {
Streams {
CreateStream,
UpdateStream,
DeleteStream,
PurgeStream,
CreateTopic,
UpdateTopic,
DeleteTopic,
PurgeTopic,
CreatePartitions,
DeletePartitions,
}
}
impl StreamsInner {
fn resolve_stream_id(&self, identifier: &iggy_common::Identifier) -> Option<usize> {
use iggy_common::IdKind;
match identifier.kind {
IdKind::Numeric => {
let id = identifier.get_u32_value().ok()? as usize;
if self.items.contains(id) {
Some(id)
} else {
None
}
}
IdKind::String => {
let name = identifier.get_string_value().ok()?;
self.index.get(name.as_str()).copied()
}
}
}
fn resolve_topic_id(
&self,
stream_id: usize,
identifier: &iggy_common::Identifier,
) -> Option<usize> {
use iggy_common::IdKind;
let stream = self.items.get(stream_id)?;
match identifier.kind {
IdKind::Numeric => {
let id = identifier.get_u32_value().ok()? as usize;
if stream.topics.contains(id) {
Some(id)
} else {
None
}
}
IdKind::String => {
let name = identifier.get_string_value().ok()?;
stream.topic_index.get(name.as_str()).copied()
}
}
}
}
impl StateHandler for CreateStream {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let name_arc: Arc<str> = Arc::from(self.name.as_str());
if state.index.contains_key(&name_arc) {
return;
}
let stream = Stream {
id: 0,
name: name_arc.clone(),
created_at: iggy_common::IggyTimestamp::now(),
stats: Arc::new(StreamStats::default()),
topics: Slab::new(),
topic_index: AHashMap::default(),
};
let id = state.items.insert(stream);
if let Some(stream) = state.items.get_mut(id) {
stream.id = id;
}
state.index.insert(name_arc, id);
}
}
impl StateHandler for UpdateStream {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
let new_name_arc: Arc<str> = Arc::from(self.name.as_str());
if let Some(&existing_id) = state.index.get(&new_name_arc)
&& existing_id != stream_id
{
return;
}
state.index.remove(&stream.name);
stream.name = new_name_arc.clone();
state.index.insert(new_name_arc, stream_id);
}
}
impl StateHandler for DeleteStream {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
if let Some(stream) = state.items.get(stream_id) {
let name = stream.name.clone();
state.items.remove(stream_id);
state.index.remove(&name);
}
}
}
impl StateHandler for PurgeStream {
type State = StreamsInner;
fn apply(&self, _state: &mut StreamsInner) {
// TODO
todo!();
}
}
impl StateHandler for CreateTopic {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
let name_arc: Arc<str> = Arc::from(self.name.as_str());
if stream.topic_index.contains_key(&name_arc) {
return;
}
let topic = Topic {
id: 0,
name: name_arc.clone(),
created_at: iggy_common::IggyTimestamp::now(),
replication_factor: self.replication_factor.unwrap_or(1),
message_expiry: self.message_expiry,
compression_algorithm: self.compression_algorithm,
max_topic_size: self.max_topic_size,
stats: Arc::new(TopicStats::new(stream.stats.clone())),
partitions: Vec::new(),
round_robin_counter: Arc::new(AtomicUsize::new(0)),
};
let topic_id = stream.topics.insert(topic);
if let Some(topic) = stream.topics.get_mut(topic_id) {
topic.id = topic_id;
for partition_id in 0..self.partitions_count as usize {
let partition = Partition {
id: partition_id,
created_at: iggy_common::IggyTimestamp::now(),
};
topic.partitions.push(partition);
}
}
stream.topic_index.insert(name_arc, topic_id);
}
}
impl StateHandler for UpdateTopic {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(topic_id) = state.resolve_topic_id(stream_id, &self.topic_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
let Some(topic) = stream.topics.get_mut(topic_id) else {
return;
};
let new_name_arc: Arc<str> = Arc::from(self.name.as_str());
if let Some(&existing_id) = stream.topic_index.get(&new_name_arc)
&& existing_id != topic_id
{
return;
}
stream.topic_index.remove(&topic.name);
topic.name = new_name_arc.clone();
topic.compression_algorithm = self.compression_algorithm;
topic.message_expiry = self.message_expiry;
topic.max_topic_size = self.max_topic_size;
if let Some(rf) = self.replication_factor {
topic.replication_factor = rf;
}
stream.topic_index.insert(new_name_arc, topic_id);
}
}
impl StateHandler for DeleteTopic {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(topic_id) = state.resolve_topic_id(stream_id, &self.topic_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
if let Some(topic) = stream.topics.get(topic_id) {
let name = topic.name.clone();
stream.topics.remove(topic_id);
stream.topic_index.remove(&name);
}
}
}
impl StateHandler for PurgeTopic {
type State = StreamsInner;
fn apply(&self, _state: &mut StreamsInner) {
// TODO
todo!();
}
}
impl StateHandler for CreatePartitions {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(topic_id) = state.resolve_topic_id(stream_id, &self.topic_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
let Some(topic) = stream.topics.get_mut(topic_id) else {
return;
};
let current_partition_count = topic.partitions.len();
for i in 0..self.partitions_count as usize {
let partition_id = current_partition_count + i;
let partition = Partition {
id: partition_id,
created_at: iggy_common::IggyTimestamp::now(),
};
topic.partitions.push(partition);
}
}
}
impl StateHandler for DeletePartitions {
type State = StreamsInner;
fn apply(&self, state: &mut StreamsInner) {
let Some(stream_id) = state.resolve_stream_id(&self.stream_id) else {
return;
};
let Some(topic_id) = state.resolve_topic_id(stream_id, &self.topic_id) else {
return;
};
let Some(stream) = state.items.get_mut(stream_id) else {
return;
};
let Some(topic) = stream.topics.get_mut(topic_id) else {
return;
};
let count_to_delete = self.partitions_count as usize;
if count_to_delete > 0 && count_to_delete <= topic.partitions.len() {
topic
.partitions
.truncate(topic.partitions.len() - count_to_delete);
}
}
}
/// Snapshot representation for the Streams state machine.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamsSnapshot {
pub items: Vec<(usize, StreamSnapshot)>,
}
impl Snapshotable for Streams {
type Snapshot = StreamsSnapshot;
fn to_snapshot(&self) -> Self::Snapshot {
self.inner.read(|inner| {
let items: Vec<(usize, StreamSnapshot)> = inner
.items
.iter()
.map(|(stream_id, stream)| {
let (size_bytes, messages_count, segments_count) =
stream.stats.load_for_snapshot();
let topics: Vec<(usize, TopicSnapshot)> = stream
.topics
.iter()
.map(|(topic_id, topic)| {
let (t_size, t_msgs, t_segs) = topic.stats.load_for_snapshot();
(
topic_id,
TopicSnapshot {
id: topic.id,
name: topic.name.to_string(),
created_at: topic.created_at,
replication_factor: topic.replication_factor,
message_expiry: topic.message_expiry,
compression_algorithm: topic.compression_algorithm,
max_topic_size: topic.max_topic_size,
stats: StatsSnapshot {
size_bytes: t_size,
messages_count: t_msgs,
segments_count: t_segs,
},
partitions: topic
.partitions
.iter()
.map(|p| PartitionSnapshot {
id: p.id,
created_at: p.created_at,
})
.collect(),
round_robin_counter: topic
.round_robin_counter
.load(Ordering::Relaxed),
},
)
})
.collect();
(
stream_id,
StreamSnapshot {
id: stream.id,
name: stream.name.to_string(),
created_at: stream.created_at,
stats: StatsSnapshot {
size_bytes,
messages_count,
segments_count,
},
topics,
},
)
})
.collect();
StreamsSnapshot { items }
})
}
fn from_snapshot(
snapshot: Self::Snapshot,
) -> Result<Self, crate::stm::snapshot::SnapshotError> {
let mut index: AHashMap<Arc<str>, usize> = AHashMap::new();
let mut stream_entries: Vec<(usize, Stream)> = Vec::new();
for (slab_key, stream_snap) in snapshot.items {
let stream_stats = Arc::new(StreamStats::default());
stream_stats.store_from_snapshot(
stream_snap.stats.size_bytes,
stream_snap.stats.messages_count,
stream_snap.stats.segments_count,
);
let mut topic_index: AHashMap<Arc<str>, usize> = AHashMap::new();
let mut topic_entries: Vec<(usize, Topic)> = Vec::new();
for (topic_slab_key, topic_snap) in stream_snap.topics {
let topic_stats = Arc::new(TopicStats::new(stream_stats.clone()));
topic_stats.store_from_snapshot(
topic_snap.stats.size_bytes,
topic_snap.stats.messages_count,
topic_snap.stats.segments_count,
);
let topic_name: Arc<str> = Arc::from(topic_snap.name.as_str());
let topic = Topic {
id: topic_snap.id,
name: topic_name.clone(),
created_at: topic_snap.created_at,
replication_factor: topic_snap.replication_factor,
message_expiry: topic_snap.message_expiry,
compression_algorithm: topic_snap.compression_algorithm,
max_topic_size: topic_snap.max_topic_size,
stats: topic_stats,
partitions: topic_snap
.partitions
.into_iter()
.map(|p| Partition {
id: p.id,
created_at: p.created_at,
})
.collect(),
round_robin_counter: Arc::new(AtomicUsize::new(topic_snap.round_robin_counter)),
};
topic_index.insert(topic_name, topic_slab_key);
topic_entries.push((topic_slab_key, topic));
}
let topics: Slab<Topic> = topic_entries.into_iter().collect();
let stream_name: Arc<str> = Arc::from(stream_snap.name.as_str());
let stream = Stream {
id: stream_snap.id,
name: stream_name.clone(),
created_at: stream_snap.created_at,
stats: stream_stats,
topics,
topic_index,
};
index.insert(stream_name, slab_key);
stream_entries.push((slab_key, stream));
}
let items: Slab<Stream> = stream_entries.into_iter().collect();
let inner = StreamsInner { index, items };
Ok(inner.into())
}
}
impl_fill_restore!(Streams, streams);