Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions core/journal/src/prepare_journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,12 @@ impl PrepareJournal {
// `PrepareHeader` projection in consensus builds prepares with
// `..Default::default()` so the integrity fields are always 0.
// Until a producer computes them, verification here would be
// trivially-passing noise. Without it, a body bit-flip that
// leaves the header valid is replayed silently as corrupt
// state. Committed bytes are meant to be byte-identical across
// trivially-passing noise. The per-message receive gate now rejects
// transit body corruption on a follower before apply, but this
// recovery scan still does not re-verify body integrity read back
// from disk, so an at-rest body bit-flip that leaves the header
// valid is replayed silently. Committed bytes are meant to be
// byte-identical across
// replicas (deterministic apply, timestamp replicated not
// re-projected), so once the producer computes the integrity fields
// they should agree on every node and this check can be turned on
Expand Down
35 changes: 34 additions & 1 deletion core/partitions/src/iggy_index_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use crate::iggy_index::{IGGY_INDEX_SIZE, IggyIndex};
use crate::iggy_index::{IGGY_INDEX_SIZE, IggyIndex, IggyIndexCache};
use bytes::Buf;
use compio::fs::{File, OpenOptions};
use compio::io::AsyncReadAtExt;
Expand Down Expand Up @@ -114,4 +114,37 @@ impl IggyIndexReader {
.await?,
))
}

/// Load every whole entry into an [`IggyIndexCache`] for offset / timestamp
/// lower-bound lookups. Reads the whole file in one pass (index files are
/// tiny: one sparse entry per flushed chunk). A trailing partial entry
/// (torn write) is ignored (see [`Self::entry_count`]).
///
/// # Errors
///
/// Returns an error if the file metadata or bytes cannot be read.
pub async fn load_all(&self) -> Result<IggyIndexCache, IggyError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_all() reads and materializes an entire .index file on the poll hot path with no bound

core/partitions/src/iggy_index_reader.rs:126

Index density is one sparse entry per flush, governed by messages_required_to_save and size_of_messages_required_to_save (core/server-ng/config.toml:515,520). At defaults a 1 GiB segment yields about 1024 entries, a 24 KiB read, and the "index files are tiny" comment holds. Set messages_required_to_save = 1, which is what in-tree repair_config() uses, with 100-byte messages, and the same segment yields 7.25M entries: a single 174 MB read_exact_at issued from inside a poll, which compio will punt to io-wq. That is the exact stall this PR set out to remove, now reachable by supported config. There is no cap, no chunking, and no fallback.

This also reintroduces up to 12 resident index caches per partition, which is the thing core/partitions/src/iggy_partition.rs:2731-2736 deliberately drops at rotation, with a comment naming retained hundreds of MB as the reason.

Since IggyIndexCache is sorted and only ever binary-searched (core/partitions/src/iggy_index.rs:91,105), the bounded alternative is roughly 20 24-byte read_entry_at preads above a byte budget; the reader already has that primitive at :67.

let count = usize::try_from(self.entry_count().await?)
.map_err(|_| IggyError::CannotReadFileMetadata)?;
if count == 0 {
return Ok(IggyIndexCache::empty());
}

// `with_capacity` (len 0): `read_exact_at` fills the spare capacity in
// place and advances the length (see `read_entry_at`).
let buffer = Vec::with_capacity(count * IGGY_INDEX_SIZE);
let (result, buffer): (std::io::Result<()>, Vec<u8>) =
self.file.read_exact_at(buffer, 0).await.into();
result.map_err(|_| IggyError::CannotReadFile)?;

let mut cache = IggyIndexCache::with_capacity(count);
let mut view = buffer.as_slice();
for _ in 0..count {
let offset = view.get_u64_le();
let timestamp = view.get_u64_le();
let position = view.get_u64_le();
cache.insert(offset, timestamp, position);
}
Ok(cache)
}
}
Loading
Loading