1#![expect(
16 unexpected_cfgs,
17 reason = "feature(hdfs-backend) is banned https://github.com/risingwavelabs/risingwave/pull/7875"
18)]
19
20pub mod sim;
21use std::io;
22use std::ops::RangeBounds;
23use std::pin::Pin;
24use std::sync::Arc;
25use std::task::{Context, Poll, ready};
26use std::time::Duration;
27
28use bytes::{Buf, Bytes};
29
30pub mod mem;
31pub use mem::*;
32
33pub mod opendal_engine;
34pub use opendal_engine::*;
35
36pub mod s3;
37use await_tree::{InstrumentAwait, SpanExt};
38use futures::stream::BoxStream;
39use futures::{Future, Stream, StreamExt};
40use futures_async_stream::try_stream;
41use pin_project_lite::pin_project;
42pub use risingwave_common::config::ObjectStoreConfig;
43pub use s3::*;
44
45pub mod error;
46pub mod object_metrics;
47
48pub mod prefix;
49
50pub use error::*;
51use object_metrics::ObjectStoreMetrics;
52use risingwave_common::util::retry::exponential_backoff;
53use thiserror_ext::AsReport;
54use tokio::io::{AsyncRead, ReadBuf};
55use tokio_retry::strategy::jitter;
56
57#[cfg(madsim)]
58use self::sim::SimObjectStore;
59
60pub type ObjectStoreRef = Arc<ObjectStoreImpl>;
61pub type ObjectStreamingUploader = StreamingUploaderImpl;
62
63pub trait ObjectRangeBounds = RangeBounds<usize> + Clone + Send + Sync + std::fmt::Debug + 'static;
64
65#[derive(Debug, Clone, PartialEq)]
66pub struct ObjectMetadata {
67 pub key: String,
69 pub last_modified: f64,
71 pub total_size: usize,
72}
73
74pub trait StreamingUploader: Send {
75 #[expect(async_fn_in_trait)]
76 async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()>;
77
78 #[expect(async_fn_in_trait)]
79 async fn finish(self) -> ObjectResult<()>;
80
81 fn get_memory_usage(&self) -> u64;
82}
83
84#[async_trait::async_trait]
86pub trait ObjectStore: Send + Sync {
87 type StreamingUploader: StreamingUploader;
88 fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String;
90
91 async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()>;
93
94 async fn streaming_upload(&self, path: &str) -> ObjectResult<Self::StreamingUploader>;
95
96 async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes>;
100
101 async fn streaming_read(
105 &self,
106 path: &str,
107 read_range: impl ObjectRangeBounds,
108 ) -> ObjectResult<ObjectDataStream>;
109
110 async fn metadata(&self, path: &str) -> ObjectResult<ObjectMetadata>;
112
113 async fn delete(&self, path: &str) -> ObjectResult<()>;
115
116 async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()>;
119
120 fn monitored(
121 self,
122 metrics: Arc<ObjectStoreMetrics>,
123 config: Arc<ObjectStoreConfig>,
124 ) -> MonitoredObjectStore<Self>
125 where
126 Self: Sized,
127 {
128 MonitoredObjectStore::new(self, metrics, config)
129 }
130
131 async fn list(
132 &self,
133 prefix: &str,
134 start_after: Option<String>,
135 limit: Option<usize>,
136 ) -> ObjectResult<ObjectMetadataIter>;
137
138 fn store_media_type(&self) -> &'static str;
139}
140
141#[cfg(not(madsim))]
142macro_rules! for_all_object_store {
143 ($macro:ident $($args:tt)*) => {
144 $macro! {
145 {
146 { InMem, InMemObjectStore },
147 { Opendal, OpendalObjectStore },
148 { S3, S3ObjectStore }
149 }
150 $($args)*
151 }
152 }
153}
154
155#[cfg(madsim)]
156macro_rules! for_all_object_store {
157 ($macro:ident $($args:tt)*) => {
158 $macro! {
159 {
160 { InMem, InMemObjectStore },
161 { Opendal, OpendalObjectStore },
162 { S3, S3ObjectStore },
163 { Sim, SimObjectStore }
164 }
165 $($args)*
166 }
167 }
168}
169
170macro_rules! enum_map {
171 (
172 {
173 $(
174 {$variant:ident, $_type_name:ty}
175 ),*
176 },
177 $object_store:expr,
178 $var_name:ident,
179 $func:expr
180 ) => {
181 match $object_store {
182 $(
183 ObjectStoreEnum::$variant($var_name) => ObjectStoreEnum::$variant({
184 $func
185 }),
186 )*
187 }
188 };
189 ($object_store:expr, |$var_name:ident| $func:expr) => {
190 for_all_object_store! {
191 enum_map, $object_store, $var_name, $func
192 }
193 };
194}
195
196macro_rules! dispatch_object_store_enum {
197 (
198 {
199 $(
200 {$variant:ident, $_type_name:ty}
201 ),*
202 },
203 $object_store:expr,
204 $var_name:ident,
205 $func:expr
206 ) => {
207 match $object_store {
208 $(
209 ObjectStoreEnum::$variant($var_name) => {
210 $func
211 },
212 )*
213 }
214 };
215 ($object_store:expr, |$var_name:ident| $func:expr) => {
216 for_all_object_store! {
217 dispatch_object_store_enum, $object_store, $var_name, $func
218 }
219 };
220}
221
222macro_rules! define_object_store_impl {
223 () => {
224 for_all_object_store! {
225 define_object_store_impl
226 }
227 };
228 (
229 {$(
230 {$variant:ident, $type_name:ty}
231 ),*}
232 ) => {
233 pub enum ObjectStoreEnum<
234 $($variant),*
235 > {
236 $(
237 $variant($variant),
238 )*
239 }
240
241 pub type ObjectStoreImpl = ObjectStoreEnum<
242 $(
243 MonitoredObjectStore<$type_name>,
244 )*
245 >;
246
247 pub type StreamingUploaderImpl = ObjectStoreEnum<
248 $(
249 MonitoredStreamingUploader<<$type_name as ObjectStore>::StreamingUploader>
250 ),*
251 >;
252 };
253}
254
255define_object_store_impl!();
256
257macro_rules! object_store_impl_method_body {
262 ($object_store:expr, $method_name:ident ($($args:expr),*).await) => {
264 {
265 dispatch_object_store_enum! {$object_store, |os| {
266 os.$method_name($($args),*).await
267 }}
268 }
269 };
270 ($object_store:expr, $method_name:ident ($(, $args:expr)*)) => {
272 {
273 dispatch_object_store_enum! {$object_store, |os| {
274 os.$method_name($($args),*)
275 }}
276 }
277 };
278}
279
280impl StreamingUploaderImpl {
281 pub async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> {
282 object_store_impl_method_body!(self, write_bytes(data).await)
283 }
284
285 pub async fn finish(self) -> ObjectResult<()> {
286 object_store_impl_method_body!(self, finish().await)
287 }
288
289 pub fn get_memory_usage(&self) -> u64 {
290 object_store_impl_method_body!(self, get_memory_usage())
291 }
292}
293
294impl ObjectStoreImpl {
295 pub async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()> {
296 object_store_impl_method_body!(self, upload(path, obj).await)
297 }
298
299 pub async fn streaming_upload(&self, path: &str) -> ObjectResult<ObjectStreamingUploader> {
300 Ok(enum_map!(self, |store| {
301 store.streaming_upload(path).await?
302 }))
303 }
304
305 pub async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes> {
306 object_store_impl_method_body!(self, read(path, range).await)
307 }
308
309 pub async fn metadata(&self, path: &str) -> ObjectResult<ObjectMetadata> {
310 object_store_impl_method_body!(self, metadata(path).await)
311 }
312
313 pub async fn streaming_read(
317 &self,
318 path: &str,
319 start_loc: impl ObjectRangeBounds,
320 ) -> ObjectResult<MonitoredStreamingReader> {
321 object_store_impl_method_body!(self, streaming_read(path, start_loc).await)
322 }
323
324 pub async fn delete(&self, path: &str) -> ObjectResult<()> {
325 object_store_impl_method_body!(self, delete(path).await)
326 }
327
328 pub async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()> {
334 object_store_impl_method_body!(self, delete_objects(paths).await)
335 }
336
337 pub async fn list(
338 &self,
339 prefix: &str,
340 start_after: Option<String>,
341 limit: Option<usize>,
342 ) -> ObjectResult<ObjectMetadataIter> {
343 object_store_impl_method_body!(self, list(prefix, start_after, limit).await)
344 }
345
346 pub fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String {
347 dispatch_object_store_enum!(self, |store| store
348 .inner
349 .get_object_prefix(obj_id, use_new_object_prefix_strategy))
350 }
351
352 pub fn media_type(&self) -> &'static str {
353 object_store_impl_method_body!(self, media_type())
354 }
355}
356
357fn try_update_failure_metric<T>(
358 metrics: &Arc<ObjectStoreMetrics>,
359 result: &ObjectResult<T>,
360 operation_type: &'static str,
361) {
362 if let Err(e) = &result {
363 tracing::error!(error = %e.as_report(), "{} failed", operation_type);
364 metrics
365 .failure_count
366 .with_label_values(&[operation_type])
367 .inc();
368 }
369}
370
371pub struct MonitoredStreamingUploader<U: StreamingUploader> {
383 inner: U,
384 object_store_metrics: Arc<ObjectStoreMetrics>,
385 operation_size: usize,
387}
388
389impl<U: StreamingUploader> MonitoredStreamingUploader<U> {
390 pub fn new(handle: U, object_store_metrics: Arc<ObjectStoreMetrics>) -> Self {
391 Self {
392 inner: handle,
393 object_store_metrics,
394 operation_size: 0,
395 }
396 }
397}
398
399impl<U: StreamingUploader> MonitoredStreamingUploader<U> {
402 async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> {
403 let operation_type = OperationType::StreamingUpload;
404 let operation_type_str = operation_type.as_str();
405 let data_len = data.len();
406
407 let res = self
408 .inner
409 .write_bytes(data)
410 .instrument_await(operation_type_str.verbose())
411 .await;
412
413 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
414
415 self.object_store_metrics
417 .write_bytes
418 .inc_by(data_len as u64);
419 self.object_store_metrics
420 .operation_size
421 .with_label_values(&[operation_type_str])
422 .observe(data_len as f64);
423 self.operation_size += data_len;
424
425 res
426 }
427
428 async fn finish(self) -> ObjectResult<()> {
429 let operation_type = OperationType::StreamingUploadFinish;
430 let operation_type_str = operation_type.as_str();
431
432 let res =
433 self.inner
435 .finish()
436 .instrument_await(operation_type_str.verbose())
437 .await;
438
439 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
440
441 self.object_store_metrics
443 .operation_size
444 .with_label_values(&[operation_type_str])
445 .observe(self.operation_size as f64);
446 res
447 }
448
449 fn get_memory_usage(&self) -> u64 {
450 self.inner.get_memory_usage()
451 }
452}
453
454pub struct MonitoredStreamingReader {
455 inner: ObjectDataStream,
456 object_store_metrics: Arc<ObjectStoreMetrics>,
457 operation_size: usize,
458 media_type: &'static str,
459 streaming_read_timeout: Option<Duration>,
460 operation_type_str: &'static str,
461}
462
463impl MonitoredStreamingReader {
464 pub fn new(
465 media_type: &'static str,
466 handle: ObjectDataStream,
467 object_store_metrics: Arc<ObjectStoreMetrics>,
468 streaming_read_timeout: Option<Duration>,
469 ) -> Self {
470 Self {
471 inner: handle,
472 object_store_metrics,
473 operation_size: 0,
474 media_type,
475 streaming_read_timeout,
476 operation_type_str: OperationType::StreamingRead.as_str(),
477 }
478 }
479
480 pub async fn read_bytes(&mut self) -> Option<ObjectResult<Bytes>> {
481 let _timer = self
482 .object_store_metrics
483 .operation_latency
484 .with_label_values(&[self.media_type, self.operation_type_str])
485 .start_timer();
486 let future = async {
487 self.inner
488 .next()
489 .instrument_await(self.operation_type_str.verbose())
490 .await
491 };
492 let res = match self.streaming_read_timeout.as_ref() {
493 None => future.await,
494 Some(timeout_duration) => tokio::time::timeout(*timeout_duration, future)
495 .await
496 .unwrap_or_else(|_| {
497 Some(Err(ObjectError::timeout(format!(
498 "Retry attempts exhausted for {}. Please modify {}_attempt_timeout_ms (current={:?}) under [storage.object_store.retry] in the config accordingly if needed.",
499 self.operation_type_str, self.operation_type_str, timeout_duration.as_millis()
500 ))))
501 }),
502 };
503
504 if let Some(ret) = &res {
505 try_update_failure_metric(&self.object_store_metrics, ret, self.operation_type_str);
506 }
507 if let Some(Ok(data)) = &res {
508 let data_len = data.len();
509 self.object_store_metrics.read_bytes.inc_by(data_len as u64);
510 self.object_store_metrics
511 .operation_size
512 .with_label_values(&[self.operation_type_str])
513 .observe(data_len as f64);
514 self.operation_size += data_len;
515 }
516 res
517 }
518
519 pub fn into_stream(self) -> ObjectDataStream {
520 Self::into_stream_inner(self).boxed()
521 }
522
523 #[try_stream(ok = Bytes, error = ObjectError)]
524 async fn into_stream_inner(mut self) {
525 while let Some(bytes) = self.read_bytes().await.transpose()? {
526 yield bytes;
527 }
528 }
529}
530
531impl Drop for MonitoredStreamingReader {
532 fn drop(&mut self) {
533 self.object_store_metrics
534 .operation_size
535 .with_label_values(&[self.operation_type_str])
536 .observe(self.operation_size as f64);
537 }
538}
539
540pub struct MonitoredObjectStore<OS: ObjectStore> {
541 inner: OS,
542 object_store_metrics: Arc<ObjectStoreMetrics>,
543 config: Arc<ObjectStoreConfig>,
544}
545
546impl<OS: ObjectStore> MonitoredObjectStore<OS> {
563 pub fn new(
564 store: OS,
565 object_store_metrics: Arc<ObjectStoreMetrics>,
566 config: Arc<ObjectStoreConfig>,
567 ) -> Self {
568 Self {
569 object_store_metrics,
570 inner: store,
571 config,
572 }
573 }
574
575 fn media_type(&self) -> &'static str {
576 self.inner.store_media_type()
577 }
578
579 pub fn inner(&self) -> &OS {
580 &self.inner
581 }
582
583 pub fn mut_inner(&mut self) -> &mut OS {
584 &mut self.inner
585 }
586
587 pub async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()> {
588 let operation_type = OperationType::Upload;
589 let operation_type_str = operation_type.as_str();
590 let media_type = self.media_type();
591
592 self.object_store_metrics
593 .write_bytes
594 .inc_by(obj.len() as u64);
595 self.object_store_metrics
596 .operation_size
597 .with_label_values(&[operation_type_str])
598 .observe(obj.len() as f64);
599 let _timer = self
600 .object_store_metrics
601 .operation_latency
602 .with_label_values(&[media_type, operation_type_str])
603 .start_timer();
604
605 let builder = || async {
606 self.inner
607 .upload(path, obj.clone())
608 .instrument_await(operation_type_str.verbose())
609 .await
610 };
611
612 let res = retry_request(
613 builder,
614 &self.config,
615 operation_type,
616 self.object_store_metrics.clone(),
617 media_type,
618 )
619 .await;
620
621 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
622 res
623 }
624
625 pub async fn streaming_upload(
626 &self,
627 path: &str,
628 ) -> ObjectResult<MonitoredStreamingUploader<OS::StreamingUploader>> {
629 let operation_type = OperationType::StreamingUploadInit;
630 let operation_type_str = operation_type.as_str();
631 let media_type = self.media_type();
632 let _timer = self
633 .object_store_metrics
634 .operation_latency
635 .with_label_values(&[media_type, operation_type_str])
636 .start_timer();
637
638 let res = self
639 .inner
640 .streaming_upload(path)
641 .instrument_await(operation_type_str.verbose())
642 .await;
643
644 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
645
646 Ok(MonitoredStreamingUploader::new(
647 res?,
648 self.object_store_metrics.clone(),
649 ))
650 }
651
652 pub async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes> {
653 let operation_type = OperationType::Read;
654 let operation_type_str = operation_type.as_str();
655 let media_type = self.media_type();
656
657 let _timer = self
658 .object_store_metrics
659 .operation_latency
660 .with_label_values(&[media_type, operation_type_str])
661 .start_timer();
662
663 let builder = || async {
664 self.inner
665 .read(path, range.clone())
666 .instrument_await(operation_type_str.verbose())
667 .await
668 };
669
670 let res = retry_request(
671 builder,
672 &self.config,
673 operation_type,
674 self.object_store_metrics.clone(),
675 media_type,
676 )
677 .await;
678
679 if let Err(e) = &res
680 && e.is_object_not_found_error()
681 && !path.ends_with(".data")
682 {
683 } else {
686 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
687 }
688
689 let data = res?;
690 self.object_store_metrics
691 .read_bytes
692 .inc_by(data.len() as u64);
693 self.object_store_metrics
694 .operation_size
695 .with_label_values(&[operation_type_str])
696 .observe(data.len() as f64);
697 Ok(data)
698 }
699
700 async fn streaming_read(
704 &self,
705 path: &str,
706 range: impl ObjectRangeBounds,
707 ) -> ObjectResult<MonitoredStreamingReader> {
708 let operation_type = OperationType::StreamingReadInit;
709 let operation_type_str = operation_type.as_str();
710 let media_type = self.media_type();
711 let _timer = self
712 .object_store_metrics
713 .operation_latency
714 .with_label_values(&[media_type, operation_type_str])
715 .start_timer();
716
717 let builder = || async {
718 self.inner
719 .streaming_read(path, range.clone())
720 .instrument_await(operation_type_str.verbose())
721 .await
722 };
723
724 let res = retry_request(
725 builder,
726 &self.config,
727 operation_type,
728 self.object_store_metrics.clone(),
729 media_type,
730 )
731 .await;
732
733 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
734
735 Ok(MonitoredStreamingReader::new(
736 media_type,
737 res?,
738 self.object_store_metrics.clone(),
739 Some(Duration::from_millis(
740 self.config.retry.streaming_read_attempt_timeout_ms,
741 )),
742 ))
743 }
744
745 pub async fn metadata(&self, path: &str) -> ObjectResult<ObjectMetadata> {
746 let operation_type = OperationType::Metadata;
747 let operation_type_str = operation_type.as_str();
748 let media_type = self.media_type();
749 let _timer = self
750 .object_store_metrics
751 .operation_latency
752 .with_label_values(&[media_type, operation_type_str])
753 .start_timer();
754
755 let builder = || async {
756 self.inner
757 .metadata(path)
758 .instrument_await(operation_type_str.verbose())
759 .await
760 };
761
762 let res = retry_request(
763 builder,
764 &self.config,
765 operation_type,
766 self.object_store_metrics.clone(),
767 media_type,
768 )
769 .await;
770
771 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
772 res
773 }
774
775 pub async fn delete(&self, path: &str) -> ObjectResult<()> {
776 let operation_type = OperationType::Delete;
777 let operation_type_str = operation_type.as_str();
778 let media_type = self.media_type();
779
780 let _timer = self
781 .object_store_metrics
782 .operation_latency
783 .with_label_values(&[media_type, operation_type_str])
784 .start_timer();
785
786 let builder = || async {
787 self.inner
788 .delete(path)
789 .instrument_await(operation_type_str.verbose())
790 .await
791 };
792
793 let res = retry_request(
794 builder,
795 &self.config,
796 operation_type,
797 self.object_store_metrics.clone(),
798 media_type,
799 )
800 .await;
801
802 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
803 res
804 }
805
806 async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()> {
807 let operation_type = OperationType::DeleteObjects;
808 let operation_type_str = operation_type.as_str();
809 let media_type = self.media_type();
810
811 let _timer = self
812 .object_store_metrics
813 .operation_latency
814 .with_label_values(&[self.media_type(), operation_type_str])
815 .start_timer();
816
817 let builder = || async {
818 self.inner
819 .delete_objects(paths)
820 .instrument_await(operation_type_str.verbose())
821 .await
822 };
823
824 let res = retry_request(
825 builder,
826 &self.config,
827 operation_type,
828 self.object_store_metrics.clone(),
829 media_type,
830 )
831 .await;
832
833 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
834 res
835 }
836
837 pub async fn list(
838 &self,
839 prefix: &str,
840 start_after: Option<String>,
841 limit: Option<usize>,
842 ) -> ObjectResult<ObjectMetadataIter> {
843 let operation_type = OperationType::List;
844 let operation_type_str = operation_type.as_str();
845 let media_type = self.media_type();
846
847 let _timer = self
848 .object_store_metrics
849 .operation_latency
850 .with_label_values(&[media_type, operation_type_str])
851 .start_timer();
852
853 let builder = || async {
854 self.inner
855 .list(prefix, start_after.clone(), limit)
856 .instrument_await(operation_type_str.verbose())
857 .await
858 };
859
860 let res = retry_request(
861 builder,
862 &self.config,
863 operation_type,
864 self.object_store_metrics.clone(),
865 media_type,
866 )
867 .await;
868
869 try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str);
870 res
871 }
872}
873
874pub async fn build_remote_object_store(
880 url: &str,
881 metrics: Arc<ObjectStoreMetrics>,
882 ident: &str,
883 config: Arc<ObjectStoreConfig>,
884) -> ObjectStoreImpl {
885 tracing::debug!(config=?config, "object store {ident}");
886 match url {
887 s3 if s3.starts_with("s3://") => {
888 if config.s3.developer.use_opendal {
889 let bucket = s3.strip_prefix("s3://").unwrap();
890 tracing::info!("Using OpenDAL to access s3, bucket is {}", bucket);
891 ObjectStoreImpl::Opendal(
892 OpendalObjectStore::new_s3_engine(
893 bucket.to_owned(),
894 config.clone(),
895 metrics.clone(),
896 )
897 .unwrap()
898 .monitored(metrics, config),
899 )
900 } else {
901 ObjectStoreImpl::S3(
902 S3ObjectStore::new_with_config(
903 s3.strip_prefix("s3://").unwrap().to_owned(),
904 metrics.clone(),
905 config.clone(),
906 )
907 .await
908 .monitored(metrics, config),
909 )
910 }
911 }
912 #[cfg(feature = "hdfs-backend")]
913 hdfs if hdfs.starts_with("hdfs://") => {
914 let hdfs = hdfs.strip_prefix("hdfs://").unwrap();
915 let (namenode, root) = hdfs.split_once('@').unwrap_or((hdfs, ""));
916 ObjectStoreImpl::Opendal(
917 OpendalObjectStore::new_hdfs_engine(
918 namenode.to_string(),
919 root.to_string(),
920 config.clone(),
921 metrics.clone(),
922 )
923 .unwrap()
924 .monitored(metrics, config),
925 )
926 }
927 gcs if gcs.starts_with("gcs://") => {
928 let gcs = gcs.strip_prefix("gcs://").unwrap();
929 let (bucket, root) = gcs.split_once('@').unwrap_or((gcs, ""));
930 ObjectStoreImpl::Opendal(
931 OpendalObjectStore::new_gcs_engine(
932 bucket.to_owned(),
933 root.to_owned(),
934 config.clone(),
935 metrics.clone(),
936 )
937 .unwrap()
938 .monitored(metrics, config),
939 )
940 }
941 obs if obs.starts_with("obs://") => {
942 let obs = obs.strip_prefix("obs://").unwrap();
943 let (bucket, root) = obs.split_once('@').unwrap_or((obs, ""));
944 ObjectStoreImpl::Opendal(
945 OpendalObjectStore::new_obs_engine(
946 bucket.to_owned(),
947 root.to_owned(),
948 config.clone(),
949 metrics.clone(),
950 )
951 .unwrap()
952 .monitored(metrics, config),
953 )
954 }
955
956 oss if oss.starts_with("oss://") => {
957 let oss = oss.strip_prefix("oss://").unwrap();
958 let (bucket, root) = oss.split_once('@').unwrap_or((oss, ""));
959 ObjectStoreImpl::Opendal(
960 OpendalObjectStore::new_oss_engine(
961 bucket.to_owned(),
962 root.to_owned(),
963 config.clone(),
964 metrics.clone(),
965 )
966 .unwrap()
967 .monitored(metrics, config),
968 )
969 }
970 webhdfs if webhdfs.starts_with("webhdfs://") => {
971 let webhdfs = webhdfs.strip_prefix("webhdfs://").unwrap();
972 let (namenode, root) = webhdfs.split_once('@').unwrap_or((webhdfs, ""));
973 ObjectStoreImpl::Opendal(
974 OpendalObjectStore::new_webhdfs_engine(
975 namenode.to_owned(),
976 root.to_owned(),
977 config.clone(),
978 metrics.clone(),
979 )
980 .unwrap()
981 .monitored(metrics, config),
982 )
983 }
984 azblob if azblob.starts_with("azblob://") => {
985 let azblob = azblob.strip_prefix("azblob://").unwrap();
986 let (container_name, root) = azblob.split_once('@').unwrap_or((azblob, ""));
987 ObjectStoreImpl::Opendal(
988 OpendalObjectStore::new_azblob_engine(
989 container_name.to_owned(),
990 root.to_owned(),
991 config.clone(),
992 metrics.clone(),
993 )
994 .unwrap()
995 .monitored(metrics, config),
996 )
997 }
998 fs if fs.starts_with("fs://") => {
999 let fs = fs.strip_prefix("fs://").unwrap();
1000 ObjectStoreImpl::Opendal(
1001 OpendalObjectStore::new_fs_engine(fs.to_owned(), config.clone(), metrics.clone())
1002 .unwrap()
1003 .monitored(metrics, config),
1004 )
1005 }
1006
1007 s3_compatible if s3_compatible.starts_with("s3-compatible://") => {
1008 tracing::error!("The s3 compatible mode has been unified with s3.");
1009 tracing::error!("If you want to use s3 compatible storage, please set your access_key, secret_key and region to the environment variable AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION,
1010 set your endpoint to the environment variable RW_S3_ENDPOINT.");
1011 panic!(
1012 "Passing s3-compatible is not supported, please modify the environment variable and pass in s3."
1013 );
1014 }
1015 minio if minio.starts_with("minio://") => {
1016 if config.s3.developer.use_opendal {
1017 tracing::info!("Using OpenDAL to access minio.");
1018 ObjectStoreImpl::Opendal(
1019 OpendalObjectStore::new_minio_engine(minio, config.clone(), metrics.clone())
1020 .unwrap()
1021 .monitored(metrics, config),
1022 )
1023 } else {
1024 ObjectStoreImpl::S3(
1025 S3ObjectStore::new_minio_engine(minio, metrics.clone(), config.clone())
1026 .await
1027 .monitored(metrics, config),
1028 )
1029 }
1030 }
1031 "memory" | "memory-shared" => {
1032 if ident == "Meta Backup" {
1033 tracing::warn!(
1034 "You're using in-memory remote object store for {}. This is not recommended for production environment.",
1035 ident
1036 );
1037 } else {
1038 tracing::warn!(
1039 "You're using in-memory remote object store for {}. This should never be used in benchmarks and production environment.",
1040 ident
1041 );
1042 }
1043 ObjectStoreImpl::InMem(InMemObjectStore::shared().monitored(metrics, config))
1044 }
1045 #[cfg(debug_assertions)]
1046 "memory-isolated-for-test" => {
1047 ObjectStoreImpl::InMem(InMemObjectStore::for_test().monitored(metrics, config))
1048 }
1049 #[cfg(madsim)]
1050 sim if sim.starts_with("sim://") => {
1051 ObjectStoreImpl::Sim(SimObjectStore::new(url).monitored(metrics, config))
1052 }
1053 other => {
1054 unimplemented!(
1055 "{} remote object store only supports s3, minio, gcs, oss, cos, azure blob, hdfs, disk, memory.",
1056 other
1057 )
1058 }
1059 }
1060}
1061
1062#[inline(always)]
1063fn get_retry_strategy(
1064 config: &ObjectStoreConfig,
1065 operation_type: OperationType,
1066) -> impl Iterator<Item = Duration> + use<> {
1067 let attempts = get_retry_attempts_by_type(config, operation_type);
1068 exponential_backoff(
1069 Duration::from_millis(config.retry.req_backoff_interval_ms),
1070 config.retry.req_backoff_factor,
1071 Duration::from_millis(config.retry.req_backoff_max_delay_ms),
1072 )
1073 .take(attempts)
1074 .map(jitter)
1075}
1076
1077pub type ObjectMetadataIter = BoxStream<'static, ObjectResult<ObjectMetadata>>;
1078pub type ObjectDataStream = BoxStream<'static, ObjectResult<Bytes>>;
1079
1080pin_project! {
1081 pub struct ObjectDataStreamReader<S> {
1082 #[pin]
1083 stream: S,
1084 pending: Bytes,
1085 }
1086}
1087
1088impl<S> ObjectDataStreamReader<S>
1089where
1090 S: Stream<Item = ObjectResult<Bytes>>,
1091{
1092 pub fn new(stream: S) -> Self {
1093 Self {
1094 stream,
1095 pending: Bytes::new(),
1096 }
1097 }
1098}
1099
1100impl ObjectDataStreamReader<ObjectDataStream> {
1101 pub fn into_bytes_stream(self) -> ObjectDataStream {
1102 Self::into_bytes_stream_inner(self).boxed()
1103 }
1104
1105 #[try_stream(ok = Bytes, error = ObjectError)]
1106 async fn into_bytes_stream_inner(mut self) {
1107 if self.pending.has_remaining() {
1108 yield std::mem::take(&mut self.pending);
1109 }
1110 while let Some(bytes) = self.stream.next().await.transpose()? {
1111 yield bytes;
1112 }
1113 }
1114}
1115
1116impl<S> AsyncRead for ObjectDataStreamReader<S>
1117where
1118 S: Stream<Item = ObjectResult<Bytes>>,
1119{
1120 fn poll_read(
1121 self: Pin<&mut Self>,
1122 cx: &mut Context<'_>,
1123 buf: &mut ReadBuf<'_>,
1124 ) -> Poll<io::Result<()>> {
1125 if buf.remaining() == 0 {
1126 return Poll::Ready(Ok(()));
1127 }
1128
1129 let mut this = self.project();
1130 loop {
1131 if this.pending.has_remaining() {
1132 let len = this.pending.remaining().min(buf.remaining());
1133 buf.put_slice(&this.pending[..len]);
1134 this.pending.advance(len);
1135 return Poll::Ready(Ok(()));
1136 }
1137
1138 match ready!(this.stream.as_mut().poll_next(cx)) {
1139 Some(Ok(bytes)) => {
1140 *this.pending = bytes;
1141 }
1142 Some(Err(err)) => return Poll::Ready(Err(io::Error::other(err))),
1143 None => return Poll::Ready(Ok(())),
1144 }
1145 }
1146 }
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151 use futures::stream;
1152 use tokio::io::AsyncReadExt;
1153
1154 use super::{Bytes, ObjectDataStreamReader, ObjectError};
1155
1156 #[tokio::test]
1157 async fn test_object_data_stream_reader_reads_across_chunks() {
1158 let stream = stream::iter([
1159 Ok(Bytes::from_static(b"ab")),
1160 Ok(Bytes::from_static(b"cdef")),
1161 Ok(Bytes::from_static(b"g")),
1162 ]);
1163 let mut reader = ObjectDataStreamReader::new(stream);
1164
1165 let mut first = [0; 3];
1166 reader.read_exact(&mut first).await.unwrap();
1167 assert_eq!(&first, b"abc");
1168
1169 let mut rest = vec![];
1170 reader.read_to_end(&mut rest).await.unwrap();
1171 assert_eq!(&rest, b"defg");
1172 }
1173
1174 #[tokio::test]
1175 async fn test_object_data_stream_reader_returns_stream_error() {
1176 let stream = stream::iter([
1177 Ok(Bytes::from_static(b"ab")),
1178 Err(ObjectError::internal("injected stream error")),
1179 ]);
1180 let mut reader = ObjectDataStreamReader::new(stream);
1181
1182 let mut output = vec![];
1183 let err = reader.read_to_end(&mut output).await.unwrap_err();
1184 assert!(err.to_string().contains("injected stream error"));
1185 }
1186}
1187
1188#[derive(Debug, Clone, Copy)]
1189enum OperationType {
1190 Upload,
1191 StreamingUploadInit,
1192 StreamingUpload,
1193 StreamingUploadFinish,
1194 Read,
1195 StreamingReadInit,
1196 StreamingRead,
1197 Metadata,
1198 Delete,
1199 DeleteObjects,
1200 List,
1201}
1202
1203impl OperationType {
1204 fn as_str(&self) -> &'static str {
1205 match self {
1206 Self::Upload => "upload",
1207 Self::StreamingUploadInit => "streaming_upload_init",
1208 Self::StreamingUpload => "streaming_upload",
1209 Self::StreamingUploadFinish => "streaming_upload_finish",
1210 Self::Read => "read",
1211 Self::StreamingReadInit => "streaming_read_init",
1212 Self::StreamingRead => "streaming_read",
1213 Self::Metadata => "metadata",
1214 Self::Delete => "delete",
1215 Self::DeleteObjects => "delete_objects",
1216 Self::List => "list",
1217 }
1218 }
1219}
1220
1221fn get_retry_attempts_by_type(config: &ObjectStoreConfig, operation_type: OperationType) -> usize {
1222 match operation_type {
1223 OperationType::Upload => config.retry.upload_retry_attempts,
1224 OperationType::StreamingUploadInit
1225 | OperationType::StreamingUpload
1226 | OperationType::StreamingUploadFinish => config.retry.streaming_upload_retry_attempts,
1227 OperationType::Read => config.retry.read_retry_attempts,
1228 OperationType::StreamingReadInit | OperationType::StreamingRead => {
1229 config.retry.streaming_read_retry_attempts
1230 }
1231 OperationType::Metadata => config.retry.metadata_retry_attempts,
1232 OperationType::Delete => config.retry.delete_retry_attempts,
1233 OperationType::DeleteObjects => config.retry.delete_objects_retry_attempts,
1234 OperationType::List => config.retry.list_retry_attempts,
1235 }
1236}
1237
1238fn get_attempt_timeout_by_type(config: &ObjectStoreConfig, operation_type: OperationType) -> u64 {
1239 match operation_type {
1240 OperationType::Upload => config.retry.upload_attempt_timeout_ms,
1241 OperationType::StreamingUploadInit
1242 | OperationType::StreamingUpload
1243 | OperationType::StreamingUploadFinish => config.retry.streaming_upload_attempt_timeout_ms,
1244 OperationType::Read => config.retry.read_attempt_timeout_ms,
1245 OperationType::StreamingReadInit | OperationType::StreamingRead => {
1246 config.retry.streaming_read_attempt_timeout_ms
1247 }
1248 OperationType::Metadata => config.retry.metadata_attempt_timeout_ms,
1249 OperationType::Delete => config.retry.delete_attempt_timeout_ms,
1250 OperationType::DeleteObjects => config.retry.delete_objects_attempt_timeout_ms,
1251 OperationType::List => config.retry.list_attempt_timeout_ms,
1252 }
1253}
1254
1255struct RetryCondition {
1256 operation_type: OperationType,
1257 retry_count: usize,
1258 metrics: Arc<ObjectStoreMetrics>,
1259 retry_opendal_s3_unknown_error: bool,
1260}
1261
1262impl RetryCondition {
1263 fn new(
1264 operation_type: OperationType,
1265 metrics: Arc<ObjectStoreMetrics>,
1266 retry_opendal_s3_unknown_error: bool,
1267 ) -> Self {
1268 Self {
1269 operation_type,
1270 retry_count: 0,
1271 metrics,
1272 retry_opendal_s3_unknown_error,
1273 }
1274 }
1275
1276 #[inline(always)]
1277 fn should_retry_inner(&mut self, err: &ObjectError) -> bool {
1278 let should_retry = err.should_retry(self.retry_opendal_s3_unknown_error);
1279 if should_retry {
1280 self.retry_count += 1;
1281 }
1282
1283 should_retry
1284 }
1285}
1286
1287impl tokio_retry::Condition<ObjectError> for RetryCondition {
1288 fn should_retry(&mut self, err: &ObjectError) -> bool {
1289 self.should_retry_inner(err)
1290 }
1291}
1292
1293impl Drop for RetryCondition {
1294 fn drop(&mut self) {
1295 if self.retry_count > 0 {
1296 self.metrics
1297 .request_retry_count
1298 .with_label_values(&[self.operation_type.as_str()])
1299 .inc_by(self.retry_count as _);
1300 }
1301 }
1302}
1303
1304async fn retry_request<F, T, B>(
1305 builder: B,
1306 config: &ObjectStoreConfig,
1307 operation_type: OperationType,
1308 object_store_metrics: Arc<ObjectStoreMetrics>,
1309 media_type: &'static str,
1310) -> ObjectResult<T>
1311where
1312 B: Fn() -> F,
1313 F: Future<Output = ObjectResult<T>>,
1314{
1315 let backoff = get_retry_strategy(config, operation_type);
1316 let timeout_duration =
1317 Duration::from_millis(get_attempt_timeout_by_type(config, operation_type));
1318 let operation_type_str = operation_type.as_str();
1319
1320 let retry_condition = RetryCondition::new(
1321 operation_type,
1322 object_store_metrics,
1323 (config.s3.developer.retry_unknown_service_error || config.s3.retry_unknown_service_error)
1324 && (media_type == opendal_engine::MediaType::S3.as_str()
1325 || media_type == opendal_engine::MediaType::Minio.as_str()),
1326 );
1327
1328 let f = || async {
1329 let future = builder();
1330 if timeout_duration.is_zero() {
1331 future.await
1332 } else {
1333 tokio::time::timeout(timeout_duration, future)
1334 .await
1335 .unwrap_or_else(|_| {
1336 Err(ObjectError::timeout(format!(
1337 "Retry attempts exhausted for {}. Please modify {}_attempt_timeout_ms (current={:?}) and {}_retry_attempts (current={}) under [storage.object_store.retry] in the config accordingly if needed.",
1338 operation_type_str, operation_type_str, timeout_duration.as_millis(), operation_type_str, get_retry_attempts_by_type(config, operation_type)
1339 )))
1340 })
1341 }
1342 };
1343
1344 tokio_retry::RetryIf::spawn(backoff, f, retry_condition).await
1345}