blob: 4fd5673db5ab3f13befde20837ef0f4a664a0472 [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::iggy_index::{IGGY_INDEX_SIZE, IggyIndexCache};
use crate::iggy_index_writer::IggyIndexWriter;
use crate::messages_writer::MessagesWriter;
use crate::segment::Segment;
use iggy_common::{IggyByteSize, IggyMessagesBatch};
use journal::{Journal, Storage};
use ringbuffer::AllocRingBuffer;
use server_common::{IggyMessagesBatchSetInFlight, SegmentStorage};
use std::fmt::Debug;
use std::rc::Rc;
const SEGMENTS_CAPACITY: usize = 1024;
const ACCESS_MAP_CAPACITY: usize = 8;
const SIZE_16MB: usize = 16 * 1024 * 1024;
/// Tracking metadata for the journal's current state.
///
/// Replaces the server journal's `Inner` struct — lives in the `SegmentedLog`
/// rather than inside the journal, and is used as a lookup table for
/// constructing `MessageLookup` headers.
#[derive(Default, Debug, Clone, Copy)]
pub struct JournalInfo {
pub base_offset: u64,
pub current_offset: u64,
pub first_timestamp: u64,
pub end_timestamp: u64,
pub max_timestamp: u64,
pub messages_count: u32,
pub size: IggyByteSize,
}
/// Groups the journal implementation with its tracking metadata.
#[derive(Debug)]
pub struct JournalState<J> {
pub inner: J,
pub info: JournalInfo,
}
impl<J, S> Journal<S> for JournalState<J>
where
S: Storage,
J: Journal<S>,
{
type Header = J::Header;
type Entry = J::Entry;
type HeaderRef<'a>
= J::HeaderRef<'a>
where
Self: 'a;
fn header(&self, idx: usize) -> Option<Self::HeaderRef<'_>> {
self.inner.header(idx)
}
fn previous_header(&self, header: &Self::Header) -> Option<Self::HeaderRef<'_>> {
self.inner.previous_header(header)
}
fn append(&self, entry: Self::Entry) -> impl Future<Output = std::io::Result<()>> {
self.inner.append(entry)
}
fn entry(&self, header: &Self::Header) -> impl Future<Output = Option<Self::Entry>> {
self.inner.entry(header)
}
}
impl<J: Default> Default for JournalState<J> {
fn default() -> Self {
Self {
inner: J::default(),
info: JournalInfo::default(),
}
}
}
// TODO: Structure this better, the segmented log does not need to be generic over S, the Journal needs.
// This struct aliases in terms of the code contained the `SegmentedLog` from `core/server/src/streaming/partitions/log.rs`.
// The only difference is the `Journal` generic, we use different trait.
#[derive(Debug)]
pub struct SegmentedLog<J, S>
where
S: Storage,
J: Debug + Journal<S>,
{
journal: JournalState<J>,
_pd: std::marker::PhantomData<S>,
// Ring buffer tracking recently accessed segment indices for cleanup optimization.
// A background task uses this to identify and close file descriptors for unused segments.
_access_map: AllocRingBuffer<usize>,
_cache: (),
segments: Vec<Segment>,
indexes: Vec<Option<IggyIndexCache>>,
storage: Vec<SegmentStorage>,
messages_writers: Vec<Option<Rc<MessagesWriter>>>,
index_writers: Vec<Option<Rc<IggyIndexWriter>>>,
in_flight: IggyMessagesBatchSetInFlight,
}
impl<J, S> Default for SegmentedLog<J, S>
where
S: Storage,
J: Debug + Default + Journal<S>,
{
fn default() -> Self {
Self {
journal: JournalState::default(),
_pd: std::marker::PhantomData,
_access_map: AllocRingBuffer::with_capacity_power_of_2(ACCESS_MAP_CAPACITY),
_cache: (),
segments: Vec::with_capacity(SEGMENTS_CAPACITY),
storage: Vec::with_capacity(SEGMENTS_CAPACITY),
indexes: Vec::with_capacity(SEGMENTS_CAPACITY),
messages_writers: Vec::with_capacity(SEGMENTS_CAPACITY),
index_writers: Vec::with_capacity(SEGMENTS_CAPACITY),
in_flight: IggyMessagesBatchSetInFlight::default(),
}
}
}
impl<J, S> SegmentedLog<J, S>
where
S: Storage,
J: Debug + Journal<S>,
{
pub const fn has_segments(&self) -> bool {
!self.segments.is_empty()
}
pub const fn segments(&self) -> &Vec<Segment> {
&self.segments
}
pub const fn segments_mut(&mut self) -> &mut Vec<Segment> {
&mut self.segments
}
pub const fn storages_mut(&mut self) -> &mut Vec<SegmentStorage> {
&mut self.storage
}
pub const fn storages(&self) -> &Vec<SegmentStorage> {
&self.storage
}
pub const fn messages_writers(&self) -> &Vec<Option<Rc<MessagesWriter>>> {
&self.messages_writers
}
pub const fn messages_writers_mut(&mut self) -> &mut Vec<Option<Rc<MessagesWriter>>> {
&mut self.messages_writers
}
pub const fn index_writers(&self) -> &Vec<Option<Rc<IggyIndexWriter>>> {
&self.index_writers
}
pub const fn index_writers_mut(&mut self) -> &mut Vec<Option<Rc<IggyIndexWriter>>> {
&mut self.index_writers
}
pub fn active_segment(&self) -> &Segment {
self.segments
.last()
.expect("active segment called on empty log")
}
pub fn active_segment_mut(&mut self) -> &mut Segment {
self.segments
.last_mut()
.expect("active segment called on empty log")
}
pub fn active_storage(&self) -> &SegmentStorage {
self.storage
.last()
.expect("active storage called on empty log")
}
pub fn active_storage_mut(&mut self) -> &mut SegmentStorage {
self.storage
.last_mut()
.expect("active storage called on empty log")
}
pub const fn indexes(&self) -> &Vec<Option<IggyIndexCache>> {
&self.indexes
}
pub const fn indexes_mut(&mut self) -> &mut Vec<Option<IggyIndexCache>> {
&mut self.indexes
}
/// Index cache for segment `index`, if resident.
pub fn segment_indexes(&self, index: usize) -> Option<&IggyIndexCache> {
self.indexes.get(index).and_then(Option::as_ref)
}
pub fn active_indexes(&self) -> Option<&IggyIndexCache> {
self.indexes
.last()
.expect("active indexes called on empty log")
.as_ref()
}
pub fn active_indexes_mut(&mut self) -> Option<&mut IggyIndexCache> {
self.indexes
.last_mut()
.expect("active indexes called on empty log")
.as_mut()
}
pub fn clear_active_indexes(&mut self) {
let indexes = self
.indexes
.last_mut()
.expect("active indexes called on empty log");
*indexes = None;
}
pub fn ensure_indexes(&mut self) {
let indexes = self
.indexes
.last_mut()
.expect("active indexes called on empty log");
if indexes.is_none() {
let capacity = SIZE_16MB / IGGY_INDEX_SIZE;
*indexes = Some(IggyIndexCache::with_capacity(capacity));
}
}
pub fn add_persisted_segment(
&mut self,
segment: Segment,
storage: SegmentStorage,
messages_writer: Option<Rc<MessagesWriter>>,
index_writer: Option<Rc<IggyIndexWriter>>,
) {
self.segments.push(segment);
self.storage.push(storage);
self.indexes.push(None);
self.messages_writers.push(messages_writer);
self.index_writers.push(index_writer);
}
pub fn set_segment_indexes(&mut self, segment_index: usize, indexes: IggyIndexCache) {
if let Some(segment_indexes) = self.indexes.get_mut(segment_index) {
*segment_indexes = Some(indexes);
}
}
pub const fn in_flight(&self) -> &IggyMessagesBatchSetInFlight {
&self.in_flight
}
pub const fn in_flight_mut(&mut self) -> &mut IggyMessagesBatchSetInFlight {
&mut self.in_flight
}
pub fn set_in_flight(&mut self, batches: Vec<IggyMessagesBatch>) {
self.in_flight.set(batches);
}
pub fn clear_in_flight(&mut self) {
self.in_flight.clear();
}
}
impl<J, S> SegmentedLog<J, S>
where
S: Storage,
J: Debug + Journal<S>,
{
pub const fn journal_mut(&mut self) -> &mut JournalState<J> {
&mut self.journal
}
pub const fn journal(&self) -> &JournalState<J> {
&self.journal
}
}