1use {
6 crate::{
7 cache::{RemoteLogMetadataCache, sort_by_start_offset},
8 dump::{PartitionDump, RlmmCacheDump},
9 error::RemoteStorageError,
10 metadata::{
11 RemoteLogSegmentMetadata, RemoteLogSegmentMetadataUpdate,
12 RemotePartitionDeleteMetadata, RemotePartitionDeleteState, TopicIdPartition,
13 },
14 metadata_manager::RemoteLogMetadataManager,
15 },
16 krabka_ids::LeaderEpoch,
17 std::{
18 collections::HashMap,
19 sync::{Mutex, PoisonError},
20 },
21};
22
23#[derive(Debug, Default)]
30pub struct InmemoryRemoteLogMetadataManager {
31 partitions: Mutex<HashMap<TopicIdPartition, RemoteLogMetadataCache>>,
32}
33
34impl InmemoryRemoteLogMetadataManager {
35 #[must_use]
37 pub fn new() -> Self { Self::default() }
38
39 fn list_segments(
40 &self,
41 topic_id_partition: &TopicIdPartition,
42 query: impl FnOnce(&RemoteLogMetadataCache) -> Vec<RemoteLogSegmentMetadata>,
43 ) -> Vec<RemoteLogSegmentMetadata> {
44 let guard = self
45 .partitions
46 .lock()
47 .unwrap_or_else(PoisonError::into_inner);
48 guard.get(topic_id_partition).map_or_default(query)
49 }
50
51 #[must_use]
55 pub fn export(&self) -> RlmmCacheDump {
56 let mut partitions: Vec<PartitionDump> = self
57 .partitions
58 .lock()
59 .unwrap_or_else(PoisonError::into_inner)
60 .iter()
61 .map(|(tp, cache)| PartitionDump {
62 topic_id_partition: tp.clone(),
63 segments: cache.dump_segments(),
64 delete_state: cache.delete_state(),
65 })
66 .collect();
67 partitions.sort_by(|a, b| {
69 (
70 a.topic_id_partition.topic_id,
71 a.topic_id_partition.partition,
72 )
73 .cmp(&(
74 b.topic_id_partition.topic_id,
75 b.topic_id_partition.partition,
76 ))
77 });
78 for p in &mut partitions {
81 sort_by_start_offset(&mut p.segments);
82 }
83 RlmmCacheDump { partitions }
84 }
85
86 pub fn import(&self, dump: RlmmCacheDump) {
91 let mut guard = self
92 .partitions
93 .lock()
94 .unwrap_or_else(PoisonError::into_inner);
95 for p in dump.partitions {
96 let cache = guard.entry(p.topic_id_partition).or_default();
97 cache.seed(p.segments, p.delete_state);
98 }
99 drop(guard);
100 }
101}
102
103impl RemoteLogMetadataManager for InmemoryRemoteLogMetadataManager {
104 fn add_remote_log_segment_metadata(
105 &self,
106 metadata: RemoteLogSegmentMetadata,
107 ) -> Result<(), RemoteStorageError> {
108 let tp = metadata.remote_log_segment_id().topic_id_partition.clone();
109 let mut guard = self
110 .partitions
111 .lock()
112 .unwrap_or_else(PoisonError::into_inner);
113 guard.entry(tp).or_default().add(metadata)
114 }
115
116 fn update_remote_log_segment_metadata(
117 &self,
118 update: RemoteLogSegmentMetadataUpdate,
119 ) -> Result<(), RemoteStorageError> {
120 let tp = update.remote_log_segment_id.topic_id_partition.clone();
121 let mut guard = self
122 .partitions
123 .lock()
124 .unwrap_or_else(PoisonError::into_inner);
125 match guard.get_mut(&tp) {
126 Some(cache) => cache.update(&update),
127 None => Err(RemoteStorageError::SegmentNotFound(
128 update.remote_log_segment_id,
129 )),
130 }
131 }
132
133 fn remote_log_segment_metadata(
134 &self,
135 topic_id_partition: &TopicIdPartition,
136 leader_epoch: LeaderEpoch,
137 offset: i64,
138 ) -> Result<Option<RemoteLogSegmentMetadata>, RemoteStorageError> {
139 let guard = self
140 .partitions
141 .lock()
142 .unwrap_or_else(PoisonError::into_inner);
143 Ok(guard
144 .get(topic_id_partition)
145 .and_then(|c| c.segment_for(leader_epoch, offset)))
146 }
147
148 fn highest_offset_for_epoch(
149 &self,
150 topic_id_partition: &TopicIdPartition,
151 leader_epoch: LeaderEpoch,
152 ) -> Result<Option<i64>, RemoteStorageError> {
153 let guard = self
154 .partitions
155 .lock()
156 .unwrap_or_else(PoisonError::into_inner);
157 Ok(guard
158 .get(topic_id_partition)
159 .and_then(|c| c.highest_offset_for_epoch(leader_epoch)))
160 }
161
162 fn list_remote_log_segments(
163 &self,
164 topic_id_partition: &TopicIdPartition,
165 ) -> Result<Vec<RemoteLogSegmentMetadata>, RemoteStorageError> {
166 Ok(self.list_segments(topic_id_partition, RemoteLogMetadataCache::list))
167 }
168
169 fn list_remote_log_segments_by_epoch(
170 &self,
171 topic_id_partition: &TopicIdPartition,
172 leader_epoch: LeaderEpoch,
173 ) -> Result<Vec<RemoteLogSegmentMetadata>, RemoteStorageError> {
174 Ok(self.list_segments(topic_id_partition, |c| c.list_by_epoch(leader_epoch)))
175 }
176
177 fn put_remote_partition_delete_metadata(
178 &self,
179 metadata: RemotePartitionDeleteMetadata,
180 ) -> Result<(), RemoteStorageError> {
181 let mut guard = self
182 .partitions
183 .lock()
184 .unwrap_or_else(PoisonError::into_inner);
185 let cache = guard
186 .entry(metadata.topic_id_partition.clone())
187 .or_default();
188 let from = cache.delete_state();
189 if !RemotePartitionDeleteState::is_valid_transition(from, metadata.state) {
190 return Err(RemoteStorageError::InvalidPartitionDeleteTransition {
191 tp: metadata.topic_id_partition,
192 from,
193 to: metadata.state,
194 });
195 }
196 cache.set_delete_state(metadata.state);
197 drop(guard);
198 Ok(())
199 }
200
201 fn caught_up(
203 &self,
204 _topic_id_partition: &TopicIdPartition,
205 ) -> Result<bool, RemoteStorageError> {
206 Ok(true)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use {
213 super::*,
214 crate::metadata::{RemotePartitionDeleteState, test_topic_partition as tp},
215 assert2::{assert, check},
216 uuid::Uuid,
217 };
218
219 krabka_macros::remote_segment_fixtures!(started, finish, crate, end);
220
221 krabka_macros::remote_metadata_missing!(check_missing_partition, crate);
222
223 krabka_macros::remote_segment_check!(check_finished_segment);
224
225 fn manager_with_finished_segment() -> InmemoryRemoteLogMetadataManager {
226 let manager = InmemoryRemoteLogMetadataManager::new();
227 manager
228 .add_remote_log_segment_metadata(started(10, 0, 99))
229 .unwrap();
230 manager
231 .update_remote_log_segment_metadata(finish(10))
232 .unwrap();
233 manager
234 }
235
236 #[test]
237 fn add_finish_query_round_trip() {
238 let m = manager_with_finished_segment();
239
240 check_finished_segment(&m, &tp());
241 }
242
243 #[test]
244 fn caught_up_is_always_true() {
245 assert!(
246 InmemoryRemoteLogMetadataManager::new()
247 .caught_up(&tp())
248 .unwrap()
249 );
250 }
251
252 #[test]
253 fn query_unknown_partition_is_none_not_error() {
254 let m = InmemoryRemoteLogMetadataManager::new();
255 let other = TopicIdPartition::new(Uuid::from_u128(999), "nope", 0);
256 check_missing_partition(&m, &other);
257 }
258
259 #[test]
260 fn list_by_epoch_returns_added_segment() {
261 let m = manager_with_finished_segment();
262 let listed = m
263 .list_remote_log_segments_by_epoch(&tp(), LeaderEpoch(0))
264 .unwrap();
265 assert!(listed.len() == 1);
266 assert!(listed[0].remote_log_segment_id().id == Uuid::from_u128(10));
267 }
268
269 #[test]
270 fn inmemory_read_outcomes_are_some_none_never_not_ready() {
271 let m = InmemoryRemoteLogMetadataManager::new();
272 m.add_remote_log_segment_metadata(started(10, 0, 99))
273 .unwrap();
274 m.update_remote_log_segment_metadata(finish(10)).unwrap();
275
276 assert!(matches!(
278 m.remote_log_segment_metadata(&tp(), LeaderEpoch(0), 42),
279 Ok(Some(_))
280 ));
281 assert!(matches!(
283 m.remote_log_segment_metadata(&tp(), LeaderEpoch(0), 10_000),
284 Ok(None)
285 ));
286 let other = TopicIdPartition::new(Uuid::from_u128(999), "nope", 0);
288 let got = m.remote_log_segment_metadata(&other, LeaderEpoch(0), 0);
289 assert!(matches!(got, Ok(None)));
290 assert!(
291 !matches!(got, Err(RemoteStorageError::NotReady { .. })),
292 "in-memory manager has no consumer lag; never NotReady"
293 );
294 }
295
296 #[test]
297 fn update_before_add_errors() {
298 let m = InmemoryRemoteLogMetadataManager::new();
299 let err = m
300 .update_remote_log_segment_metadata(finish(10))
301 .unwrap_err();
302 assert!(matches!(err, RemoteStorageError::SegmentNotFound(_)));
303 }
304
305 #[test]
306 fn list_returns_all_states_ordered() {
307 let m = InmemoryRemoteLogMetadataManager::new();
308 m.add_remote_log_segment_metadata(started(11, 100, 199))
309 .unwrap();
310 m.add_remote_log_segment_metadata(started(10, 0, 99))
311 .unwrap();
312 m.update_remote_log_segment_metadata(finish(10)).unwrap();
313 let listed = m.list_remote_log_segments(&tp()).unwrap();
314 let start_offsets: Vec<i64> = listed
315 .iter()
316 .map(RemoteLogSegmentMetadata::start_offset)
317 .collect();
318 assert!(start_offsets == [0, 100]);
319 }
320
321 #[test]
322 fn partition_delete_lifecycle() {
323 let m = InmemoryRemoteLogMetadataManager::new();
324 for state in [
325 RemotePartitionDeleteState::DeletePartitionMarked,
326 RemotePartitionDeleteState::DeletePartitionStarted,
327 RemotePartitionDeleteState::DeletePartitionFinished,
328 ] {
329 m.put_remote_partition_delete_metadata(RemotePartitionDeleteMetadata {
330 topic_id_partition: tp(),
331 state,
332 event_timestamp_ms: 500,
333 broker_id: 1,
334 })
335 .unwrap();
336 }
337 }
338
339 #[test]
340 fn export_then_import_reproduces_cache() {
341 let m = InmemoryRemoteLogMetadataManager::new();
342 m.add_remote_log_segment_metadata(started(10, 0, 99))
343 .unwrap();
344 m.add_remote_log_segment_metadata(started(11, 100, 199))
345 .unwrap();
346 m.update_remote_log_segment_metadata(finish(10)).unwrap();
347 m.put_remote_partition_delete_metadata(RemotePartitionDeleteMetadata {
348 topic_id_partition: tp(),
349 state: RemotePartitionDeleteState::DeletePartitionMarked,
350 event_timestamp_ms: 500,
351 broker_id: 1,
352 })
353 .unwrap();
354
355 let dump = m.export();
356 let restored = InmemoryRemoteLogMetadataManager::new();
357 restored.import(dump);
358
359 let before = m.list_remote_log_segments(&tp()).unwrap();
361 let after = restored.list_remote_log_segments(&tp()).unwrap();
362 check!(before == after);
363 check!(
365 restored
366 .highest_offset_for_epoch(&tp(), LeaderEpoch(0))
367 .unwrap()
368 == Some(99)
369 );
370 check!(m.export() == restored.export());
372 }
373
374 #[test]
375 fn partition_delete_rejects_out_of_order() {
376 let m = InmemoryRemoteLogMetadataManager::new();
377 let err = m
378 .put_remote_partition_delete_metadata(RemotePartitionDeleteMetadata {
379 topic_id_partition: tp(),
380 state: RemotePartitionDeleteState::DeletePartitionFinished,
381 event_timestamp_ms: 500,
382 broker_id: 1,
383 })
384 .unwrap_err();
385 assert!(matches!(
386 err,
387 RemoteStorageError::InvalidPartitionDeleteTransition { .. }
388 ));
389 }
390}