Coverage Report

Created: 2026-08-06 11:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/io/fs/s3_file_reader.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "io/fs/s3_file_reader.h"
19
20
#include <aws/core/http/URI.h>
21
#include <aws/core/utils/Outcome.h>
22
#include <aws/s3/S3Client.h>
23
#include <aws/s3/S3Errors.h>
24
#include <aws/s3/model/GetObjectRequest.h>
25
#include <aws/s3/model/GetObjectResult.h>
26
#include <bvar/latency_recorder.h>
27
#include <bvar/reducer.h>
28
#include <fmt/format.h>
29
#include <glog/logging.h>
30
31
#include <algorithm>
32
#include <utility>
33
34
#include "common/compiler_util.h" // IWYU pragma: keep
35
#include "common/metrics/doris_metrics.h"
36
#include "io/cache/block_file_cache.h"
37
#include "io/fs/err_utils.h"
38
#include "io/fs/obj_storage_client.h"
39
#include "io/fs/s3_common.h"
40
#include "runtime/file_scan_profile.h"
41
#include "runtime/runtime_profile.h"
42
#include "runtime/thread_context.h"
43
#include "runtime/workload_group/workload_group.h"
44
#include "runtime/workload_management/io_throttle.h"
45
#include "runtime/workload_management/resource_context.h"
46
#include "util/bvar_helper.h"
47
#include "util/concurrency_stats.h"
48
#include "util/debug_points.h"
49
#include "util/s3_util.h"
50
51
namespace doris::io {
52
53
bvar::Adder<uint64_t> s3_file_reader_read_counter("s3_file_reader", "read_at");
54
bvar::Adder<uint64_t> s3_file_reader_total("s3_file_reader", "total_num");
55
bvar::Adder<uint64_t> s3_bytes_read_total("s3_file_reader", "bytes_read");
56
bvar::Adder<uint64_t> s3_file_being_read("s3_file_reader", "file_being_read");
57
bvar::Adder<uint64_t> s3_file_reader_too_many_request_counter("s3_file_reader", "too_many_request");
58
bvar::LatencyRecorder s3_bytes_per_read("s3_file_reader", "bytes_per_read"); // also QPS
59
bvar::PerSecond<bvar::Adder<uint64_t>> s3_read_througthput("s3_file_reader", "s3_read_throughput",
60
                                                           &s3_bytes_read_total);
61
// Although we can get QPS from s3_bytes_per_read, but s3_bytes_per_read only
62
// record successfull request, and s3_get_request_qps will record all request.
63
bvar::PerSecond<bvar::Adder<uint64_t>> s3_get_request_qps("s3_file_reader", "s3_get_request",
64
                                                          &s3_file_reader_read_counter);
65
bvar::LatencyRecorder s3_file_reader_latency("s3_file_reader", "s3_latency");
66
67
Result<FileReaderSPtr> S3FileReader::create(std::shared_ptr<const ObjClientHolder> client,
68
                                            std::string bucket, std::string key, int64_t file_size,
69
277k
                                            RuntimeProfile* profile) {
70
277k
    if (file_size < 0) {
71
33.2k
        auto res = client->object_file_size(bucket, key);
72
33.2k
        if (!res.has_value()) {
73
3
            return ResultError(std::move(res.error()));
74
3
        }
75
76
33.2k
        file_size = res.value();
77
33.2k
    }
78
79
277k
    return std::make_shared<S3FileReader>(std::move(client), std::move(bucket), std::move(key),
80
277k
                                          file_size, profile);
81
277k
}
82
83
S3FileReader::S3FileReader(std::shared_ptr<const ObjClientHolder> client, std::string bucket,
84
                           std::string key, size_t file_size, RuntimeProfile* profile)
85
277k
        : _path(fmt::format("s3://{}/{}", bucket, key)),
86
277k
          _file_size(file_size),
87
277k
          _bucket(std::move(bucket)),
88
277k
          _key(std::move(key)),
89
277k
          _client(std::move(client)),
90
277k
          _profile(profile) {
91
277k
    DorisMetrics::instance()->s3_file_open_reading->increment(1);
92
277k
    DorisMetrics::instance()->s3_file_reader_total->increment(1);
93
277k
    s3_file_reader_total << 1;
94
277k
    s3_file_being_read << 1;
95
96
277k
    Aws::Http::SetCompliantRfc3986Encoding(true);
97
277k
}
98
99
240k
S3FileReader::~S3FileReader() {
100
240k
    static_cast<void>(close());
101
240k
    s3_file_being_read << -1;
102
240k
}
103
104
487k
Status S3FileReader::close() {
105
487k
    bool expected = false;
106
487k
    if (_closed.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
107
241k
        DorisMetrics::instance()->s3_file_open_reading->increment(-1);
108
241k
    }
109
487k
    return Status::OK();
110
487k
}
111
112
Status S3FileReader::read_at_impl(size_t offset, Slice result, size_t* bytes_read,
113
314k
                                  const IOContext* /*io_ctx*/) {
114
314k
    DCHECK(!closed());
115
314k
    if (offset > _file_size) {
116
0
        return Status::InternalError(
117
0
                "offset exceeds file size(offset: {}, file size: {}, path: {})", offset, _file_size,
118
0
                _path.native());
119
0
    }
120
314k
    size_t bytes_req = result.size;
121
314k
    char* to = result.data;
122
314k
    bytes_req = std::min(bytes_req, _file_size - offset);
123
18.4E
    VLOG_DEBUG << fmt::format("S3FileReader::read_at_impl offset={} size={} path={} hash={}",
124
18.4E
                              offset, result.size, _path.native(),
125
18.4E
                              io::BlockFileCache::hash(_path.native()).to_string());
126
314k
    VLOG_DEBUG << "enter s3 read_at_impl, off=" << offset << " n=" << bytes_req
127
9
               << " req=" << result.size << " file size=" << _file_size;
128
314k
    if (UNLIKELY(bytes_req == 0)) {
129
128
        *bytes_read = 0;
130
128
        return Status::OK();
131
128
    }
132
133
314k
    auto client = _client->get();
134
314k
    if (!client) {
135
0
        return Status::InternalError("init s3 client error");
136
0
    }
137
138
314k
    SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().s3_file_reader_read);
139
140
314k
    int retry_count = 0;
141
314k
    const int base_wait_time = config::s3_read_base_wait_time_ms; // Base wait time in milliseconds
142
314k
    const int max_wait_time = config::s3_read_max_wait_time_ms; // Maximum wait time in milliseconds
143
314k
    const int max_retries = config::max_s3_client_retry; // wait 1s, 2s, 4s, 8s for each backoff
144
145
314k
    int64_t begin_ts = std::chrono::duration_cast<std::chrono::microseconds>(
146
314k
                               std::chrono::system_clock::now().time_since_epoch())
147
314k
                               .count();
148
314k
    LIMIT_REMOTE_SCAN_IO(bytes_read);
149
314k
    DBUG_EXECUTE_IF("S3FileReader::read_at_impl.io_slow", {
150
314k
        auto sleep_time = dp->param("sleep", 3);
151
314k
        LOG_INFO("S3FileReader::read_at_impl.io_slow inject microseconds {} s", sleep_time)
152
314k
                .tag("bucket", _bucket)
153
314k
                .tag("key", _key);
154
314k
        std::this_thread::sleep_for(std::chrono::microseconds(sleep_time));
155
314k
    });
156
314k
    Defer defer_latency {[&]() {
157
314k
        int64_t end_ts = std::chrono::duration_cast<std::chrono::microseconds>(
158
314k
                                 std::chrono::system_clock::now().time_since_epoch())
159
314k
                                 .count();
160
314k
        s3_file_reader_latency << (end_ts - begin_ts);
161
314k
    }};
162
314k
    SCOPED_RAW_TIMER(&_s3_stats.total_get_request_time_ns);
163
164
314k
    int total_sleep_time = 0;
165
314k
    while (retry_count <= max_retries) {
166
314k
        *bytes_read = 0;
167
314k
        s3_file_reader_read_counter << 1;
168
        // clang-format off
169
314k
        auto resp = client->get_object( { .bucket = _bucket, .key = _key, },
170
314k
                to, offset, bytes_req, bytes_read);
171
        // clang-format on
172
314k
        _s3_stats.total_get_request_counter++;
173
314k
        if (resp.status.code != ErrorCode::OK) {
174
0
            if (resp.http_code ==
175
0
                static_cast<int>(Aws::Http::HttpResponseCode::TOO_MANY_REQUESTS)) {
176
0
                s3_file_reader_too_many_request_counter << 1;
177
0
                retry_count++;
178
0
                int wait_time = std::min(base_wait_time * (1 << retry_count),
179
0
                                         max_wait_time); // Exponential backoff
180
0
                std::this_thread::sleep_for(std::chrono::milliseconds(wait_time));
181
0
                _s3_stats.too_many_request_err_counter++;
182
0
                _s3_stats.too_many_request_sleep_time_ms += wait_time;
183
0
                total_sleep_time += wait_time;
184
0
                continue;
185
0
            } else {
186
                // Handle other errors
187
0
                return std::move(Status(resp.status.code, std::move(resp.status.msg))
188
0
                                         .append("failed to read"));
189
0
            }
190
0
        }
191
314k
        if (*bytes_read != bytes_req) {
192
0
            std::string msg = fmt::format(
193
0
                    "failed to get object, path={} offset={} bytes_req={} bytes_read={} "
194
0
                    "file_size={} tries={}",
195
0
                    _path.native(), offset, bytes_req, *bytes_read, _file_size, (retry_count + 1));
196
0
            LOG(WARNING) << msg;
197
0
            return Status::InternalError(msg);
198
0
        }
199
314k
        _s3_stats.total_bytes_read += bytes_req;
200
314k
        s3_bytes_read_total << bytes_req;
201
314k
        s3_bytes_per_read << bytes_req;
202
314k
        DorisMetrics::instance()->s3_bytes_read_total->increment(bytes_req);
203
314k
        if (retry_count > 0) {
204
0
            LOG(INFO) << fmt::format("read s3 file {} succeed after {} times with {} ms sleeping",
205
0
                                     _path.native(), retry_count, total_sleep_time);
206
0
        }
207
314k
        return Status::OK();
208
314k
    }
209
4
    std::string msg = fmt::format(
210
4
            "failed to get object, path={} offset={} bytes_req={} bytes_read={} file_size={} "
211
4
            "tries={}",
212
4
            _path.native(), offset, bytes_req, *bytes_read, _file_size, (max_retries + 1));
213
4
    LOG(WARNING) << msg;
214
4
    return Status::InternalError(msg);
215
314k
}
216
217
25.0k
void S3FileReader::_collect_profile_before_close() {
218
25.0k
    if (_profile != nullptr) {
219
23.9k
        const char* s3_profile_name = "S3Profile";
220
23.9k
        auto* total_time =
221
23.9k
                ADD_CHILD_TIMER(_profile, s3_profile_name,
222
23.9k
                                file_scan_profile::parent_or_root(_profile, file_scan_profile::IO));
223
23.9k
        RuntimeProfile::Counter* total_get_request_counter =
224
23.9k
                ADD_CHILD_COUNTER(_profile, "TotalGetRequest", TUnit::UNIT, s3_profile_name);
225
23.9k
        RuntimeProfile::Counter* too_many_request_err_counter =
226
23.9k
                ADD_CHILD_COUNTER(_profile, "TooManyRequestErr", TUnit::UNIT, s3_profile_name);
227
23.9k
        RuntimeProfile::Counter* too_many_request_sleep_time = ADD_CHILD_COUNTER(
228
23.9k
                _profile, "TooManyRequestSleepTime", TUnit::TIME_MS, s3_profile_name);
229
23.9k
        RuntimeProfile::Counter* total_bytes_read =
230
23.9k
                ADD_CHILD_COUNTER(_profile, "TotalBytesRead", TUnit::BYTES, s3_profile_name);
231
23.9k
        RuntimeProfile::Counter* total_get_request_time_ns =
232
23.9k
                ADD_CHILD_TIMER(_profile, "TotalGetRequestTime", s3_profile_name);
233
234
23.9k
        COUNTER_UPDATE(total_get_request_counter, _s3_stats.total_get_request_counter);
235
23.9k
        COUNTER_UPDATE(too_many_request_err_counter, _s3_stats.too_many_request_err_counter);
236
23.9k
        COUNTER_UPDATE(too_many_request_sleep_time, _s3_stats.too_many_request_sleep_time_ms);
237
23.9k
        COUNTER_UPDATE(total_bytes_read, _s3_stats.total_bytes_read);
238
23.9k
        COUNTER_UPDATE(total_get_request_time_ns, _s3_stats.total_get_request_time_ns);
239
23.9k
        COUNTER_UPDATE(total_time, _s3_stats.total_get_request_time_ns);
240
23.9k
    }
241
25.0k
}
242
243
} // namespace doris::io