blob: a420f2c8fd4137cc5eb861ad75bb8abbe0b50109 [file]
use crate::streaming::persistence::persister::Persister;
use crate::streaming::segments::index::{Index, IndexRange};
use crate::streaming::segments::segment::Segment;
use crate::streaming::segments::time_index::TimeIndex;
use crate::streaming::storage::{SegmentStorage, Storage};
use crate::streaming::utils::file;
use anyhow::Context;
use async_trait::async_trait;
use bytes::{BufMut, Bytes, BytesMut};
use iggy::bytes_serializable::BytesSerializable;
use iggy::error::IggyError;
use iggy::models::messages::{Message, MessageState};
use iggy::utils::checksum;
use std::collections::HashMap;
use std::io::SeekFrom;
use std::path::Path;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncSeekExt, BufReader};
use tracing::log::{trace, warn};
use tracing::{error, info};
const EMPTY_INDEXES: Vec<Index> = vec![];
const EMPTY_TIME_INDEXES: Vec<TimeIndex> = vec![];
const INDEX_SIZE: u32 = 4;
const BUF_READER_CAPACITY_BYTES: usize = 512 * 1000;
#[derive(Debug)]
pub struct FileSegmentStorage {
persister: Arc<dyn Persister>,
}
impl FileSegmentStorage {
pub fn new(persister: Arc<dyn Persister>) -> Self {
Self { persister }
}
}
unsafe impl Send for FileSegmentStorage {}
unsafe impl Sync for FileSegmentStorage {}
// TODO: Split into smaller components.
#[async_trait]
impl Storage<Segment> for FileSegmentStorage {
async fn load(&self, segment: &mut Segment) -> Result<(), IggyError> {
info!(
"Loading segment from disk for start offset: {} and partition with ID: {} for topic with ID: {} and stream with ID: {} ...",
segment.start_offset, segment.partition_id, segment.topic_id, segment.stream_id
);
let log_file = file::open(&segment.log_path).await?;
let file_size = log_file.metadata().await.unwrap().len() as u64;
segment.size_bytes = file_size as u32;
let messages_count = segment.get_messages_count();
info!(
"Segment log file of size {} for start offset {}, current offset: {}, and partition with ID: {} for topic with ID: {} and stream with ID: {}.",
segment.size_bytes, segment.start_offset, segment.current_offset, segment.partition_id, segment.topic_id, segment.stream_id
);
if segment.config.segment.cache_indexes {
segment.indexes = Some(segment.storage.segment.load_all_indexes(segment).await?);
info!(
"Loaded {} indexes for segment with start offset: {} and partition with ID: {} for topic with ID: {} and stream with ID: {}.",
segment.indexes.as_ref().unwrap().len(),
segment.start_offset,
segment.partition_id,
segment.topic_id,
segment.stream_id
);
}
if segment.config.segment.cache_time_indexes {
let time_indexes = self.load_all_time_indexes(segment).await?;
if !time_indexes.is_empty() {
let last_index = time_indexes.last().unwrap();
segment.current_offset = segment.start_offset + last_index.relative_offset as u64;
segment.time_indexes = Some(time_indexes);
}
info!(
"Loaded {} time indexes for segment with start offset: {} and partition with ID: {} for topic with ID: {} and stream with ID: {}.",
segment.time_indexes.as_ref().unwrap().len(),
segment.start_offset,
segment.partition_id,
segment.topic_id,
segment.stream_id
);
} else {
let last_time_index = self.load_last_time_index(segment).await?;
if let Some(last_index) = last_time_index {
segment.current_offset = segment.start_offset + last_index.relative_offset as u64;
info!(
"Loaded last time index for segment with start offset: {} and partition with ID: {} for topic with ID: {} and stream with ID: {}.",
segment.start_offset,
segment.partition_id,
segment.topic_id,
segment.stream_id
);
}
}
if segment.is_full().await {
segment.is_closed = true;
}
segment
.size_of_parent_stream
.fetch_add(file_size, Ordering::SeqCst);
segment
.size_of_parent_topic
.fetch_add(file_size, Ordering::SeqCst);
segment
.size_of_parent_partition
.fetch_add(file_size, Ordering::SeqCst);
segment
.messages_count_of_parent_stream
.fetch_add(messages_count, Ordering::SeqCst);
segment
.messages_count_of_parent_topic
.fetch_add(messages_count, Ordering::SeqCst);
segment
.messages_count_of_parent_partition
.fetch_add(messages_count, Ordering::SeqCst);
Ok(())
}
async fn save(&self, segment: &Segment) -> Result<(), IggyError> {
info!("Saving segment with start offset: {} for partition with ID: {} for topic with ID: {} and stream with ID: {}",
segment.start_offset, segment.partition_id, segment.topic_id, segment.stream_id);
if !Path::new(&segment.log_path).exists()
&& self
.persister
.overwrite(&segment.log_path, &[])
.await
.is_err()
{
return Err(IggyError::CannotCreateSegmentLogFile(
segment.log_path.clone(),
));
}
if !Path::new(&segment.time_index_path).exists()
&& self
.persister
.overwrite(&segment.time_index_path, &[])
.await
.is_err()
{
return Err(IggyError::CannotCreateSegmentTimeIndexFile(
segment.time_index_path.clone(),
));
}
if !Path::new(&segment.index_path).exists()
&& self
.persister
.overwrite(&segment.index_path, &[])
.await
.is_err()
{
return Err(IggyError::CannotCreateSegmentIndexFile(
segment.index_path.clone(),
));
}
info!("Saved segment log file with start offset: {} for partition with ID: {} for topic with ID: {} and stream with ID: {}",
segment.start_offset, segment.partition_id, segment.topic_id, segment.stream_id);
Ok(())
}
async fn delete(&self, segment: &Segment) -> Result<(), IggyError> {
let segment_size = segment.size_bytes;
let segment_count_of_messages = segment.get_messages_count();
info!(
"Deleting segment of size {segment_size} with start offset: {} for partition with ID: {} for stream with ID: {} and topic with ID: {}...",
segment.start_offset, segment.partition_id, segment.stream_id, segment.topic_id,
);
self.persister.delete(&segment.log_path).await?;
self.persister.delete(&segment.index_path).await?;
self.persister.delete(&segment.time_index_path).await?;
segment
.size_of_parent_stream
.fetch_sub(segment.size_bytes as u64, Ordering::SeqCst);
segment
.size_of_parent_topic
.fetch_sub(segment.size_bytes as u64, Ordering::SeqCst);
segment
.size_of_parent_partition
.fetch_sub(segment.size_bytes as u64, Ordering::SeqCst);
segment
.messages_count_of_parent_stream
.fetch_sub(segment_count_of_messages, Ordering::SeqCst);
segment
.messages_count_of_parent_topic
.fetch_sub(segment_count_of_messages, Ordering::SeqCst);
segment
.messages_count_of_parent_partition
.fetch_sub(segment_count_of_messages, Ordering::SeqCst);
info!(
"Deleted segment of size {segment_size} with start offset: {} for partition with ID: {} for stream with ID: {} and topic with ID: {}.",
segment.start_offset, segment.partition_id, segment.stream_id, segment.topic_id,
);
Ok(())
}
}
#[async_trait]
impl SegmentStorage for FileSegmentStorage {
async fn load_messages(
&self,
segment: &Segment,
index_range: &IndexRange,
) -> Result<Vec<Arc<Message>>, IggyError> {
let mut messages = Vec::with_capacity(
1 + (index_range.end.relative_offset - index_range.start.relative_offset) as usize,
);
load_messages_by_range(segment, index_range, |message: Message| {
messages.push(Arc::new(message));
Ok(())
})
.await?;
trace!("Loaded {} messages from disk.", messages.len());
Ok(messages)
}
async fn load_newest_messages_by_size(
&self,
segment: &Segment,
size_bytes: u64,
) -> Result<Vec<Arc<Message>>, IggyError> {
let mut messages = Vec::new();
let mut total_size_bytes = 0;
load_messages_by_size(segment, size_bytes, |message: Message| {
total_size_bytes += message.get_size_bytes() as u64;
messages.push(Arc::new(message));
Ok(())
})
.await?;
trace!(
"Loaded {} newest messages of total size {} bytes from disk.",
messages.len(),
total_size_bytes
);
Ok(messages)
}
async fn save_messages(
&self,
segment: &Segment,
messages: &[Arc<Message>],
) -> Result<u32, IggyError> {
let messages_size = messages
.iter()
.map(|message| message.get_size_bytes())
.sum::<u32>();
let mut bytes = BytesMut::with_capacity(messages_size as usize);
for message in messages {
message.extend(&mut bytes);
}
if let Err(err) = self
.persister
.append(&segment.log_path, &bytes)
.await
.with_context(|| format!("Failed to save messages to segment: {}", segment.log_path))
{
return Err(IggyError::CannotSaveMessagesToSegment(err));
}
Ok(messages_size)
}
async fn load_message_ids(&self, segment: &Segment) -> Result<Vec<u128>, IggyError> {
let mut message_ids = Vec::new();
load_messages_by_range(segment, &IndexRange::max_range(), |message: Message| {
message_ids.push(message.id);
Ok(())
})
.await?;
trace!("Loaded {} message IDs from disk.", message_ids.len());
Ok(message_ids)
}
async fn load_checksums(&self, segment: &Segment) -> Result<(), IggyError> {
load_messages_by_range(segment, &IndexRange::max_range(), |message: Message| {
let calculated_checksum = checksum::calculate(&message.payload);
trace!(
"Loaded message for offset: {}, checksum: {}, expected: {}",
message.offset,
calculated_checksum,
message.checksum
);
if calculated_checksum != message.checksum {
return Err(IggyError::InvalidMessageChecksum(
calculated_checksum,
message.checksum,
message.offset,
));
}
Ok(())
})
.await?;
Ok(())
}
async fn load_all_indexes(&self, segment: &Segment) -> Result<Vec<Index>, IggyError> {
trace!("Loading indexes from file...");
let file = file::open(&segment.index_path).await?;
let file_size = file.metadata().await?.len() as usize;
if file_size == 0 {
trace!("Index file is empty.");
return Ok(EMPTY_INDEXES);
}
let indexes_count = file_size / 4;
let mut indexes = Vec::with_capacity(indexes_count);
let mut reader = BufReader::with_capacity(BUF_READER_CAPACITY_BYTES, file);
for offset in 0..indexes_count {
match reader.read_u32_le().await {
Ok(position) => {
indexes.push(Index {
relative_offset: offset as u32,
position,
});
}
Err(error) => {
error!(
"Cannot read position from index file for offset: {}. Error: {}",
offset, error
);
break;
}
}
}
if indexes.len() != indexes_count {
error!(
"Loaded {} indexes from disk, expected {}.",
indexes.len(),
indexes_count
);
}
trace!("Loaded {} indexes from file.", indexes_count);
Ok(indexes)
}
async fn load_index_range(
&self,
segment: &Segment,
segment_start_offset: u64,
mut index_start_offset: u64,
index_end_offset: u64,
) -> Result<Option<IndexRange>, IggyError> {
trace!(
"Loading index range for offsets: {} to {}, segment starts at: {}",
index_start_offset,
index_end_offset,
segment_start_offset
);
if index_start_offset > index_end_offset {
warn!(
"Index start offset: {} is greater than index end offset: {}.",
index_start_offset, index_end_offset
);
return Ok(None);
}
let mut file = file::open(&segment.index_path).await?;
let file_length = file.metadata().await?.len() as u32;
if file_length == 0 {
trace!("Index file is empty.");
return Ok(None);
}
trace!("Index file length: {}.", file_length);
if index_start_offset < segment_start_offset {
index_start_offset = segment_start_offset - 1;
}
let relative_start_offset = (index_start_offset - segment_start_offset) as u32;
let relative_end_offset = (index_end_offset - segment_start_offset) as u32;
let start_seek_position = relative_start_offset * INDEX_SIZE;
let mut end_seek_position = relative_end_offset * INDEX_SIZE;
if end_seek_position >= file_length {
end_seek_position = file_length - INDEX_SIZE;
}
if start_seek_position >= end_seek_position {
trace!(
"Start seek position: {} is greater than or equal to end seek position: {}.",
start_seek_position,
end_seek_position
);
return Ok(None);
}
trace!(
"Seeking to index range: {}...{}, position range: {}...{}",
relative_start_offset,
relative_end_offset,
start_seek_position,
end_seek_position
);
file.seek(SeekFrom::Start(start_seek_position as u64))
.await?;
let start_position = file.read_u32_le().await?;
file.seek(SeekFrom::Start(end_seek_position as u64)).await?;
let mut end_position = file.read_u32_le().await?;
if end_position == 0 {
end_position = file_length;
}
trace!(
"Loaded index range: {}...{}, position range: {}...{}",
relative_start_offset,
relative_end_offset,
start_position,
end_position
);
Ok(Some(IndexRange {
start: Index {
relative_offset: relative_start_offset,
position: start_position,
},
end: Index {
relative_offset: relative_end_offset,
position: end_position,
},
}))
}
async fn save_index(
&self,
segment: &Segment,
mut current_position: u32,
messages: &[Arc<Message>],
) -> Result<(), IggyError> {
let mut bytes = Vec::with_capacity(messages.len() * 4);
for message in messages {
trace!("Persisting index for position: {}", current_position);
bytes.put_u32_le(current_position);
current_position += message.get_size_bytes();
}
if let Err(err) = self
.persister
.append(&segment.index_path, &bytes)
.await
.with_context(|| format!("Failed to save index to segment: {}", segment.index_path))
{
return Err(IggyError::CannotSaveIndexToSegment(err));
}
Ok(())
}
async fn load_all_time_indexes(&self, segment: &Segment) -> Result<Vec<TimeIndex>, IggyError> {
trace!("Loading time indexes from file...");
let file = file::open(&segment.time_index_path).await?;
let file_size = file.metadata().await?.len() as usize;
if file_size == 0 {
trace!("Time index file is empty.");
return Ok(EMPTY_TIME_INDEXES);
}
let indexes_count = file_size / 8;
let mut indexes = Vec::with_capacity(indexes_count);
let mut reader = BufReader::with_capacity(BUF_READER_CAPACITY_BYTES, file);
for offset in 0..indexes_count {
match reader.read_u64_le().await {
Ok(timestamp) => {
indexes.push(TimeIndex {
relative_offset: offset as u32,
timestamp,
});
}
Err(error) => {
error!(
"Cannot read timestamp from time index file for offset: {}. Error: {}",
offset, error
);
break;
}
}
}
if indexes.len() != indexes_count {
error!(
"Loaded {} time indexes from disk, expected {}.",
indexes.len(),
indexes_count
);
}
trace!("Loaded {} time indexes from file.", indexes_count);
Ok(indexes)
}
async fn load_last_time_index(
&self,
segment: &Segment,
) -> Result<Option<TimeIndex>, IggyError> {
trace!("Loading last time index from file...");
let mut file = file::open(&segment.time_index_path).await?;
let file_size = file.metadata().await?.len() as usize;
if file_size == 0 {
trace!("Time index file is empty.");
return Ok(None);
}
let indexes_count = file_size / 8;
let last_index_position = file_size - 8;
file.seek(SeekFrom::Start(last_index_position as u64))
.await?;
let timestamp = file.read_u64_le().await?;
let index = TimeIndex {
relative_offset: indexes_count as u32 - 1,
timestamp,
};
trace!("Loaded last time index from file: {:?}", index);
Ok(Some(index))
}
async fn save_time_index(
&self,
segment: &Segment,
messages: &[Arc<Message>],
) -> Result<(), IggyError> {
let mut bytes = Vec::with_capacity(messages.len() * 8);
for message in messages {
bytes.put_u64_le(message.timestamp);
}
if let Err(err) = self
.persister
.append(&segment.time_index_path, &bytes)
.await
.with_context(|| {
format!(
"Failed to save TimeIndex to segment: {}",
segment.time_index_path
)
})
{
return Err(IggyError::CannotSaveTimeIndexToSegment(err));
}
Ok(())
}
}
async fn load_messages_by_range(
segment: &Segment,
index_range: &IndexRange,
mut on_message: impl FnMut(Message) -> Result<(), IggyError>,
) -> Result<(), IggyError> {
let file = file::open(&segment.log_path).await?;
let file_size = file.metadata().await?.len();
if file_size == 0 {
return Ok(());
}
if index_range.end.position == 0 {
return Ok(());
}
let mut reader = BufReader::with_capacity(BUF_READER_CAPACITY_BYTES, file);
reader
.seek(SeekFrom::Start(index_range.start.position as u64))
.await?;
let mut read_messages = 0;
let messages_count =
(1 + index_range.end.relative_offset - index_range.start.relative_offset) as usize;
while read_messages < messages_count {
let offset = reader.read_u64_le().await;
if offset.is_err() {
break;
}
let state = reader.read_u8().await;
if state.is_err() {
return Err(IggyError::CannotReadMessageState);
}
let state = MessageState::from_code(state.unwrap())?;
let timestamp = reader.read_u64_le().await;
if timestamp.is_err() {
return Err(IggyError::CannotReadMessageTimestamp);
}
let id = reader.read_u128_le().await;
if id.is_err() {
return Err(IggyError::CannotReadMessageId);
}
let checksum = reader.read_u32_le().await;
if checksum.is_err() {
return Err(IggyError::CannotReadMessageChecksum);
}
let headers_length = reader.read_u32_le().await;
if headers_length.is_err() {
return Err(IggyError::CannotReadHeadersLength);
}
let headers_length = headers_length.unwrap();
let headers = match headers_length {
0 => None,
_ => {
let mut headers_payload = BytesMut::with_capacity(headers_length as usize);
headers_payload.put_bytes(0, headers_length as usize);
if reader.read_exact(&mut headers_payload).await.is_err() {
return Err(IggyError::CannotReadHeadersPayload);
}
let headers = HashMap::from_bytes(headers_payload.freeze())?;
Some(headers)
}
};
let payload_length = reader.read_u32_le().await?;
let mut payload = BytesMut::with_capacity(payload_length as usize);
payload.put_bytes(0, payload_length as usize);
if reader.read_exact(&mut payload).await.is_err() {
return Err(IggyError::CannotReadMessagePayload);
}
let offset = offset.unwrap();
let timestamp = timestamp.unwrap();
let id = id.unwrap();
let checksum = checksum.unwrap();
let message = Message::create(
offset,
state,
timestamp,
id,
payload.freeze(),
checksum,
headers,
);
read_messages += 1;
on_message(message)?;
}
Ok(())
}
async fn load_messages_by_size(
segment: &Segment,
size_bytes: u64,
mut on_message: impl FnMut(Message) -> Result<(), IggyError>,
) -> Result<(), IggyError> {
let file = file::open(&segment.log_path).await?;
let file_size = file.metadata().await?.len();
if file_size == 0 {
return Ok(());
}
let threshold = file_size.saturating_sub(size_bytes);
let mut reader = BufReader::with_capacity(BUF_READER_CAPACITY_BYTES, file);
let mut accumulated_size: u64 = 0;
loop {
let offset = reader.read_u64_le().await;
if offset.is_err() {
break;
}
let state = reader.read_u8().await;
if state.is_err() {
return Err(IggyError::CannotReadMessageState);
}
let state = MessageState::from_code(state.unwrap())?;
let timestamp = reader.read_u64_le().await;
if timestamp.is_err() {
return Err(IggyError::CannotReadMessageTimestamp);
}
let id = reader.read_u128_le().await;
if id.is_err() {
return Err(IggyError::CannotReadMessageId);
}
let checksum = reader.read_u32_le().await;
if checksum.is_err() {
return Err(IggyError::CannotReadMessageChecksum);
}
let headers_length = reader.read_u32_le().await;
if headers_length.is_err() {
return Err(IggyError::CannotReadHeadersLength);
}
let headers_length = headers_length.unwrap();
let headers = match headers_length {
0 => None,
_ => {
let mut headers_payload = BytesMut::with_capacity(headers_length as usize);
if reader.read_exact(&mut headers_payload).await.is_err() {
return Err(IggyError::CannotReadHeadersPayload);
}
let headers = HashMap::from_bytes(headers_payload.freeze())?;
Some(headers)
}
};
let payload_length = reader.read_u32_le().await;
if payload_length.is_err() {
return Err(IggyError::CannotReadMessageLength);
}
let mut payload = vec![0; payload_length.unwrap() as usize];
if reader.read_exact(&mut payload).await.is_err() {
return Err(IggyError::CannotReadMessagePayload);
}
let offset = offset.unwrap();
let timestamp = timestamp.unwrap();
let id = id.unwrap();
let checksum = checksum.unwrap();
let message = Message::create(
offset,
state,
timestamp,
id,
Bytes::from(payload),
checksum,
headers,
);
let message_size = message.get_size_bytes() as u64;
if accumulated_size >= threshold {
on_message(message)?;
}
accumulated_size += message_size;
if accumulated_size >= file_size {
break;
}
}
Ok(())
}