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