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