blob: b23b83231943ccb66b6dc30970ca97f7e7f3cff6 [file]
use std::sync::atomic::Ordering;
use super::IggyMessagesBatchSet;
use crate::streaming::segments::segment::Segment;
use error_set::ErrContext;
use iggy::models::messaging::IggyIndexes;
use iggy::prelude::*;
use tracing::trace;
const COMPONENT: &str = "STREAMING_SEGMENT";
// TODO: test when offset > u32
impl Segment {
pub fn get_messages_size(&self) -> IggyByteSize {
IggyByteSize::from(self.messages_size.load(Ordering::Relaxed))
}
pub fn get_messages_count(&self) -> u32 {
if self.get_messages_size() == 0 {
return 0;
}
(self.end_offset - self.start_offset + 1) as u32
}
pub async fn get_messages_by_timestamp(
&self,
timestamp: u64,
count: u32,
) -> Result<IggyMessagesBatchSet, IggyError> {
if count == 0 {
return Ok(IggyMessagesBatchSet::default());
}
trace!(
"Getting {count} messages by timestamp {timestamp}, current_offset: {}...",
self.end_offset
);
// Case 0: Accumulator is empty, so all messages have to be on disk
if self.accumulator.is_empty() {
return self
.load_messages_from_disk_by_timestamp(timestamp, count)
.await;
}
let accumulator_first_timestamp = self.accumulator.first_timestamp();
let accumulator_last_timestamp = self.accumulator.last_timestamp();
// Case 1: Requested timestamp is higher than any available timestamp
if timestamp > accumulator_last_timestamp {
return Ok(IggyMessagesBatchSet::empty());
}
// Case 2: Requested timestamp falls within accumulator range only
if timestamp >= accumulator_first_timestamp {
// Get all messages from accumulator with timestamp >= the requested timestamp
return Ok(self.accumulator.get_messages_by_timestamp(timestamp, count));
}
// Case 3: Timestamp is lower than accumulator's first timestamp
// Need to get messages from disk and potentially combine with accumulator
let messages_from_disk = self
.load_messages_from_disk_by_timestamp(timestamp, count)
.await
.with_error_context(|error| {
format!(
"{COMPONENT} (error: {error}) - failed to load messages from disk by timestamp, stream ID: {}, topic ID: {}, partition ID: {}, timestamp: {timestamp}",
self.stream_id, self.topic_id, self.partition_id
)
})?;
// If we got enough messages from disk or there are no messages from disk,
// we don't need to consider messages from the accumulator
if messages_from_disk.count() >= count {
return Ok(messages_from_disk);
}
// If we need more messages, get them from accumulator, respecting the original timestamp
// This ensures we don't miss messages with the same or very close timestamps
let remaining_count = count - messages_from_disk.count();
let accumulator_messages = self
.accumulator
.get_messages_by_timestamp(timestamp, remaining_count);
// Combine the messages
let mut out = messages_from_disk;
out.add_batch_set(accumulator_messages);
Ok(out)
}
pub async fn get_messages_by_offset(
&self,
mut offset: u64,
count: u32,
) -> Result<IggyMessagesBatchSet, IggyError> {
if count == 0 {
return Ok(IggyMessagesBatchSet::default());
}
if offset < self.start_offset {
offset = self.start_offset;
}
let mut end_offset = offset + (count - 1) as u64;
if end_offset > self.end_offset {
end_offset = self.end_offset;
}
trace!(
"Getting messages by offset: {}, count: {}, segment start_offset: {}, segment end_offset: {}",
offset,
count,
self.start_offset,
self.end_offset
);
// Case 0: Accumulator is empty, so all messages have to be on disk
if self.accumulator.is_empty() {
return self.load_messages_from_disk_by_offset(offset, count).await;
}
let accumulator_first_msg_offset = self.accumulator.first_offset();
let accumulator_last_msg_offset = self.accumulator.last_offset();
// Case 1: All messages are in accumulator buffer
if offset >= accumulator_first_msg_offset && end_offset <= accumulator_last_msg_offset {
return Ok(self.accumulator.get_messages_by_offset(offset, count));
}
// Case 2: All messages are on disk
if end_offset < accumulator_first_msg_offset {
return self.load_messages_from_disk_by_offset(offset, count).await;
}
// Case 3: Messages span disk and accumulator buffer boundary
// Calculate how many messages we need from disk
let disk_count = if offset < accumulator_first_msg_offset {
((accumulator_first_msg_offset - offset) as u32).min(count)
} else {
0
};
let mut combined_batch_set = IggyMessagesBatchSet::empty();
// Load messages from disk if needed
if disk_count > 0 {
let disk_messages = self
.load_messages_from_disk_by_offset(offset, disk_count)
.await
.with_error_context(|error| {
format!(
"STREAMING_SEGMENT (error: {error}) - failed to load messages from disk, stream ID: {}, topic ID: {}, partition ID: {}, start offset: {offset}, count: {disk_count}",
self.stream_id, self.topic_id, self.partition_id
)
})?;
if !disk_messages.is_empty() {
combined_batch_set.add_batch_set(disk_messages);
}
}
// Calculate how many more messages we need from the accumulator
let remaining_count = count - combined_batch_set.count();
if remaining_count > 0 {
let accumulator_start_offset = std::cmp::max(offset, accumulator_first_msg_offset);
let accumulator_messages = self
.accumulator
.get_messages_by_offset(accumulator_start_offset, remaining_count);
if !accumulator_messages.is_empty() {
combined_batch_set.add_batch_set(accumulator_messages);
}
}
Ok(combined_batch_set)
}
/// Loads and returns all message IDs from the log file.
pub async fn load_message_ids(&self) -> Result<Vec<u128>, IggyError> {
let messages_count = self.get_messages_count();
trace!(
"Loading message IDs for {messages_count} messages from log file: {}",
self.messages_path
);
let indexes = self.load_indexes_by_offset(0, messages_count).await?;
if indexes.is_none() {
return Ok(vec![]);
}
let indexes = indexes.unwrap();
let ids = self
.messages_reader
.as_ref()
.unwrap()
.load_all_message_ids_from_disk(indexes, messages_count)
.await
.with_error_context(|error| {
format!("Failed to load message IDs, error: {error} for {self}")
})?;
trace!("Loaded {} message IDs from log file.", ids.len());
Ok(ids)
}
async fn load_indexes_by_offset(
&self,
relative_start_offset: u32,
count: u32,
) -> Result<Option<IggyIndexes>, IggyError> {
let indexes = if self.config.segment.cache_indexes {
self.indexes.slice_by_offset(relative_start_offset, count)
} else {
self.index_reader
.as_ref()
.expect("Index reader not initialized")
.load_from_disk_by_offset(relative_start_offset, count)
.await?
};
Ok(indexes)
}
async fn load_indexes_by_timestamp(
&self,
timestamp: u64,
count: u32,
) -> Result<Option<IggyIndexes>, IggyError> {
let indexes = if self.config.segment.cache_indexes {
self.indexes.slice_by_timestamp(timestamp, count)
} else {
self.index_reader
.as_ref()
.unwrap()
.load_from_disk_by_timestamp(timestamp, count)
.await?
};
Ok(indexes)
}
async fn load_messages_from_disk_by_offset(
&self,
start_offset: u64,
count: u32,
) -> Result<IggyMessagesBatchSet, IggyError> {
tracing::trace!(
"Loading {count} messages from disk, start_offset: {start_offset}, end_offset: {}...",
self.end_offset
);
let relative_start_offset = (start_offset - self.start_offset) as u32;
let indexes_to_read = self
.load_indexes_by_offset(relative_start_offset, count)
.await?;
if indexes_to_read.is_none() {
return Ok(IggyMessagesBatchSet::empty());
}
let indexes_to_read = indexes_to_read.unwrap();
let msgs = self
.messages_reader
.as_ref()
.expect("Messages reader not initialized")
.load_messages_from_disk(indexes_to_read)
.await
.with_error_context(|error| {
format!("Failed to load messages from segment file: {self}. {error}")
})?;
tracing::trace!(
"Loaded {} messages ({} bytes) from disk (requested {count} messages), start_offset: {start_offset}, end_offset: {}",
msgs.count(),
msgs.size(),
self.end_offset
);
Ok(msgs)
}
async fn load_messages_from_disk_by_timestamp(
&self,
timestamp: u64,
count: u32,
) -> Result<IggyMessagesBatchSet, IggyError> {
tracing::trace!(
"Loading {count} messages from disk, timestamp: {timestamp}, current_timestamp: {}...",
self.end_timestamp
);
let indexes_to_read = self.load_indexes_by_timestamp(timestamp, count).await?;
if indexes_to_read.is_none() {
return Ok(IggyMessagesBatchSet::empty());
}
let indexes_to_read = indexes_to_read.unwrap();
self.messages_reader
.as_ref()
.expect("Messages reader not initialized")
.load_messages_from_disk(indexes_to_read)
.await
.with_error_context(|error| {
format!("Failed to load messages from segment file by timestamp: {self}. {error}")
})
}
}