1pub mod in_memory;
2pub mod wal;
3
4use std::borrow::Cow;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8
9use serde::Deserialize;
10use serde::Serialize;
11use ursula_shard::BucketStreamId;
12use ursula_shard::ShardPlacement;
13use ursula_stream::ColdFlushCandidate;
14use ursula_stream::ColdGcEntry;
15use ursula_stream::StreamErrorCode;
16use ursula_stream::StreamErrorContext;
17
18use crate::command::GroupSnapshot;
19use crate::command::GroupWriteCommand;
20use crate::metrics::RaftWriteManySample;
21use crate::metrics::RuntimeMetricsInner;
22use crate::request::AckColdGcResponse;
23use crate::request::AppendBatchRequest;
24use crate::request::AppendExternalRequest;
25use crate::request::AppendRequest;
26use crate::request::AppendResponse;
27use crate::request::BootstrapStreamRequest;
28use crate::request::BootstrapStreamResponse;
29use crate::request::CloseStreamRequest;
30use crate::request::CloseStreamResponse;
31use crate::request::ColdHotBacklog;
32use crate::request::ColdWriteAdmission;
33use crate::request::CreateStreamExternalRequest;
34use crate::request::CreateStreamRequest;
35use crate::request::CreateStreamResponse;
36use crate::request::DeleteSnapshotRequest;
37use crate::request::DeleteStreamRequest;
38use crate::request::DeleteStreamResponse;
39use crate::request::FlushColdRequest;
40use crate::request::FlushColdResponse;
41use crate::request::ForkRefResponse;
42use crate::request::GetStreamAttrsRequest;
43use crate::request::GetStreamAttrsResponse;
44use crate::request::GroupReadStreamParts;
45use crate::request::HeadStreamRequest;
46use crate::request::HeadStreamResponse;
47use crate::request::PlanColdFlushRequest;
48use crate::request::PlanGroupColdFlushRequest;
49use crate::request::PublishSnapshotRequest;
50use crate::request::PublishSnapshotResponse;
51use crate::request::ReadSnapshotRequest;
52use crate::request::ReadSnapshotResponse;
53use crate::request::ReadStreamRequest;
54use crate::request::ReadStreamResponse;
55use crate::request::TouchStreamAccessResponse;
56use crate::request::UpdateStreamAttrsRequest;
57use crate::request::UpdateStreamAttrsResponse;
58
59pub type GroupAppendFuture<'a> =
60 Pin<Box<dyn Future<Output = Result<AppendResponse, GroupEngineError>> + Send + 'a>>;
61pub type GroupAppendBatchFuture<'a> =
62 Pin<Box<dyn Future<Output = Result<GroupAppendBatchResponse, GroupEngineError>> + Send + 'a>>;
63pub type GroupFlushColdFuture<'a> =
64 Pin<Box<dyn Future<Output = Result<FlushColdResponse, GroupEngineError>> + Send + 'a>>;
65pub type GroupPlanColdFlushFuture<'a> =
66 Pin<Box<dyn Future<Output = Result<Option<ColdFlushCandidate>, GroupEngineError>> + Send + 'a>>;
67pub type GroupPlanNextColdFlushFuture<'a> =
68 Pin<Box<dyn Future<Output = Result<Option<ColdFlushCandidate>, GroupEngineError>> + Send + 'a>>;
69pub type GroupPlanNextColdFlushBatchFuture<'a> =
70 Pin<Box<dyn Future<Output = Result<Vec<ColdFlushCandidate>, GroupEngineError>> + Send + 'a>>;
71pub type GroupColdHotBacklogFuture<'a> =
72 Pin<Box<dyn Future<Output = Result<ColdHotBacklog, GroupEngineError>> + Send + 'a>>;
73pub type GroupCreateStreamFuture<'a> =
74 Pin<Box<dyn Future<Output = Result<CreateStreamResponse, GroupEngineError>> + Send + 'a>>;
75pub type GroupHeadStreamFuture<'a> =
76 Pin<Box<dyn Future<Output = Result<HeadStreamResponse, GroupEngineError>> + Send + 'a>>;
77pub type GroupGetStreamAttrsFuture<'a> =
78 Pin<Box<dyn Future<Output = Result<GetStreamAttrsResponse, GroupEngineError>> + Send + 'a>>;
79pub type GroupReadStreamFuture<'a> =
80 Pin<Box<dyn Future<Output = Result<ReadStreamResponse, GroupEngineError>> + Send + 'a>>;
81pub type GroupReadStreamPartsFuture<'a> =
82 Pin<Box<dyn Future<Output = Result<GroupReadStreamParts, GroupEngineError>> + Send + 'a>>;
83pub type GroupRequireLiveReadOwnerFuture<'a> =
84 Pin<Box<dyn Future<Output = Result<(), GroupEngineError>> + Send + 'a>>;
85pub type GroupPublishSnapshotFuture<'a> =
86 Pin<Box<dyn Future<Output = Result<PublishSnapshotResponse, GroupEngineError>> + Send + 'a>>;
87pub type GroupReadSnapshotFuture<'a> =
88 Pin<Box<dyn Future<Output = Result<ReadSnapshotResponse, GroupEngineError>> + Send + 'a>>;
89pub type GroupDeleteSnapshotFuture<'a> =
90 Pin<Box<dyn Future<Output = Result<(), GroupEngineError>> + Send + 'a>>;
91pub type GroupBootstrapStreamFuture<'a> =
92 Pin<Box<dyn Future<Output = Result<BootstrapStreamResponse, GroupEngineError>> + Send + 'a>>;
93pub type GroupTouchStreamAccessFuture<'a> =
94 Pin<Box<dyn Future<Output = Result<TouchStreamAccessResponse, GroupEngineError>> + Send + 'a>>;
95pub type GroupUpdateStreamAttrsFuture<'a> =
96 Pin<Box<dyn Future<Output = Result<UpdateStreamAttrsResponse, GroupEngineError>> + Send + 'a>>;
97pub type GroupCloseStreamFuture<'a> =
98 Pin<Box<dyn Future<Output = Result<CloseStreamResponse, GroupEngineError>> + Send + 'a>>;
99pub type GroupDeleteStreamFuture<'a> =
100 Pin<Box<dyn Future<Output = Result<DeleteStreamResponse, GroupEngineError>> + Send + 'a>>;
101pub type GroupAckColdGcFuture<'a> =
102 Pin<Box<dyn Future<Output = Result<AckColdGcResponse, GroupEngineError>> + Send + 'a>>;
103pub type GroupPlanColdGcFuture<'a> =
104 Pin<Box<dyn Future<Output = Result<Vec<ColdGcEntry>, GroupEngineError>> + Send + 'a>>;
105pub type GroupForkRefFuture<'a> =
106 Pin<Box<dyn Future<Output = Result<ForkRefResponse, GroupEngineError>> + Send + 'a>>;
107pub type GroupSnapshotFuture<'a> =
108 Pin<Box<dyn Future<Output = Result<GroupSnapshot, GroupEngineError>> + Send + 'a>>;
109pub type GroupInstallSnapshotFuture<'a> =
110 Pin<Box<dyn Future<Output = Result<(), GroupEngineError>> + Send + 'a>>;
111pub type GroupShutdownFuture<'a> =
112 Pin<Box<dyn Future<Output = Result<(), GroupEngineError>> + Send + 'a>>;
113pub type GroupWriteBatchFuture<'a> = Pin<
114 Box<
115 dyn Future<
116 Output = Result<
117 Vec<Result<GroupWriteResponse, GroupEngineError>>,
118 GroupEngineError,
119 >,
120 > + Send
121 + 'a,
122 >,
123>;
124pub type GroupEngineCreateFuture<'a> =
125 Pin<Box<dyn Future<Output = Result<Box<dyn GroupEngine>, GroupEngineError>> + Send + 'a>>;
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct GroupAppendBatchResponse {
129 pub placement: ShardPlacement,
130 pub items: Vec<Result<AppendResponse, GroupEngineError>>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub enum GroupWriteResponse {
135 CreateStream(CreateStreamResponse),
136 Append(AppendResponse),
137 AppendBatch(GroupAppendBatchResponse),
138 PublishSnapshot(PublishSnapshotResponse),
139 TouchStreamAccess(TouchStreamAccessResponse),
140 UpdateStreamAttrs(UpdateStreamAttrsResponse),
141 AddForkRef(ForkRefResponse),
142 ReleaseForkRef(ForkRefResponse),
143 FlushCold(FlushColdResponse),
144 CloseStream(CloseStreamResponse),
145 DeleteStream(DeleteStreamResponse),
146 AckColdGc(AckColdGcResponse),
147 Batch(Vec<Result<GroupWriteResponse, GroupEngineError>>),
148}
149
150pub trait GroupEngine: Send + 'static {
151 fn accepts_local_writes(&self) -> bool {
152 true
153 }
154
155 fn create_stream<'a>(
156 &'a mut self,
157 request: CreateStreamRequest,
158 placement: ShardPlacement,
159 ) -> GroupCreateStreamFuture<'a>;
160
161 fn create_stream_external<'a>(
162 &'a mut self,
163 request: CreateStreamExternalRequest,
164 _placement: ShardPlacement,
165 ) -> GroupCreateStreamFuture<'a> {
166 Box::pin(async move {
167 Err(GroupEngineError::new(format!(
168 "external stream create is not supported for stream '{}'",
169 request.stream_id
170 )))
171 })
172 }
173
174 fn head_stream<'a>(
175 &'a mut self,
176 request: HeadStreamRequest,
177 placement: ShardPlacement,
178 ) -> GroupHeadStreamFuture<'a>;
179
180 fn get_stream_attrs<'a>(
181 &'a mut self,
182 request: GetStreamAttrsRequest,
183 _placement: ShardPlacement,
184 ) -> GroupGetStreamAttrsFuture<'a> {
185 Box::pin(async move {
186 Err(GroupEngineError::new(format!(
187 "stream attrs read is not supported for stream '{}'",
188 request.stream_id
189 )))
190 })
191 }
192
193 fn read_stream<'a>(
194 &'a mut self,
195 request: ReadStreamRequest,
196 placement: ShardPlacement,
197 ) -> GroupReadStreamFuture<'a>;
198
199 fn read_stream_parts<'a>(
200 &'a mut self,
201 request: ReadStreamRequest,
202 placement: ShardPlacement,
203 ) -> GroupReadStreamPartsFuture<'a> {
204 Box::pin(async move {
205 let response = self.read_stream(request, placement).await?;
206 Ok(GroupReadStreamParts::from_response(response))
207 })
208 }
209
210 fn require_local_live_read_owner<'a>(
211 &'a mut self,
212 _placement: ShardPlacement,
213 ) -> GroupRequireLiveReadOwnerFuture<'a> {
214 Box::pin(async { Ok(()) })
215 }
216
217 fn publish_snapshot<'a>(
218 &'a mut self,
219 request: PublishSnapshotRequest,
220 _placement: ShardPlacement,
221 ) -> GroupPublishSnapshotFuture<'a> {
222 Box::pin(async move {
223 Err(GroupEngineError::new(format!(
224 "snapshot publish is not supported for stream '{}'",
225 request.stream_id
226 )))
227 })
228 }
229
230 fn read_snapshot<'a>(
231 &'a mut self,
232 request: ReadSnapshotRequest,
233 _placement: ShardPlacement,
234 ) -> GroupReadSnapshotFuture<'a> {
235 Box::pin(async move {
236 Err(GroupEngineError::new(format!(
237 "snapshot read is not supported for stream '{}'",
238 request.stream_id
239 )))
240 })
241 }
242
243 fn delete_snapshot<'a>(
244 &'a mut self,
245 request: DeleteSnapshotRequest,
246 _placement: ShardPlacement,
247 ) -> GroupDeleteSnapshotFuture<'a> {
248 Box::pin(async move {
249 Err(GroupEngineError::new(format!(
250 "snapshot delete is not supported for stream '{}'",
251 request.stream_id
252 )))
253 })
254 }
255
256 fn bootstrap_stream<'a>(
257 &'a mut self,
258 request: BootstrapStreamRequest,
259 _placement: ShardPlacement,
260 ) -> GroupBootstrapStreamFuture<'a> {
261 Box::pin(async move {
262 Err(GroupEngineError::new(format!(
263 "bootstrap is not supported for stream '{}'",
264 request.stream_id
265 )))
266 })
267 }
268
269 fn touch_stream_access<'a>(
270 &'a mut self,
271 stream_id: BucketStreamId,
272 now_ms: u64,
273 renew_ttl: bool,
274 placement: ShardPlacement,
275 ) -> GroupTouchStreamAccessFuture<'a>;
276
277 fn update_stream_attrs<'a>(
278 &'a mut self,
279 request: UpdateStreamAttrsRequest,
280 _placement: ShardPlacement,
281 ) -> GroupUpdateStreamAttrsFuture<'a> {
282 Box::pin(async move {
283 Err(GroupEngineError::new(format!(
284 "stream attrs update is not supported for stream '{}'",
285 request.stream_id
286 )))
287 })
288 }
289
290 fn add_fork_ref<'a>(
291 &'a mut self,
292 stream_id: BucketStreamId,
293 now_ms: u64,
294 placement: ShardPlacement,
295 ) -> GroupForkRefFuture<'a>;
296
297 fn release_fork_ref<'a>(
298 &'a mut self,
299 stream_id: BucketStreamId,
300 placement: ShardPlacement,
301 ) -> GroupForkRefFuture<'a>;
302
303 fn close_stream<'a>(
304 &'a mut self,
305 request: CloseStreamRequest,
306 placement: ShardPlacement,
307 ) -> GroupCloseStreamFuture<'a>;
308
309 fn delete_stream<'a>(
310 &'a mut self,
311 request: DeleteStreamRequest,
312 placement: ShardPlacement,
313 ) -> GroupDeleteStreamFuture<'a>;
314
315 fn ack_cold_gc<'a>(
318 &'a mut self,
319 _up_to_seq: u64,
320 _placement: ShardPlacement,
321 ) -> GroupAckColdGcFuture<'a> {
322 Box::pin(async { Err(GroupEngineError::new("cold GC ack is not supported")) })
323 }
324
325 fn plan_cold_gc<'a>(
328 &'a mut self,
329 _max: usize,
330 _placement: ShardPlacement,
331 ) -> GroupPlanColdGcFuture<'a> {
332 Box::pin(async { Ok(Vec::new()) })
333 }
334
335 fn append<'a>(
336 &'a mut self,
337 request: AppendRequest,
338 placement: ShardPlacement,
339 ) -> GroupAppendFuture<'a>;
340
341 fn append_external<'a>(
342 &'a mut self,
343 request: AppendExternalRequest,
344 _placement: ShardPlacement,
345 ) -> GroupAppendFuture<'a> {
346 Box::pin(async move {
347 Err(GroupEngineError::new(format!(
348 "external append is not supported for stream '{}'",
349 request.stream_id
350 )))
351 })
352 }
353
354 fn append_batch<'a>(
355 &'a mut self,
356 request: AppendBatchRequest,
357 placement: ShardPlacement,
358 ) -> GroupAppendBatchFuture<'a>;
359
360 fn create_stream_with_cold_admission<'a>(
361 &'a mut self,
362 request: CreateStreamRequest,
363 placement: ShardPlacement,
364 _admission: ColdWriteAdmission,
365 ) -> GroupCreateStreamFuture<'a> {
366 self.create_stream(request, placement)
367 }
368
369 fn append_with_cold_admission<'a>(
370 &'a mut self,
371 request: AppendRequest,
372 placement: ShardPlacement,
373 _admission: ColdWriteAdmission,
374 ) -> GroupAppendFuture<'a> {
375 self.append(request, placement)
376 }
377
378 fn append_batch_with_cold_admission<'a>(
379 &'a mut self,
380 request: AppendBatchRequest,
381 placement: ShardPlacement,
382 _admission: ColdWriteAdmission,
383 ) -> GroupAppendBatchFuture<'a> {
384 self.append_batch(request, placement)
385 }
386
387 fn append_batch_many_with_cold_admission<'a>(
388 &'a mut self,
389 requests: Vec<AppendBatchRequest>,
390 placement: ShardPlacement,
391 admission: ColdWriteAdmission,
392 ) -> GroupWriteBatchFuture<'a> {
393 Box::pin(async move {
394 let mut responses = Vec::with_capacity(requests.len());
395 for request in requests {
396 let response = self
397 .append_batch_with_cold_admission(request, placement, admission)
398 .await
399 .map(GroupWriteResponse::AppendBatch);
400 responses.push(response);
401 }
402 Ok(responses)
403 })
404 }
405
406 fn flush_cold<'a>(
407 &'a mut self,
408 request: FlushColdRequest,
409 _placement: ShardPlacement,
410 ) -> GroupFlushColdFuture<'a> {
411 Box::pin(async move {
412 Err(GroupEngineError::new(format!(
413 "cold flush is not supported for stream '{}'",
414 request.stream_id
415 )))
416 })
417 }
418
419 fn plan_cold_flush<'a>(
420 &'a mut self,
421 request: PlanColdFlushRequest,
422 _placement: ShardPlacement,
423 ) -> GroupPlanColdFlushFuture<'a> {
424 Box::pin(async move {
425 Err(GroupEngineError::new(format!(
426 "cold flush planning is not supported for stream '{}'",
427 request.stream_id
428 )))
429 })
430 }
431
432 fn plan_next_cold_flush<'a>(
433 &'a mut self,
434 _request: PlanGroupColdFlushRequest,
435 _placement: ShardPlacement,
436 ) -> GroupPlanNextColdFlushFuture<'a> {
437 Box::pin(async move {
438 Err(GroupEngineError::new(
439 "group cold flush planning is not supported",
440 ))
441 })
442 }
443
444 fn plan_next_cold_flush_batch<'a>(
445 &'a mut self,
446 request: PlanGroupColdFlushRequest,
447 placement: ShardPlacement,
448 max_candidates: usize,
449 ) -> GroupPlanNextColdFlushBatchFuture<'a> {
450 Box::pin(async move {
451 match self.plan_next_cold_flush(request, placement).await? {
452 Some(candidate) if max_candidates > 0 => Ok(vec![candidate]),
453 _ => Ok(Vec::new()),
454 }
455 })
456 }
457
458 fn cold_hot_backlog<'a>(
459 &'a mut self,
460 stream_id: BucketStreamId,
461 _placement: ShardPlacement,
462 ) -> GroupColdHotBacklogFuture<'a> {
463 Box::pin(async move {
464 Err(GroupEngineError::new(format!(
465 "cold hot backlog is not supported for stream '{stream_id}'"
466 )))
467 })
468 }
469
470 fn snapshot<'a>(&'a mut self, placement: ShardPlacement) -> GroupSnapshotFuture<'a>;
471
472 fn install_snapshot<'a>(
473 &'a mut self,
474 snapshot: GroupSnapshot,
475 ) -> GroupInstallSnapshotFuture<'a>;
476
477 fn shutdown<'a>(&'a mut self) -> GroupShutdownFuture<'a> {
478 Box::pin(async { Ok(()) })
479 }
480
481 fn write_batch<'a>(
482 &'a mut self,
483 commands: Vec<GroupWriteCommand>,
484 placement: ShardPlacement,
485 ) -> GroupWriteBatchFuture<'a> {
486 Box::pin(async move {
487 let mut responses = Vec::with_capacity(commands.len());
488 for command in commands {
489 let response = match command {
490 GroupWriteCommand::CreateStream {
491 stream_id,
492 content_type,
493 initial_payload,
494 close_after,
495 stream_seq,
496 producer,
497 stream_ttl_seconds,
498 stream_expires_at_ms,
499 forked_from,
500 fork_offset,
501 attrs,
502 now_ms,
503 } => self
504 .create_stream(
505 CreateStreamRequest {
506 stream_id,
507 content_type,
508 content_type_explicit: true,
509 initial_payload,
510 close_after,
511 stream_seq,
512 producer,
513 stream_ttl_seconds,
514 stream_expires_at_ms,
515 forked_from,
516 fork_offset,
517 attrs,
518 now_ms,
519 },
520 placement,
521 )
522 .await
523 .map(GroupWriteResponse::CreateStream),
524 GroupWriteCommand::CreateExternal {
525 stream_id,
526 content_type,
527 initial_payload,
528 close_after,
529 stream_seq,
530 producer,
531 stream_ttl_seconds,
532 stream_expires_at_ms,
533 forked_from,
534 fork_offset,
535 attrs,
536 now_ms,
537 } => self
538 .create_stream_external(
539 CreateStreamExternalRequest {
540 stream_id,
541 content_type,
542 initial_payload,
543 close_after,
544 stream_seq,
545 producer,
546 stream_ttl_seconds,
547 stream_expires_at_ms,
548 forked_from,
549 fork_offset,
550 attrs,
551 now_ms,
552 },
553 placement,
554 )
555 .await
556 .map(GroupWriteResponse::CreateStream),
557 GroupWriteCommand::Append {
558 stream_id,
559 content_type,
560 payload,
561 close_after,
562 stream_seq,
563 producer,
564 now_ms,
565 } => self
566 .append(
567 AppendRequest {
568 stream_id,
569 content_type,
570 payload,
571 close_after,
572 stream_seq,
573 producer,
574 now_ms,
575 },
576 placement,
577 )
578 .await
579 .map(GroupWriteResponse::Append),
580 GroupWriteCommand::AppendExternal {
581 stream_id,
582 content_type,
583 payload,
584 close_after,
585 stream_seq,
586 producer,
587 now_ms,
588 } => self
589 .append_external(
590 AppendExternalRequest {
591 stream_id,
592 content_type,
593 payload,
594 close_after,
595 stream_seq,
596 producer,
597 now_ms,
598 },
599 placement,
600 )
601 .await
602 .map(GroupWriteResponse::Append),
603 GroupWriteCommand::AppendBatch {
604 stream_id,
605 content_type,
606 payloads,
607 producer,
608 now_ms,
609 } => self
610 .append_batch(
611 AppendBatchRequest {
612 stream_id,
613 content_type,
614 payloads,
615 producer,
616 now_ms,
617 },
618 placement,
619 )
620 .await
621 .map(GroupWriteResponse::AppendBatch),
622 GroupWriteCommand::PublishSnapshot {
623 stream_id,
624 snapshot_offset,
625 content_type,
626 payload,
627 now_ms,
628 } => self
629 .publish_snapshot(
630 PublishSnapshotRequest {
631 stream_id,
632 snapshot_offset,
633 content_type,
634 payload,
635 now_ms,
636 },
637 placement,
638 )
639 .await
640 .map(GroupWriteResponse::PublishSnapshot),
641 GroupWriteCommand::TouchStreamAccess {
642 stream_id,
643 now_ms,
644 renew_ttl,
645 } => self
646 .touch_stream_access(stream_id, now_ms, renew_ttl, placement)
647 .await
648 .map(GroupWriteResponse::TouchStreamAccess),
649 GroupWriteCommand::UpdateStreamAttrs {
650 stream_id,
651 attrs,
652 now_ms,
653 } => self
654 .update_stream_attrs(
655 UpdateStreamAttrsRequest {
656 stream_id,
657 attrs,
658 now_ms,
659 },
660 placement,
661 )
662 .await
663 .map(GroupWriteResponse::UpdateStreamAttrs),
664 GroupWriteCommand::AddForkRef { stream_id, now_ms } => self
665 .add_fork_ref(stream_id, now_ms, placement)
666 .await
667 .map(GroupWriteResponse::AddForkRef),
668 GroupWriteCommand::ReleaseForkRef { stream_id } => self
669 .release_fork_ref(stream_id, placement)
670 .await
671 .map(GroupWriteResponse::ReleaseForkRef),
672 GroupWriteCommand::FlushCold { stream_id, chunk } => self
673 .flush_cold(FlushColdRequest { stream_id, chunk }, placement)
674 .await
675 .map(GroupWriteResponse::FlushCold),
676 GroupWriteCommand::CloseStream {
677 stream_id,
678 stream_seq,
679 producer,
680 now_ms,
681 } => self
682 .close_stream(
683 CloseStreamRequest {
684 stream_id,
685 stream_seq,
686 producer,
687 now_ms,
688 },
689 placement,
690 )
691 .await
692 .map(GroupWriteResponse::CloseStream),
693 GroupWriteCommand::DeleteStream { stream_id } => self
694 .delete_stream(DeleteStreamRequest { stream_id }, placement)
695 .await
696 .map(GroupWriteResponse::DeleteStream),
697 GroupWriteCommand::AckColdGc { up_to_seq } => self
698 .ack_cold_gc(up_to_seq, placement)
699 .await
700 .map(GroupWriteResponse::AckColdGc),
701 GroupWriteCommand::Batch { commands } => self
702 .write_batch(commands, placement)
703 .await
704 .map(GroupWriteResponse::Batch),
705 };
706 responses.push(response);
707 }
708 Ok(responses)
709 })
710 }
711}
712
713pub trait GroupEngineFactory: Send + Sync + 'static {
714 fn hosts_group(&self, _placement: ShardPlacement) -> bool {
715 true
716 }
717
718 fn create<'a>(
719 &'a self,
720 placement: ShardPlacement,
721 metrics: GroupEngineMetrics,
722 ) -> GroupEngineCreateFuture<'a>;
723}
724
725#[derive(Debug, Clone)]
726pub struct GroupEngineMetrics {
727 pub(crate) inner: Arc<RuntimeMetricsInner>,
728}
729
730impl GroupEngineMetrics {
731 pub fn record_wal_batch(
732 &self,
733 placement: ShardPlacement,
734 record_count: usize,
735 write_ns: u64,
736 sync_ns: u64,
737 ) {
738 self.inner.record_wal_batch(
739 placement.core_id,
740 placement.raft_group_id,
741 u64::try_from(record_count).expect("record count fits u64"),
742 write_ns,
743 sync_ns,
744 );
745 }
746
747 pub fn record_raft_write_many(
748 &self,
749 placement: ShardPlacement,
750 command_count: usize,
751 logical_command_count: usize,
752 response_count: usize,
753 submit_ns: u64,
754 response_ns: u64,
755 ) {
756 self.inner.record_raft_write_many(
757 placement.core_id,
758 placement.raft_group_id,
759 RaftWriteManySample {
760 command_count: u64::try_from(command_count).expect("command count fits u64"),
761 logical_command_count: u64::try_from(logical_command_count)
762 .expect("logical command count fits u64"),
763 response_count: u64::try_from(response_count).expect("response count fits u64"),
764 submit_ns,
765 response_ns,
766 },
767 );
768 }
769
770 pub fn record_raft_apply_batch(
771 &self,
772 placement: ShardPlacement,
773 entry_count: usize,
774 apply_ns: u64,
775 ) {
776 self.inner.record_raft_apply_batch(
777 placement.core_id,
778 placement.raft_group_id,
779 u64::try_from(entry_count).expect("entry count fits u64"),
780 apply_ns,
781 );
782 }
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
786pub struct GroupLeaderHint {
787 pub node_id: Option<u64>,
788 pub address: Option<String>,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
792pub struct StreamEngineError {
793 message: String,
794 code: StreamErrorCode,
795 next_offset: Option<u64>,
796 #[serde(default, skip_serializing_if = "Vec::is_empty")]
797 context: Vec<StreamErrorContext>,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
805pub enum GroupInfraError {
806 Internal {
807 message: String,
808 },
809 ProtoDecode {
810 field: String,
811 },
812 ColdBackpressure {
813 stream_id: BucketStreamId,
814 before_group_hot_bytes: u64,
815 after_group_hot_bytes: u64,
816 limit: u64,
817 },
818 RaftUncommittedBackpressure {
819 current: u64,
820 incoming: u64,
821 limit: u64,
822 },
823}
824
825impl GroupInfraError {
826 pub fn internal(message: impl Into<String>) -> Self {
827 Self::Internal {
828 message: message.into(),
829 }
830 }
831
832 pub fn proto_decode(field: impl Into<String>) -> Self {
833 Self::ProtoDecode {
834 field: field.into(),
835 }
836 }
837
838 pub fn cold_backpressure(
839 stream_id: BucketStreamId,
840 before_group_hot_bytes: u64,
841 after_group_hot_bytes: u64,
842 limit: u64,
843 ) -> Self {
844 Self::ColdBackpressure {
845 stream_id,
846 before_group_hot_bytes,
847 after_group_hot_bytes,
848 limit,
849 }
850 }
851
852 pub fn raft_uncommitted_backpressure(current: u64, incoming: u64, limit: u64) -> Self {
853 Self::RaftUncommittedBackpressure {
854 current,
855 incoming,
856 limit,
857 }
858 }
859
860 pub fn message(&self) -> Cow<'_, str> {
861 match self {
862 Self::Internal { message } => Cow::Borrowed(message),
863 Self::ProtoDecode { field } => Cow::Owned(format!(
864 "ProtoDecode: protobuf raft payload missing {field}"
865 )),
866 Self::ColdBackpressure {
867 stream_id,
868 before_group_hot_bytes,
869 after_group_hot_bytes,
870 limit,
871 } => Cow::Owned(format!(
872 "ColdBackpressure: stream '{stream_id}' would raise group hot bytes from {before_group_hot_bytes} to {after_group_hot_bytes}, above limit {limit}"
873 )),
874 Self::RaftUncommittedBackpressure {
875 current,
876 incoming,
877 limit,
878 } => Cow::Owned(format!(
879 "RaftUncommittedBackpressure: group uncommitted bytes {current} plus incoming {incoming} would exceed limit {limit}"
880 )),
881 }
882 }
883
884 pub fn is_cold_backpressure(&self) -> bool {
885 matches!(self, Self::ColdBackpressure { .. })
886 }
887
888 pub fn is_backpressure(&self) -> bool {
889 matches!(
890 self,
891 Self::ColdBackpressure { .. } | Self::RaftUncommittedBackpressure { .. }
892 )
893 }
894}
895
896#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
897pub enum GroupEngineError {
898 Stream(StreamEngineError),
899 Infra(GroupInfraError),
900 ForwardToLeader {
901 message: String,
902 leader_hint: GroupLeaderHint,
903 },
904}
905
906impl GroupEngineError {
907 pub fn new(message: impl Into<String>) -> Self {
908 Self::Infra(GroupInfraError::internal(message))
909 }
910
911 pub fn cold_backpressure(
912 stream_id: BucketStreamId,
913 before_group_hot_bytes: u64,
914 after_group_hot_bytes: u64,
915 limit: u64,
916 ) -> Self {
917 Self::Infra(GroupInfraError::cold_backpressure(
918 stream_id,
919 before_group_hot_bytes,
920 after_group_hot_bytes,
921 limit,
922 ))
923 }
924
925 pub fn raft_uncommitted_backpressure(current: u64, incoming: u64, limit: u64) -> Self {
926 Self::Infra(GroupInfraError::raft_uncommitted_backpressure(
927 current, incoming, limit,
928 ))
929 }
930
931 pub fn stream(code: StreamErrorCode, message: impl Into<String>) -> Self {
932 Self::stream_with_next_offset(code, message, None)
933 }
934
935 pub fn stream_with_next_offset(
936 code: StreamErrorCode,
937 message: impl Into<String>,
938 next_offset: Option<u64>,
939 ) -> Self {
940 Self::stream_with_context(code, message, next_offset, vec![])
941 }
942
943 pub fn stream_with_context(
944 code: StreamErrorCode,
945 message: impl Into<String>,
946 next_offset: Option<u64>,
947 context: Vec<StreamErrorContext>,
948 ) -> Self {
949 Self::Stream(StreamEngineError {
950 message: format!("{code:?}: {}", message.into()),
951 code,
952 next_offset,
953 context,
954 })
955 }
956
957 pub fn stream_from_replicated(
958 message: impl Into<String>,
959 code: StreamErrorCode,
960 next_offset: Option<u64>,
961 context: Vec<StreamErrorContext>,
962 ) -> Self {
963 Self::Stream(StreamEngineError {
964 message: message.into(),
965 code,
966 next_offset,
967 context,
968 })
969 }
970
971 pub fn forward_to_leader(
972 message: impl Into<String>,
973 node_id: Option<u64>,
974 address: Option<String>,
975 ) -> Self {
976 Self::ForwardToLeader {
977 message: message.into(),
978 leader_hint: GroupLeaderHint { node_id, address },
979 }
980 }
981
982 pub fn message(&self) -> Cow<'_, str> {
983 match self {
984 Self::Stream(err) => Cow::Borrowed(&err.message),
985 Self::Infra(err) => err.message(),
986 Self::ForwardToLeader { message, .. } => Cow::Borrowed(message),
987 }
988 }
989
990 pub fn code(&self) -> Option<StreamErrorCode> {
991 match self {
992 Self::Stream(err) => Some(err.code),
993 Self::Infra(_) | Self::ForwardToLeader { .. } => None,
994 }
995 }
996
997 pub fn stream_parts(
998 &self,
999 ) -> Option<(&str, StreamErrorCode, Option<u64>, &[StreamErrorContext])> {
1000 match self {
1001 Self::Stream(err) => Some((&err.message, err.code, err.next_offset, &err.context)),
1002 Self::Infra(_) | Self::ForwardToLeader { .. } => None,
1003 }
1004 }
1005
1006 pub fn next_offset(&self) -> Option<u64> {
1007 match self {
1008 Self::Stream(err) => err.next_offset,
1009 Self::Infra(_) | Self::ForwardToLeader { .. } => None,
1010 }
1011 }
1012
1013 pub fn context(&self) -> &[StreamErrorContext] {
1014 match self {
1015 Self::Stream(err) => &err.context,
1016 Self::Infra(_) | Self::ForwardToLeader { .. } => &[],
1017 }
1018 }
1019
1020 pub fn leader_hint(&self) -> Option<&GroupLeaderHint> {
1021 match self {
1022 Self::ForwardToLeader { leader_hint, .. } => Some(leader_hint),
1023 Self::Stream(_) | Self::Infra(_) => None,
1024 }
1025 }
1026
1027 pub fn infra(&self) -> Option<&GroupInfraError> {
1028 match self {
1029 Self::Infra(err) => Some(err),
1030 Self::Stream(_) | Self::ForwardToLeader { .. } => None,
1031 }
1032 }
1033
1034 pub fn is_cold_backpressure(&self) -> bool {
1035 self.infra()
1036 .is_some_and(GroupInfraError::is_cold_backpressure)
1037 }
1038
1039 pub fn is_backpressure(&self) -> bool {
1040 self.infra().is_some_and(GroupInfraError::is_backpressure)
1041 }
1042}
1043
1044impl std::fmt::Display for GroupEngineError {
1045 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1046 f.write_str(&self.message())
1047 }
1048}
1049
1050impl std::error::Error for GroupEngineError {}