be/src/exec/scan/parallel_scanner_builder.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 "exec/scan/parallel_scanner_builder.h" |
19 | | |
20 | | #include <cstddef> |
21 | | |
22 | | #include "cloud/cloud_storage_engine.h" |
23 | | #include "cloud/cloud_tablet_hotspot.h" |
24 | | #include "cloud/config.h" |
25 | | #include "common/status.h" |
26 | | #include "exec/operator/olap_scan_operator.h" |
27 | | #include "exec/scan/olap_scanner.h" |
28 | | #include "io/io_common.h" |
29 | | #include "runtime/query_context.h" |
30 | | #include "storage/rowset/beta_rowset.h" |
31 | | #include "storage/segment/segment_loader.h" |
32 | | #include "storage/tablet/base_tablet.h" |
33 | | |
34 | | namespace doris { |
35 | | |
36 | | namespace { |
37 | | |
38 | | io::FileCacheStatistics take_initial_file_cache_stats( |
39 | 344k | std::unordered_map<int64_t, io::FileCacheStatistics>* preload_stats, int64_t tablet_id) { |
40 | 344k | auto it = preload_stats->find(tablet_id); |
41 | 344k | if (it == preload_stats->end()) { |
42 | 55.8k | return {}; |
43 | 55.8k | } |
44 | 288k | auto stats = std::move(it->second); |
45 | 288k | preload_stats->erase(it); |
46 | 288k | return stats; |
47 | 344k | } |
48 | | |
49 | 2.13M | io::IOContext create_preload_io_context(RuntimeState* state, OlapReaderStatistics* preload_stats) { |
50 | 2.13M | io::IOContext io_ctx; |
51 | 2.13M | io_ctx.reader_type = ReaderType::READER_QUERY; |
52 | 18.4E | io_ctx.file_cache_stats = preload_stats ? &preload_stats->file_cache_stats : nullptr; |
53 | 2.13M | if (state == nullptr) { |
54 | 0 | return io_ctx; |
55 | 0 | } |
56 | 2.13M | io_ctx.query_id = &state->query_id(); |
57 | 2.13M | io_ctx.read_file_cache = state->query_options().enable_file_cache; |
58 | 2.13M | io_ctx.is_disposable = state->query_options().disable_file_cache; |
59 | 2.13M | if (auto* query_ctx = state->get_query_ctx(); query_ctx != nullptr) { |
60 | 2.13M | io_ctx.remote_scan_cache_write_limiter = query_ctx->remote_scan_cache_write_limiter(); |
61 | 2.13M | } |
62 | 2.13M | return io_ctx; |
63 | 2.13M | } |
64 | | |
65 | | } // namespace |
66 | | |
67 | 120k | Status ParallelScannerBuilder::build_scanners(std::list<ScannerSPtr>& scanners) { |
68 | 120k | RETURN_IF_ERROR(_load()); |
69 | 120k | if (_scan_parallelism_by_per_segment) { |
70 | 19 | return _build_scanners_by_per_segment(scanners); |
71 | 121k | } else if (_is_dup_mow_key) { |
72 | | // Default strategy for DUP/MOW tables: split by rowids within segments |
73 | 121k | return _build_scanners_by_rowid(scanners); |
74 | 18.4E | } else { |
75 | | // TODO: support to split by key range |
76 | 18.4E | return Status::NotSupported("split by key range not supported yet."); |
77 | 18.4E | } |
78 | 120k | } |
79 | | |
80 | 122k | Status ParallelScannerBuilder::_build_scanners_by_rowid(std::list<ScannerSPtr>& scanners) { |
81 | 122k | DCHECK_GE(_rows_per_scanner, _min_rows_per_scanner); |
82 | | |
83 | 510k | for (auto&& [tablet, version] : _tablets) { |
84 | 510k | DCHECK(_all_read_sources.contains(tablet->tablet_id())); |
85 | 510k | auto& entire_read_source = _all_read_sources[tablet->tablet_id()]; |
86 | | |
87 | 510k | if (config::is_cloud_mode()) { |
88 | | // FIXME(plat1ko): Avoid pointer cast |
89 | 504k | ExecEnv::GetInstance()->storage_engine().to_cloud().tablet_hotspot().count(*tablet); |
90 | 504k | } |
91 | | |
92 | | // `rs_splits` in `entire read source` will be devided into several partitial read sources |
93 | | // to build several parallel scanners, based on segment rows number. All the partitial read sources |
94 | | // share the same delete predicates from their corresponding entire read source. |
95 | 510k | TabletReadSource partitial_read_source; |
96 | 510k | int64_t rows_collected = 0; |
97 | 2.13M | for (auto& rs_split : entire_read_source.rs_splits) { |
98 | 2.13M | auto reader = rs_split.rs_reader; |
99 | 2.13M | auto rowset = reader->rowset(); |
100 | 2.13M | const auto rowset_id = rowset->rowset_id(); |
101 | | |
102 | 2.13M | const auto& segments_rows = _all_segments_rows[rowset_id]; |
103 | | |
104 | 2.13M | if (rowset->num_rows() == 0) { |
105 | 1.48M | continue; |
106 | 1.48M | } |
107 | | |
108 | 654k | int64_t segment_start = 0; |
109 | 654k | auto split = RowSetSplits(reader->clone()); |
110 | | |
111 | 1.30M | for (size_t i = 0; i != segments_rows.size(); ++i) { |
112 | 653k | const size_t rows_of_segment = segments_rows[i]; |
113 | 653k | RowRanges row_ranges; |
114 | 653k | int64_t offset_in_segment = 0; |
115 | | |
116 | | // try to split large segments into RowRanges |
117 | 1.36M | while (offset_in_segment < rows_of_segment) { |
118 | 707k | const int64_t remaining_rows = rows_of_segment - offset_in_segment; |
119 | 707k | auto rows_need = _rows_per_scanner - rows_collected; |
120 | | |
121 | | // 0.9: try to avoid splitting the segments into excessively small parts. |
122 | 707k | if (rows_need >= remaining_rows * 9 / 10) { |
123 | 653k | rows_need = remaining_rows; |
124 | 653k | } |
125 | 707k | DCHECK_LE(rows_need, remaining_rows); |
126 | | |
127 | | // RowRange stands for range: [From, To), From is inclusive, To is exclusive. |
128 | 707k | row_ranges.add({offset_in_segment, |
129 | 707k | offset_in_segment + static_cast<int64_t>(rows_need)}); |
130 | 707k | rows_collected += rows_need; |
131 | 707k | offset_in_segment += rows_need; |
132 | | |
133 | | // If collected enough rows, build a new scanner |
134 | 707k | if (rows_collected >= _rows_per_scanner) { |
135 | 56.4k | split.segment_offsets.first = segment_start, |
136 | 56.4k | split.segment_offsets.second = i + 1; |
137 | 56.4k | split.segment_row_ranges.emplace_back(std::move(row_ranges)); |
138 | | |
139 | 56.4k | DCHECK_EQ(split.segment_offsets.second - split.segment_offsets.first, |
140 | 56.4k | split.segment_row_ranges.size()); |
141 | | |
142 | 56.4k | partitial_read_source.rs_splits.emplace_back(std::move(split)); |
143 | | |
144 | 56.4k | scanners.emplace_back(_build_scanner( |
145 | 56.4k | tablet, version, _key_ranges, |
146 | 56.4k | {.rs_splits = std::move(partitial_read_source.rs_splits), |
147 | 56.4k | .delete_predicates = entire_read_source.delete_predicates, |
148 | 56.4k | .delete_bitmap = entire_read_source.delete_bitmap}, |
149 | 56.4k | take_initial_file_cache_stats(&_tablet_preload_file_cache_stats, |
150 | 56.4k | tablet->tablet_id()))); |
151 | | |
152 | 56.4k | partitial_read_source = {}; |
153 | 56.4k | split = RowSetSplits(reader->clone()); |
154 | 56.4k | row_ranges = RowRanges(); |
155 | | |
156 | 56.4k | segment_start = offset_in_segment < rows_of_segment ? i : i + 1; |
157 | 56.4k | rows_collected = 0; |
158 | 56.4k | } |
159 | 707k | } |
160 | | |
161 | | // The non-empty `row_ranges` means there are some rows left in this segment not added into `split`. |
162 | 653k | if (!row_ranges.is_empty()) { |
163 | 650k | DCHECK_GT(rows_collected, 0); |
164 | 650k | DCHECK_EQ(row_ranges.to(), rows_of_segment); |
165 | 650k | split.segment_row_ranges.emplace_back(std::move(row_ranges)); |
166 | 650k | } |
167 | 653k | } |
168 | | |
169 | 654k | DCHECK_LE(rows_collected, _rows_per_scanner); |
170 | 654k | if (rows_collected > 0) { |
171 | 650k | split.segment_offsets.first = segment_start; |
172 | 650k | split.segment_offsets.second = segments_rows.size(); |
173 | 650k | DCHECK_GT(split.segment_offsets.second, split.segment_offsets.first); |
174 | 650k | DCHECK_EQ(split.segment_row_ranges.size(), |
175 | 650k | split.segment_offsets.second - split.segment_offsets.first); |
176 | 650k | partitial_read_source.rs_splits.emplace_back(std::move(split)); |
177 | 650k | } |
178 | 654k | } // end `for (auto& rowset : rowsets)` |
179 | | |
180 | 510k | DCHECK_LE(rows_collected, _rows_per_scanner); |
181 | 510k | if (rows_collected > 0) { |
182 | 288k | DCHECK_GT(partitial_read_source.rs_splits.size(), 0); |
183 | 288k | #ifndef NDEBUG |
184 | 575k | for (auto& split : partitial_read_source.rs_splits) { |
185 | 575k | DCHECK(split.rs_reader != nullptr); |
186 | 575k | DCHECK_LT(split.segment_offsets.first, split.segment_offsets.second); |
187 | 575k | DCHECK_EQ(split.segment_row_ranges.size(), |
188 | 575k | split.segment_offsets.second - split.segment_offsets.first); |
189 | 575k | } |
190 | 288k | #endif |
191 | 288k | scanners.emplace_back( |
192 | 288k | _build_scanner(tablet, version, _key_ranges, |
193 | 288k | {.rs_splits = std::move(partitial_read_source.rs_splits), |
194 | 288k | .delete_predicates = entire_read_source.delete_predicates, |
195 | 288k | .delete_bitmap = entire_read_source.delete_bitmap}, |
196 | 288k | take_initial_file_cache_stats(&_tablet_preload_file_cache_stats, |
197 | 288k | tablet->tablet_id()))); |
198 | 288k | } |
199 | 510k | } |
200 | | |
201 | 122k | return Status::OK(); |
202 | 122k | } |
203 | | |
204 | | // Build scanners so that each segment is exclusively scanned by a single scanner. |
205 | | // This guarantees the number of scanners equals the number of segments across all rowsets |
206 | | // for the involved tablets. It preserves delete predicates and key ranges, and clones |
207 | | // RowsetReader per scanner to avoid sharing between scanners. |
208 | 19 | Status ParallelScannerBuilder::_build_scanners_by_per_segment(std::list<ScannerSPtr>& scanners) { |
209 | 19 | DCHECK_GE(_rows_per_scanner, _min_rows_per_scanner); |
210 | | |
211 | 109 | for (auto&& [tablet, version] : _tablets) { |
212 | 109 | DCHECK(_all_read_sources.contains(tablet->tablet_id())); |
213 | 109 | auto& entire_read_source = _all_read_sources[tablet->tablet_id()]; |
214 | | |
215 | 109 | if (config::is_cloud_mode()) { |
216 | | // FIXME(plat1ko): Avoid pointer cast |
217 | 109 | ExecEnv::GetInstance()->storage_engine().to_cloud().tablet_hotspot().count(*tablet); |
218 | 109 | } |
219 | | |
220 | | // For each RowSet split in the read source, split by segment id and build |
221 | | // one scanner per segment. Keep delete predicates shared. |
222 | 218 | for (auto& rs_split : entire_read_source.rs_splits) { |
223 | 218 | auto reader = rs_split.rs_reader; |
224 | 218 | auto rowset = reader->rowset(); |
225 | 218 | const auto rowset_id = rowset->rowset_id(); |
226 | 218 | const auto& segments_rows = _all_segments_rows[rowset_id]; |
227 | 218 | if (segments_rows.empty() || rowset->num_rows() == 0) { |
228 | 171 | continue; |
229 | 171 | } |
230 | | |
231 | | // Build scanners for [i, i+1) segment range, without row-range slicing. |
232 | 94 | for (int64_t i = 0; i < rowset->num_segments(); ++i) { |
233 | 47 | RowSetSplits split(reader->clone()); |
234 | 47 | split.segment_offsets.first = i; |
235 | 47 | split.segment_offsets.second = i + 1; |
236 | | // No row-ranges slicing; scan whole segment i. |
237 | 47 | DCHECK_GE(split.segment_offsets.second, split.segment_offsets.first + 1); |
238 | | |
239 | 47 | TabletReadSource partitial_read_source; |
240 | 47 | partitial_read_source.rs_splits.emplace_back(std::move(split)); |
241 | | |
242 | 47 | scanners.emplace_back(_build_scanner( |
243 | 47 | tablet, version, _key_ranges, |
244 | 47 | {.rs_splits = std::move(partitial_read_source.rs_splits), |
245 | 47 | .delete_predicates = entire_read_source.delete_predicates, |
246 | 47 | .delete_bitmap = entire_read_source.delete_bitmap}, |
247 | 47 | take_initial_file_cache_stats(&_tablet_preload_file_cache_stats, |
248 | 47 | tablet->tablet_id()))); |
249 | 47 | } |
250 | 47 | } |
251 | 109 | } |
252 | | |
253 | 19 | return Status::OK(); |
254 | 19 | } |
255 | | |
256 | | /** |
257 | | * Load rowsets of each tablet with specified version, segments of each rowset. |
258 | | */ |
259 | 119k | Status ParallelScannerBuilder::_load() { |
260 | 119k | _total_rows = 0; |
261 | 119k | size_t idx = 0; |
262 | 119k | bool enable_segment_cache = _state->query_options().__isset.enable_segment_cache |
263 | 120k | ? _state->query_options().enable_segment_cache |
264 | 18.4E | : true; |
265 | 508k | for (auto&& [tablet, version] : _tablets) { |
266 | 508k | const auto tablet_id = tablet->tablet_id(); |
267 | 508k | _all_read_sources[tablet_id] = _read_sources[idx]; |
268 | 508k | const auto& read_source = _all_read_sources[tablet_id]; |
269 | 2.13M | for (auto& rs_split : read_source.rs_splits) { |
270 | 2.13M | auto rowset = rs_split.rs_reader->rowset(); |
271 | 2.13M | RETURN_IF_ERROR(rowset->load()); |
272 | 2.13M | const auto rowset_id = rowset->rowset_id(); |
273 | | |
274 | 2.13M | auto beta_rowset = std::dynamic_pointer_cast<BetaRowset>(rowset); |
275 | 2.13M | std::vector<uint32_t> segment_rows; |
276 | 2.13M | OlapReaderStatistics preload_stats; |
277 | 2.13M | auto preload_io_ctx = create_preload_io_context(_state, &preload_stats); |
278 | 2.13M | RETURN_IF_ERROR(beta_rowset->get_segment_num_rows(&segment_rows, enable_segment_cache, |
279 | 2.13M | &preload_stats, &preload_io_ctx)); |
280 | 2.13M | _tablet_preload_file_cache_stats[tablet_id].merge_from(preload_stats.file_cache_stats); |
281 | 2.13M | auto segment_count = rowset->num_segments(); |
282 | 2.78M | for (int64_t i = 0; i != segment_count; i++) { |
283 | 652k | _all_segments_rows[rowset_id].emplace_back(segment_rows[i]); |
284 | 652k | } |
285 | 2.13M | _total_rows += rowset->num_rows(); |
286 | 2.13M | } |
287 | 508k | idx++; |
288 | 508k | } |
289 | | |
290 | 119k | _rows_per_scanner = _total_rows / _max_scanners_count; |
291 | 119k | _rows_per_scanner = std::max<size_t>(_rows_per_scanner, _min_rows_per_scanner); |
292 | | |
293 | 119k | return Status::OK(); |
294 | 119k | } |
295 | | |
296 | | std::shared_ptr<OlapScanner> ParallelScannerBuilder::_build_scanner( |
297 | | BaseTabletSPtr tablet, int64_t version, const std::vector<OlapScanRange*>& key_ranges, |
298 | 343k | TabletReadSource&& read_source, io::FileCacheStatistics&& initial_file_cache_stats) { |
299 | 343k | OlapScanner::Params params { |
300 | 343k | .state = _state, |
301 | 343k | .profile = _scanner_profile.get(), |
302 | 343k | .key_ranges = key_ranges, |
303 | 343k | .tablet = std::move(tablet), |
304 | 343k | .version = version, |
305 | 343k | .read_source = std::move(read_source), |
306 | 343k | .initial_file_cache_stats = std::move(initial_file_cache_stats), |
307 | 343k | .limit = _limit, |
308 | 343k | .aggregation = _is_preaggregation, |
309 | 343k | .read_row_binlog = false, |
310 | 343k | .binlog_scan_type = TBinlogScanType::NONE, |
311 | 343k | .start_tso = std::nullopt, |
312 | 343k | .end_tso = std::nullopt, |
313 | 343k | }; |
314 | 343k | return OlapScanner::create_shared(_parent, std::move(params)); |
315 | 343k | } |
316 | | |
317 | | } // namespace doris |