Skip to main content

krabka_remote_storage/
lib.rs

1//! KIP-405 tiered-storage SPI and reference implementations for Krabka.
2//!
3//! This crate is the foundation layer for Krabka's tiered storage. It
4//! defines the two plugin SPIs and the data model that they exchange, and
5//! it supplies the two reference implementations that the rest of the
6//! tiered-storage stack builds and tests against. It mirrors the shapes
7//! of Apache Kafka's `storage-api` module
8//! (`org.apache.kafka.server.log.remote.storage`).
9//!
10//! It also holds the write-once (WORM) archive layer, which turns an
11//! object-store tier into a compliance archive: single-PUT objects are
12//! conditional creates, multipart objects are guarded by verified bucket
13//! retention, every segment copy seals a signed and hash-chained manifest,
14//! and the backend refuses every delete. See
15//! [Write-once archive mode](#write-once-worm-archive-mode).
16//!
17//! ## What this crate provides
18//!
19//! - [`RemoteStorageManager`] copies, fetches, and deletes segment data and
20//!   indexes to and from the remote tier.
21//! - [`RemoteLogMetadataManager`] stores and queries remote-segment metadata,
22//!   with a strict lifecycle state machine.
23//! - The data model: [`TopicIdPartition`], [`RemoteLogSegmentId`],
24//!   [`RemoteLogSegmentMetadata`] / [`RemoteLogSegmentMetadataUpdate`],
25//!   [`RemoteLogSegmentState`], [`LogSegmentData`], [`IndexType`],
26//!   [`CustomMetadata`], and the partition-delete lifecycle
27//!   ([`RemotePartitionDeleteMetadata`] / [`RemotePartitionDeleteState`]).
28//! - The archive key codec ([`partition_dir_name`] / [`parse_partition_dir_name`],
29//!   [`segment_file_name`] / [`parse_segment_file_name`]) and the index
30//!   decoders in [`index`], which together let a reader discover and decode an
31//!   archive from object storage alone.
32//! - [`LocalTieredStorage`] is a filesystem [`RemoteStorageManager`].
33//! - [`InmemoryRemoteLogMetadataManager`] is a process-memory
34//!   [`RemoteLogMetadataManager`].
35//! - [`S3RemoteStorage`] is an object-store [`RemoteStorageManager`] for S3 and
36//!   for Google Cloud Storage.
37//! - The write-once (WORM) archive layer: [`WormConfig`] turns the mode on
38//!   through [`S3RemoteStorage::with_worm`], [`WormArchiver`] seals each
39//!   [`SegmentManifest`] onto the partition chain, and [`verify_archive`]
40//!   audits a finished archive into an [`ArchiveVerifyReport`].
41//!
42//! ## Boundary with the broker
43//!
44//! This crate is the SPI and reference-implementation layer. Broker-specific
45//! behavior such as segment-copy scheduling, `Fetch` remote reads,
46//! local-vs-remote retention policy, and topic config parsing lives in the
47//! broker.
48//!
49//! The SPIs are intentionally **synchronous**. They mirror Kafka's
50//! blocking `RemoteStorageManager` / `RemoteLogMetadataManager`, which the
51//! broker drives from a thread pool. The broker wraps the calls in
52//! `spawn_blocking`. Because the SPIs stay synchronous, the segment-copy and
53//! segment-fetch paths here need no async runtime of their own. The archive
54//! verifier is the exception: [`verify_archive`] is an `async fn` over the
55//! object store, and the `krabka-worm-verify` binary starts a Tokio runtime to
56//! drive it.
57//!
58//! ## Write-once (WORM) archive mode
59//!
60//! [`S3RemoteStorage::with_worm`] puts a verified S3 backend into archive
61//! mode. Startup confirms versioning and locked default retention (S3 Object
62//! Lock in compliance mode or GCS Bucket Lock). Each copy writes small objects
63//! with `PutMode::Create`; multipart objects atomically reserve the key, then
64//! read back and verify the completed digest and version. The manifest records
65//! which protection each object used, joins the partition's hash chain, and is
66//! signed with Ed25519.
67//! The backend refuses every delete, and a [`WormConfig::write_only`] archive
68//! refuses every remote fetch as well.
69//!
70//! The bucket enforces the retention, not this crate. The archive layer refuses
71//! to start unless the bucket already has the required policy.
72//!
73//! [`verify_archive`] reads a finished archive back with nothing but the
74//! objects. It recomputes every chain head, checks every signature against a
75//! [`TrustedManifestKeys`] set, and confirms that every object a manifest names
76//! is present with the recorded size. Tail truncation is the one attack the
77//! archive cannot reveal on its own, so a caller that holds an independently
78//! recorded tip passes it as [`VerifyRequest::expect_head`]. The
79//! `krabka-worm-verify` binary is the same check with a command line and graded
80//! exit codes.
81//!
82//! ## Filesystem-backed remote tier
83//!
84//! ```no_run
85//! use {
86//!     bytes::Bytes,
87//!     krabka_ids::LeaderEpoch,
88//!     krabka_remote_storage::{
89//!         IndexType, LocalTieredStorage, LogSegmentData, RemoteLogSegmentDetails,
90//!         RemoteLogSegmentId, RemoteLogSegmentMetadata, RemoteLogSegmentState,
91//!         RemoteStorageManager, TopicIdPartition,
92//!     },
93//!     std::{collections::BTreeMap, path::PathBuf},
94//!     uuid::Uuid,
95//! };
96//!
97//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
98//! let storage = LocalTieredStorage::new(PathBuf::from("/var/lib/krabka-remote"));
99//! let topic_partition = TopicIdPartition::new(Uuid::new_v4(), "orders", 0);
100//! let segment_id = RemoteLogSegmentId::new(topic_partition, Uuid::new_v4());
101//! let mut leader_epochs = BTreeMap::new();
102//! leader_epochs.insert(LeaderEpoch(0), 0);
103//! let metadata = RemoteLogSegmentMetadata::new(
104//!     segment_id,
105//!     0,
106//!     999,
107//!     1_713_000_000_000,
108//!     1,
109//!     1_713_000_000_000,
110//!     RemoteLogSegmentDetails::new(
111//!         1_048_576,
112//!         RemoteLogSegmentState::CopySegmentStarted,
113//!         leader_epochs,
114//!     ),
115//! )?;
116//!
117//! // The broker fills these paths from a closed local log segment.
118//! let segment = LogSegmentData {
119//!     log_segment: PathBuf::from("/var/lib/krabka/orders-0/00000000000000000000.log"),
120//!     offset_index: PathBuf::from("/var/lib/krabka/orders-0/00000000000000000000.index"),
121//!     time_index: PathBuf::from("/var/lib/krabka/orders-0/00000000000000000000.timeindex"),
122//!     transaction_index: None,
123//!     producer_snapshot_index: None,
124//!     leader_epoch_index: Bytes::new(),
125//! };
126//! let _custom_metadata = storage.copy_log_segment_data(&metadata, &segment)?;
127//! let bytes = storage.fetch_index(&metadata, IndexType::Offset)?;
128//! # let _ = bytes;
129//! # Ok(())
130//! # }
131//! ```
132
133#![doc(html_root_url = "https://docs.rs/krabka-remote-storage/1.1.0")]
134
135mod cache;
136pub mod diskless;
137pub mod dump;
138mod error;
139mod gcs;
140pub mod index;
141mod index_cache;
142mod inmemory;
143mod local;
144mod metadata;
145mod metadata_manager;
146mod s3;
147mod storage_manager;
148mod worm;
149
150#[cfg(test)]
151mod test_support;
152
153pub use {
154    dump::{PartitionDump, RlmmCacheDump},
155    error::RemoteStorageError,
156    index::{
157        AbortedTxnIndexEntry, BytePosition, LogOffset, OffsetIndexEntry, RelativeOffset,
158        TimeIndexEntry, TimestampMs, corrupt_log, end_position_for, first_batch_at_or_after,
159        first_record_at_or_after_timestamp, parse_offset_index, parse_time_index, parse_txn_index,
160        position_for_relative_offset, relative_offset_floor_for_timestamp, txn_overlaps,
161    },
162    index_cache::{
163        DEFAULT_INDEX_CACHE_TOTAL_SIZE_BYTES, IndexCacheOutcome, IndexCacheStats,
164        REMOTE_INDEX_CACHE_DIR, RemoteIndexCache,
165    },
166    inmemory::InmemoryRemoteLogMetadataManager,
167    krabka_object_store::{
168        DEFAULT_MULTIPART_CHUNK_SIZE, DEFAULT_MULTIPART_THRESHOLD, GcsConfig, ObjectStoreConfig,
169        S3Config,
170    },
171    local::LocalTieredStorage,
172    metadata::{
173        CustomMetadata, RemoteLogSegmentDetails, RemoteLogSegmentId, RemoteLogSegmentMetadata,
174        RemoteLogSegmentMetadataUpdate, RemoteLogSegmentState, RemotePartitionDeleteMetadata,
175        RemotePartitionDeleteState, TopicIdPartition,
176    },
177    metadata_manager::RemoteLogMetadataManager,
178    s3::S3RemoteStorage,
179    storage_manager::{
180        IndexType, LOG_FILE_SUFFIX, LogSegmentData, PartitionDirName, RemoteStorageManager,
181        SegmentFileName, decode_kafka_uuid, kafka_uuid, parse_partition_dir_name,
182        parse_segment_file_name, partition_dir_name, segment_file_name,
183    },
184    worm::{
185        ArchiveVerifyReport, AuthenticatedArchive, ChainHead, ChainStamp, EpochId, EpochSpan,
186        HexBytes, MANIFEST_BODY_DOMAIN, MANIFEST_DOMAIN, MANIFEST_FORMAT_VERSION, MANIFEST_SUFFIX,
187        MAX_MANIFEST_BYTES, ManifestBody, ManifestSeq, ManifestSignature, ObjectEntry,
188        ObjectProtectionReport, OffsetGap, PartitionVerifyReport, SealedManifest, SegmentIdentity,
189        SegmentManifest, Sha256Digest, TrustedManifestKeys, VerifyBreak, VerifyDepth,
190        VerifyRequest, WORM_CHAIN_RECORD_VERSION, WormArchiver, WormChainRecord, WormConfig,
191        WormError, authenticate_archive, canonical_manifest_bytes, manifest_head,
192        manifest_signing_bytes, next_chain_stamp, verify_archive, verify_manifest_signature,
193    },
194};
195
196#[cfg(test)]
197extern crate self as krabka_remote_storage;