Skip to main content

risingwave_object_store/object/
mod.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![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    // Full path
67    pub key: String,
68    // Seconds since unix epoch.
69    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/// The implementation must be thread-safe.
84#[async_trait::async_trait]
85pub trait ObjectStore: Send + Sync {
86    type StreamingUploader: StreamingUploader;
87    /// Get the key prefix for object, the prefix is determined by the type of object store and `devise_object_prefix`.
88    fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String;
89
90    /// Uploads the object to `ObjectStore`.
91    async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()>;
92
93    async fn streaming_upload(&self, path: &str) -> ObjectResult<Self::StreamingUploader>;
94
95    /// If objects are PUT using a multipart upload, it's a good practice to GET them in the same
96    /// part sizes (or at least aligned to part boundaries) for best performance.
97    /// <https://d1.awsstatic.com/whitepapers/AmazonS3BestPractices.pdf?stod_obj2>
98    async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult<Bytes>;
99
100    /// Returns a stream reading the object specified in `path`. If given, the stream starts at the
101    /// byte with index `start_pos` (0-based). As far as possible, the stream only loads the amount
102    /// of data into memory that is read from the stream.
103    async fn streaming_read(
104        &self,
105        path: &str,
106        read_range: impl ObjectRangeBounds,
107    ) -> ObjectResult<ObjectDataStream>;
108
109    /// Obtains the object metadata.
110    async fn metadata(&self, path: &str) -> ObjectResult<ObjectMetadata>;
111
112    /// Deletes blob permanently.
113    async fn delete(&self, path: &str) -> ObjectResult<()>;
114
115    /// Deletes the objects with the given paths permanently from the storage. If an object
116    /// specified in the request is not found, it will be considered as successfully deleted.
117    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
256/// This macro routes the object store operation to the real implementation by the `ObjectStoreImpl`
257/// enum type and the `path`.
258///
259/// Except for `InMem`,the operation should be performed on remote object store.
260macro_rules! object_store_impl_method_body {
261    // with await
262    ($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    // no await
270    ($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    /// Returns a stream reading the object specified in `path`. If given, the stream starts at the
313    /// byte with index `start_pos` (0-based). As far as possible, the stream only loads the amount
314    /// of data into memory that is read from the stream.
315    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    /// Deletes the objects with the given paths permanently from the storage. If an object
328    /// specified in the request is not found, it will be considered as successfully deleted.
329    ///
330    /// If a hybrid storage is used, the method will first attempt to delete objects in local
331    /// storage. Only if that is successful, it will remove objects from remote storage.
332    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
370/// `MonitoredStreamingUploader` will report the following metrics.
371/// - `write_bytes`: The number of bytes uploaded from the uploader's creation to finish.
372/// - `operation_size`:
373///   - `streaming_upload_write_bytes`: The number of bytes written for each call to `write_bytes`.
374///   - `streaming_upload`: Same as `write_bytes`.
375/// - `operation_latency`:
376///   - `streaming_upload_start`: The time spent creating the uploader.
377///   - `streaming_upload_write_bytes`: The time spent on each call to `write_bytes`.
378///   - `streaming_upload_finish`: The time spent calling `finish`.
379/// - `failure_count`: `streaming_upload_start`, `streaming_upload_write_bytes`,
380///   `streaming_upload_finish`
381pub struct MonitoredStreamingUploader<U: StreamingUploader> {
382    inner: U,
383    object_store_metrics: Arc<ObjectStoreMetrics>,
384    /// Length of data uploaded with this uploader.
385    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
398/// NOTICE: after #16231, streaming uploader implemented via aws-sdk-s3 will maintain metrics internally in s3.rs
399/// so `MonitoredStreamingUploader` will only be used when the inner object store is opendal.
400impl<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        // duration metrics is collected and reported inside the specific implementation of the streaming uploader.
415        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            // TODO: we should avoid this special case after fully migrating to opeandal for s3.
433            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        // duration metrics is collected and reported inside the specific implementation of the streaming uploader.
441        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
545/// Manually dispatch trait methods.
546///
547/// The metrics are updated in the following order:
548/// - Write operations
549///   - `write_bytes`
550///   - `operation_size`
551///   - start `operation_latency` timer
552///   - `failure_count`
553/// - Read operations
554///   - start `operation_latency` timer
555///   - `failure-count`
556///   - `read_bytes`
557///   - `operation_size`
558/// - Other
559///   - start `operation_latency` timer
560///   - `failure-count`
561impl<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            // Some not_found_error is expected, e.g. metadata backup's manifest.json.
683            // This is a quick fix that'll only log error in `try_update_failure_metric` in state store usage.
684        } 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    /// Returns a stream reading the object specified in `path`. If given, the stream starts at the
700    /// byte with index `start_pos` (0-based). As far as possible, the stream only loads the amount
701    /// of data into memory that is read from the stream.
702    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
873/// Creates a new [`ObjectStore`] from the given `url`. Credentials are configured via environment
874/// variables.
875///
876/// # Panics
877/// If the `url` is invalid. Therefore, it is only suitable to be used during startup.
878pub 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" /* backward compatible, memory is always shared now */ => {
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" /* isolated memory is only available for tests */ => {
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}