blob: 186795430a8ffa1e3129ee87b1ddca0c596491d3 [file]
use crate::streaming::batching::batch_accumulator::BatchAccumulator;
use crate::streaming::batching::batch_filter::BatchItemizer;
use crate::streaming::batching::message_batch::{RetainedMessageBatch, RETAINED_BATCH_OVERHEAD};
use crate::streaming::models::messages::RetainedMessage;
use crate::streaming::segments::index::{Index, IndexRange};
use crate::streaming::segments::segment::Segment;
use crate::streaming::segments::time_index::TimeIndex;
use crate::streaming::sizeable::Sizeable;
use iggy::error::IggyError;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tracing::{info, trace, warn};
const EMPTY_MESSAGES: Vec<RetainedMessage> = vec![];
impl Segment {
pub fn get_messages_count(&self) -> u64 {
if self.size_bytes == 0 {
return 0;
}
self.current_offset - self.start_offset + 1
}
pub async fn get_messages(
&self,
mut offset: u64,
count: u32,
) -> Result<Vec<Arc<RetainedMessage>>, IggyError> {
if count == 0 {
return Ok(EMPTY_MESSAGES.into_iter().map(Arc::new).collect());
}
if offset < self.start_offset {
offset = self.start_offset;
}
let end_offset = offset + (count - 1) as u64;
// In case that the partition messages buffer is disabled, we need to check the unsaved messages buffer
if self.unsaved_messages.is_none() {
return self.load_messages_from_disk(offset, end_offset).await;
}
let batch_accumulator = self.unsaved_messages.as_ref().unwrap();
if batch_accumulator.is_empty() {
return self.load_messages_from_disk(offset, end_offset).await;
}
let first_offset = batch_accumulator.batch_base_offset();
if end_offset < first_offset {
return self.load_messages_from_disk(offset, end_offset).await;
}
let last_offset = batch_accumulator.batch_max_offset();
if offset >= first_offset && end_offset <= last_offset {
return Ok(self.load_messages_from_unsaved_buffer(offset, end_offset));
}
// Can this be somehow improved? maybe with chain iterators
let mut messages = self.load_messages_from_disk(offset, end_offset).await?;
let mut buffered_messages = self.load_messages_from_unsaved_buffer(offset, last_offset);
messages.append(&mut buffered_messages);
Ok(messages)
}
pub async fn get_all_messages(&self) -> Result<Vec<Arc<RetainedMessage>>, IggyError> {
self.get_messages(self.start_offset, self.get_messages_count() as u32)
.await
}
pub async fn get_all_batches(&self) -> Result<Vec<RetainedMessageBatch>, IggyError> {
self.storage
.segment
.load_message_batches(self, &IndexRange::max_range())
.await
}
pub async fn get_newest_batches_by_size(
&self,
size_bytes: u64,
) -> Result<Vec<RetainedMessageBatch>, IggyError> {
let messages = self
.storage
.segment
.load_newest_batches_by_size(self, size_bytes)
.await?;
Ok(messages)
}
fn load_messages_from_unsaved_buffer(
&self,
start_offset: u64,
end_offset: u64,
) -> Vec<Arc<RetainedMessage>> {
let batch_accumulator = self.unsaved_messages.as_ref().unwrap();
batch_accumulator.get_messages_by_offset(start_offset, end_offset)
}
async fn load_messages_from_disk(
&self,
start_offset: u64,
end_offset: u64,
) -> Result<Vec<Arc<RetainedMessage>>, IggyError> {
trace!(
"Loading messages from disk, segment start offset: {}, end offset: {}, current offset: {}...",
start_offset,
end_offset,
self.current_offset
);
if start_offset > end_offset {
warn!(
"Cannot load messages from disk, invalid offset range: {} - {}.",
start_offset, end_offset
);
return Ok(EMPTY_MESSAGES.into_iter().map(Arc::new).collect());
}
if let Some(indices) = &self.indexes {
let relative_start_offset = (start_offset - self.start_offset) as u32;
let relative_end_offset = (end_offset - self.start_offset) as u32;
let index_range = match self.load_highest_lower_bound_index(
indices,
relative_start_offset,
relative_end_offset,
) {
Ok(range) => range,
Err(_) => {
trace!(
"Cannot load messages from disk, index range not found: {} - {}.",
start_offset,
end_offset
);
return Ok(EMPTY_MESSAGES.into_iter().map(Arc::new).collect());
}
};
return self
.load_messages_from_segment_file(&index_range, start_offset, end_offset)
.await;
}
match self
.storage
.segment
.load_index_range(self, start_offset, end_offset)
.await?
{
Some(index_range) => {
self.load_messages_from_segment_file(&index_range, start_offset, end_offset)
.await
}
None => Ok(EMPTY_MESSAGES.into_iter().map(Arc::new).collect()),
}
}
async fn load_messages_from_segment_file(
&self,
index_range: &IndexRange,
start_offset: u64,
end_offset: u64,
) -> Result<Vec<Arc<RetainedMessage>>, IggyError> {
let messages_count = (start_offset + end_offset) as usize;
let messages = self
.storage
.segment
.load_message_batches(self, index_range)
.await?
.iter()
.to_messages_with_filter(messages_count, &|msg| {
msg.offset >= start_offset && msg.offset <= end_offset
});
trace!(
"Loaded {} messages from disk, segment start offset: {}, end offset: {}.",
messages.len(),
self.start_offset,
self.current_offset
);
Ok(messages.into_iter().map(Arc::new).collect())
}
pub async fn append_batch(
&mut self,
batch_size: u64,
messages_count: u32,
batch: &[Arc<RetainedMessage>],
) -> Result<(), IggyError> {
if self.is_closed {
return Err(IggyError::SegmentClosed(
self.start_offset,
self.partition_id,
));
}
let messages_cap = self.config.partition.messages_required_to_save as usize;
let batch_base_offset = batch.first().unwrap().offset;
let batch_accumulator = self
.unsaved_messages
.get_or_insert_with(|| BatchAccumulator::new(batch_base_offset, messages_cap));
batch_accumulator.append(batch_size, batch);
let curr_offset = batch_accumulator.batch_max_offset();
self.current_offset = curr_offset;
self.size_bytes += batch_size as u32;
self.size_of_parent_stream
.fetch_add(batch_size, Ordering::AcqRel);
self.size_of_parent_topic
.fetch_add(batch_size, Ordering::AcqRel);
self.size_of_parent_partition
.fetch_add(batch_size, Ordering::AcqRel);
self.messages_count_of_parent_stream
.fetch_add(messages_count as u64, Ordering::SeqCst);
self.messages_count_of_parent_topic
.fetch_add(messages_count as u64, Ordering::SeqCst);
self.messages_count_of_parent_partition
.fetch_add(messages_count as u64, Ordering::SeqCst);
Ok(())
}
fn store_offset_and_timestamp_index_for_batch(
&mut self,
batch_last_offset: u64,
batch_max_timestamp: u64,
) -> (Index, TimeIndex) {
let relative_offset = (batch_last_offset - self.start_offset) as u32;
trace!("Storing index for relative_offset: {relative_offset}");
let index = Index {
relative_offset,
position: self.last_index_position,
};
let time_index = TimeIndex {
relative_offset,
timestamp: batch_max_timestamp,
};
match (&mut self.indexes, &mut self.time_indexes) {
(Some(indexes), Some(time_indexes)) => {
indexes.push(index);
time_indexes.push(time_index);
}
(Some(indexes), None) => {
indexes.push(index);
}
(None, Some(time_indexes)) => {
time_indexes.push(time_index);
}
(None, None) => {}
};
(index, time_index)
}
pub async fn persist_messages(&mut self) -> Result<usize, IggyError> {
let storage = self.storage.segment.clone();
if self.unsaved_messages.is_none() {
return Ok(0);
}
let mut batch_accumulator = self.unsaved_messages.take().unwrap();
if batch_accumulator.is_empty() {
return Ok(0);
}
let batch_max_offset = batch_accumulator.batch_max_offset();
let batch_max_timestamp = batch_accumulator.batch_max_timestamp();
let (index, time_index) =
self.store_offset_and_timestamp_index_for_batch(batch_max_offset, batch_max_timestamp);
let unsaved_messages_number = batch_accumulator.unsaved_messages_count();
trace!(
"Saving {} messages on disk in segment with start offset: {} for partition with ID: {}...",
unsaved_messages_number,
self.start_offset,
self.partition_id
);
let (has_remainder, batch) = batch_accumulator.materialize_batch_and_maybe_update_state();
let batch_size = batch.get_size_bytes();
if has_remainder {
self.unsaved_messages = Some(batch_accumulator);
}
let saved_bytes = storage.save_batches(self, batch).await?;
storage.save_index(&self.index_path, index).await?;
storage
.save_time_index(&self.time_index_path, time_index)
.await?;
self.last_index_position += batch_size;
self.size_bytes += RETAINED_BATCH_OVERHEAD;
self.size_of_parent_stream
.fetch_add(RETAINED_BATCH_OVERHEAD as u64, Ordering::AcqRel);
self.size_of_parent_topic
.fetch_add(RETAINED_BATCH_OVERHEAD as u64, Ordering::AcqRel);
self.size_of_parent_partition
.fetch_add(RETAINED_BATCH_OVERHEAD as u64, Ordering::AcqRel);
trace!(
"Saved {} messages on disk in segment with start offset: {} for partition with ID: {}, total bytes written: {}.",
unsaved_messages_number,
self.start_offset,
self.partition_id,
saved_bytes
);
if self.is_full().await {
self.end_offset = self.current_offset;
self.is_closed = true;
self.unsaved_messages = None;
info!(
"Closed segment with start offset: {} for partition with ID: {}.",
self.start_offset, self.partition_id
);
}
Ok(unsaved_messages_number)
}
}