be/src/service/point_query_executor.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 "service/point_query_executor.h" |
19 | | |
20 | | #include <fmt/format.h> |
21 | | #include <gen_cpp/Descriptors_types.h> |
22 | | #include <gen_cpp/Exprs_types.h> |
23 | | #include <gen_cpp/PaloInternalService_types.h> |
24 | | #include <gen_cpp/internal_service.pb.h> |
25 | | #include <glog/logging.h> |
26 | | #include <google/protobuf/extension_set.h> |
27 | | #include <stdlib.h> |
28 | | |
29 | | #include <memory> |
30 | | #include <optional> |
31 | | #include <unordered_map> |
32 | | #include <vector> |
33 | | |
34 | | #include "cloud/cloud_tablet.h" |
35 | | #include "cloud/config.h" |
36 | | #include "common/cast_set.h" |
37 | | #include "common/consts.h" |
38 | | #include "common/status.h" |
39 | | #include "core/data_type/data_type_factory.hpp" |
40 | | #include "core/data_type_serde/data_type_serde.h" |
41 | | #include "exec/sink/writer/vmysql_result_writer.h" |
42 | | #include "exprs/vexpr.h" |
43 | | #include "exprs/vexpr_context.h" |
44 | | #include "exprs/vexpr_fwd.h" |
45 | | #include "exprs/vslot_ref.h" |
46 | | #include "io/cache/remote_scan_cache_write_limiter.h" |
47 | | #include "io/io_common.h" |
48 | | #include "runtime/descriptors.h" |
49 | | #include "runtime/exec_env.h" |
50 | | #include "runtime/result_block_buffer.h" |
51 | | #include "runtime/runtime_profile.h" |
52 | | #include "runtime/runtime_state.h" |
53 | | #include "runtime/thread_context.h" |
54 | | #include "storage/read_time_hidden_column.h" |
55 | | #include "storage/row_cursor.h" |
56 | | #include "storage/rowset/beta_rowset.h" |
57 | | #include "storage/rowset/rowset_fwd.h" |
58 | | #include "storage/schema.h" |
59 | | #include "storage/segment/column_reader.h" |
60 | | #include "storage/tablet/tablet_schema.h" |
61 | | #include "storage/utils.h" |
62 | | #include "util/defer_op.h" |
63 | | #include "util/jsonb/serialize.h" |
64 | | #include "util/lru_cache.h" |
65 | | #include "util/simd/bits.h" |
66 | | #include "util/thrift_util.h" |
67 | | |
68 | | namespace doris { |
69 | | |
70 | 0 | static BetaRowsetSharedPtr get_beta_rowset(RowsetSharedPtr* rowset_ptr) { |
71 | 0 | DORIS_CHECK(rowset_ptr != nullptr); |
72 | 0 | DORIS_CHECK(*rowset_ptr != nullptr); |
73 | 0 | return std::static_pointer_cast<BetaRowset>(*rowset_ptr); |
74 | 0 | } |
75 | | |
76 | | class PointQueryResultBlockBuffer final : public MySQLResultBlockBuffer { |
77 | | public: |
78 | 0 | PointQueryResultBlockBuffer(RuntimeState* state) : MySQLResultBlockBuffer(state) {} |
79 | 0 | ~PointQueryResultBlockBuffer() override = default; |
80 | 0 | std::shared_ptr<TFetchDataResult> get_block() { |
81 | 0 | std::lock_guard<std::mutex> l(_lock); |
82 | 0 | DCHECK_EQ(_result_batch_queue.size(), 1); |
83 | 0 | auto result = std::move(_result_batch_queue.front()); |
84 | 0 | _result_batch_queue.pop_front(); |
85 | 0 | return result; |
86 | 0 | } |
87 | | }; |
88 | | |
89 | 4.10k | Reusable::~Reusable() = default; |
90 | | |
91 | | // get missing and include column ids |
92 | | // input include_cids : the output expr slots columns unique ids |
93 | | // missing_cids : the output expr columns that not in row columns cids |
94 | | static void get_missing_and_include_cids(const TabletSchema& schema, |
95 | | const std::vector<SlotDescriptor*>& slots, |
96 | | int target_rs_column_id, bool has_delete_sign, |
97 | | std::unordered_set<int>& missing_cids, |
98 | 4.05k | std::unordered_set<int>& include_cids) { |
99 | 4.05k | missing_cids.clear(); |
100 | 4.05k | include_cids.clear(); |
101 | 4.06k | for (auto* slot : slots) { |
102 | 4.06k | missing_cids.insert(slot->col_unique_id()); |
103 | 4.06k | } |
104 | 4.05k | if (has_delete_sign) { |
105 | 1 | missing_cids.insert(schema.columns()[schema.delete_sign_idx()]->unique_id()); |
106 | 1 | } |
107 | 4.05k | if (target_rs_column_id == -1) { |
108 | | // no row store columns |
109 | 4.04k | return; |
110 | 4.04k | } |
111 | 5 | const TabletColumn& target_rs_column = schema.column_by_uid(target_rs_column_id); |
112 | 5 | DCHECK(target_rs_column.is_row_store_column()); |
113 | | // An empty row_columns_uids() means the row-store column contains the full row. Keep |
114 | | // missing_cids empty so full-row point queries can be served entirely from the row store, |
115 | | // including row-cache hits. Reusable::init then takes a projected VERSION/COMMIT_TSO out of |
116 | | // the JSONB decode and the row cache: for example, JSONB stores 0 for __DORIS_VERSION_COL__, |
117 | | // while its published value must be obtained from the rowset. |
118 | 5 | if (schema.row_columns_uids().empty()) { |
119 | 4 | missing_cids.clear(); |
120 | 4 | return; |
121 | 4 | } |
122 | 4 | for (int cid : schema.row_columns_uids()) { |
123 | 4 | missing_cids.erase(cid); |
124 | 4 | include_cids.insert(cid); |
125 | 4 | } |
126 | 1 | } |
127 | | |
128 | | static void extract_slot_ref(const VExprSPtr& expr, TupleDescriptor* tuple_desc, |
129 | 4.06k | std::vector<SlotDescriptor*>& slots) { |
130 | 4.06k | const auto& children = expr->children(); |
131 | 4.06k | for (const auto& i : children) { |
132 | 0 | extract_slot_ref(i, tuple_desc, slots); |
133 | 0 | } |
134 | | |
135 | 4.06k | auto node_type = expr->node_type(); |
136 | 4.06k | if (node_type == TExprNodeType::SLOT_REF) { |
137 | 4.06k | int column_id = static_cast<const VSlotRef*>(expr.get())->column_id(); |
138 | 4.06k | auto* slot_desc = tuple_desc->slots()[column_id]; |
139 | 4.06k | slots.push_back(slot_desc); |
140 | 4.06k | } |
141 | 4.06k | } |
142 | | |
143 | | Status Reusable::init(const TDescriptorTable& t_desc_tbl, const std::vector<TExpr>& output_exprs, |
144 | | const TQueryOptions& query_options, const TabletSchema& schema, |
145 | 4.05k | size_t block_size) { |
146 | 4.05k | _runtime_state = RuntimeState::create_unique(); |
147 | 4.05k | _runtime_state->set_query_options(query_options); |
148 | 4.05k | RETURN_IF_ERROR(DescriptorTbl::create(_runtime_state->obj_pool(), t_desc_tbl, &_desc_tbl)); |
149 | 4.05k | _runtime_state->set_desc_tbl(_desc_tbl); |
150 | 89.0k | for (const auto* slot : tuple_desc()->slots()) { |
151 | 89.0k | if (!slot->all_access_paths().empty() || !slot->predicate_access_paths().empty()) { |
152 | 0 | return Status::InternalError( |
153 | 0 | "Short-circuit point query does not support nested column access paths, " |
154 | 0 | "slot: {}. Please upgrade FE to disable nested column pruning for " |
155 | 0 | "short-circuit point queries.", |
156 | 0 | slot->col_name()); |
157 | 0 | } |
158 | 89.0k | } |
159 | 4.05k | _block_pool.resize(block_size); |
160 | 8.10k | for (auto& i : _block_pool) { |
161 | 8.10k | i = Block::create_unique(tuple_desc()->slots(), 2); |
162 | | // Name is useless but cost space |
163 | 8.10k | i->clear_names(); |
164 | 8.10k | } |
165 | | |
166 | 4.05k | RETURN_IF_ERROR(VExpr::create_expr_trees(output_exprs, _output_exprs_ctxs)); |
167 | 4.05k | RowDescriptor row_desc(tuple_desc()); |
168 | | // Prepare the exprs to run. |
169 | 4.05k | RETURN_IF_ERROR(VExpr::prepare(_output_exprs_ctxs, _runtime_state.get(), row_desc)); |
170 | 4.05k | RETURN_IF_ERROR(VExpr::open(_output_exprs_ctxs, _runtime_state.get())); |
171 | 4.05k | _create_timestamp = butil::gettimeofday_ms(); |
172 | 4.05k | _data_type_serdes = create_data_type_serdes(tuple_desc()->slots()); |
173 | 4.05k | _col_default_values.resize(tuple_desc()->slots().size()); |
174 | 4.05k | bool has_delete_sign = false; |
175 | 93.0k | for (int i = 0; i < tuple_desc()->slots().size(); ++i) { |
176 | 89.0k | auto* slot = tuple_desc()->slots()[i]; |
177 | 89.0k | if (slot->col_name() == DELETE_SIGN) { |
178 | 1 | has_delete_sign = true; |
179 | 1 | } |
180 | 89.0k | _col_uid_to_idx[slot->col_unique_id()] = i; |
181 | 89.0k | _col_default_values[i] = slot->col_default_value(); |
182 | 89.0k | } |
183 | | |
184 | | // Get the output slot descriptors |
185 | 4.05k | std::vector<SlotDescriptor*> output_slot_descs; |
186 | 4.06k | for (const auto& expr : _output_exprs_ctxs) { |
187 | 4.06k | extract_slot_ref(expr->root(), tuple_desc(), output_slot_descs); |
188 | 4.06k | } |
189 | | |
190 | | // get the delete sign idx in block |
191 | 4.05k | if (has_delete_sign) { |
192 | 1 | _delete_sign_idx = _col_uid_to_idx[schema.columns()[schema.delete_sign_idx()]->unique_id()]; |
193 | 1 | } |
194 | | |
195 | 4.05k | if (schema.have_column(BeConsts::ROW_STORE_COL)) { |
196 | 6 | const auto& column = *DORIS_TRY(schema.column(BeConsts::ROW_STORE_COL)); |
197 | 6 | _row_store_column_ids = column.unique_id(); |
198 | 6 | } |
199 | 4.05k | get_missing_and_include_cids(schema, output_slot_descs, _row_store_column_ids, has_delete_sign, |
200 | 4.05k | _missing_col_uids, _include_col_uids); |
201 | 4.05k | _column_store_col_uids = _missing_col_uids; |
202 | | // VERSION/COMMIT_TSO never come from the row-store JSONB, see row_store_value_may_be_stale(). |
203 | | // Serve them like the columns the row store lacks and keep the JSONB decode away from them. |
204 | 4.05k | std::unordered_set<int32_t> rowset_derived_hidden_uids; |
205 | 4.06k | for (const auto* slot : output_slot_descs) { |
206 | 4.06k | const int32_t column_uid = slot->col_unique_id(); |
207 | 4.06k | if (row_store_value_may_be_stale(get_read_time_hidden_column_type(schema, column_uid))) { |
208 | 10 | rowset_derived_hidden_uids.insert(column_uid); |
209 | 10 | } |
210 | 4.06k | } |
211 | 4.05k | _has_rowset_derived_hidden_columns = !rowset_derived_hidden_uids.empty(); |
212 | 4.05k | _column_store_col_uids.insert(rowset_derived_hidden_uids.begin(), |
213 | 4.05k | rowset_derived_hidden_uids.end()); |
214 | 4.05k | if (_row_store_column_ids != -1 && _has_rowset_derived_hidden_columns) { |
215 | 4 | if (_include_col_uids.empty()) { |
216 | | // A full row store decodes every slot; name them so the hidden ones can be left out. |
217 | 6 | for (const auto* slot : tuple_desc()->slots()) { |
218 | 6 | _include_col_uids.insert(slot->col_unique_id()); |
219 | 6 | } |
220 | 2 | } |
221 | 7 | for (int32_t column_uid : rowset_derived_hidden_uids) { |
222 | 7 | _include_col_uids.erase(column_uid); |
223 | 7 | } |
224 | | // An empty include set would mean "every slot" to the decoder again. |
225 | 4 | _decode_row_store = !_include_col_uids.empty(); |
226 | 4 | } |
227 | | |
228 | 4.05k | return Status::OK(); |
229 | 4.05k | } |
230 | | |
231 | 0 | std::unique_ptr<Block> Reusable::get_block() { |
232 | 0 | std::lock_guard lock(_block_mutex); |
233 | 0 | if (_block_pool.empty()) { |
234 | 0 | auto block = Block::create_unique(tuple_desc()->slots(), 2); |
235 | | // Name is useless but cost space |
236 | 0 | block->clear_names(); |
237 | 0 | return block; |
238 | 0 | } |
239 | 0 | auto block = std::move(_block_pool.back()); |
240 | 0 | CHECK(block != nullptr); |
241 | 0 | _block_pool.pop_back(); |
242 | 0 | return block; |
243 | 0 | } |
244 | | |
245 | 0 | void Reusable::return_block(std::unique_ptr<Block>& block) { |
246 | 0 | std::lock_guard lock(_block_mutex); |
247 | 0 | if (block == nullptr) { |
248 | 0 | return; |
249 | 0 | } |
250 | 0 | block->clear_column_data(); |
251 | 0 | _block_pool.push_back(std::move(block)); |
252 | 0 | const auto block_pool_size = config::get_lookup_connection_cache_block_pool_size(); |
253 | 0 | if (_block_pool.size() > block_pool_size) { |
254 | 0 | _block_pool.resize(block_pool_size); |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | 0 | LookupConnectionCache* LookupConnectionCache::create_global_instance(size_t capacity) { |
259 | 0 | DCHECK(ExecEnv::GetInstance()->get_lookup_connection_cache() == nullptr); |
260 | 0 | auto* res = new LookupConnectionCache(capacity); |
261 | 0 | return res; |
262 | 0 | } |
263 | | |
264 | | RowCache::RowCache(int64_t capacity, int num_shards) |
265 | 5 | : LRUCachePolicy(CachePolicy::CacheType::POINT_QUERY_ROW_CACHE, capacity, |
266 | 5 | LRUCacheType::SIZE, config::point_query_row_cache_stale_sweep_time_sec, |
267 | 5 | num_shards, /*element count capacity */ 0, |
268 | 5 | /*enable prune*/ true, /*is lru-k*/ true) {} |
269 | | |
270 | | // Create global instance of this class |
271 | 0 | RowCache* RowCache::create_global_cache(int64_t capacity, uint32_t num_shards) { |
272 | 0 | DCHECK(ExecEnv::GetInstance()->get_row_cache() == nullptr); |
273 | 0 | auto* res = new RowCache(capacity, num_shards); |
274 | 0 | return res; |
275 | 0 | } |
276 | | |
277 | 12 | RowCache* RowCache::instance() { |
278 | 12 | return ExecEnv::GetInstance()->get_row_cache(); |
279 | 12 | } |
280 | | |
281 | 10 | bool RowCache::lookup(const RowCacheKey& key, CacheHandle* handle) { |
282 | 10 | const std::string& encoded_key = key.encode(); |
283 | 10 | auto* lru_handle = LRUCachePolicy::lookup(encoded_key); |
284 | 10 | if (!lru_handle) { |
285 | | // cache miss |
286 | 5 | return false; |
287 | 5 | } |
288 | 5 | *handle = CacheHandle(this, lru_handle); |
289 | 5 | return true; |
290 | 10 | } |
291 | | |
292 | 15 | void RowCache::insert(const RowCacheKey& key, const Slice& value) { |
293 | 15 | char* cache_value = static_cast<char*>(malloc(value.size)); |
294 | 15 | memcpy(cache_value, value.data, value.size); |
295 | 15 | auto* row_cache_value = new RowCacheValue; |
296 | 15 | row_cache_value->cache_value = cache_value; |
297 | 15 | const std::string& encoded_key = key.encode(); |
298 | 15 | auto* handle = LRUCachePolicy::insert(encoded_key, row_cache_value, value.size, value.size, |
299 | 15 | CachePriority::NORMAL); |
300 | | // handle will released |
301 | 15 | auto tmp = CacheHandle {this, handle}; |
302 | 15 | } |
303 | | |
304 | 3 | void RowCache::erase(const RowCacheKey& key) { |
305 | 3 | const std::string& encoded_key = key.encode(); |
306 | 3 | LRUCachePolicy::erase(encoded_key); |
307 | 3 | } |
308 | | |
309 | 2.09k | LookupConnectionCache::CacheValue::~CacheValue() { |
310 | 2.09k | SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER( |
311 | 2.09k | ExecEnv::GetInstance()->point_query_executor_mem_tracker()); |
312 | 2.09k | item.reset(); |
313 | 2.09k | } |
314 | | |
315 | 0 | PointQueryExecutor::~PointQueryExecutor() { |
316 | 0 | SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER( |
317 | 0 | ExecEnv::GetInstance()->point_query_executor_mem_tracker()); |
318 | 0 | _tablet.reset(); |
319 | 0 | _reusable.reset(); |
320 | 0 | _result_block.reset(); |
321 | 0 | _row_read_ctxs.clear(); |
322 | 0 | } |
323 | | |
324 | 0 | void PointQueryExecutor::_init_remote_scan_cache_write_limiter() { |
325 | 0 | const auto& query_options = _reusable->runtime_state()->query_options(); |
326 | 0 | const bool initialize_remote_scan_cache_write_limiter = |
327 | 0 | config::is_cloud_mode() && config::enable_file_cache && |
328 | 0 | query_options.__isset.file_cache_query_limit_bytes && |
329 | 0 | query_options.file_cache_query_limit_bytes >= 0 && |
330 | 0 | query_options.query_type == TQueryType::SELECT; |
331 | 0 | if (initialize_remote_scan_cache_write_limiter) { |
332 | 0 | _remote_scan_cache_write_limiter = std::make_unique<io::RemoteScanCacheWriteLimiter>( |
333 | 0 | _reusable->runtime_state()->query_id(), query_options.file_cache_query_limit_bytes); |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | Status PointQueryExecutor::init(const PTabletKeyLookupRequest* request, |
338 | 0 | PTabletKeyLookupResponse* response) { |
339 | 0 | SCOPED_TIMER(&_profile_metrics.init_ns); |
340 | 0 | _response = response; |
341 | | // using cache |
342 | 0 | __int128_t uuid = |
343 | 0 | static_cast<__int128_t>(request->uuid().uuid_high()) << 64 | request->uuid().uuid_low(); |
344 | 0 | SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->point_query_executor_mem_tracker()); |
345 | 0 | auto cache_handle = LookupConnectionCache::instance()->get(uuid); |
346 | 0 | _binary_row_format = request->is_binary_row(); |
347 | 0 | _tablet = DORIS_TRY(ExecEnv::get_tablet(request->tablet_id())); |
348 | 0 | if (cache_handle != nullptr) { |
349 | 0 | _reusable = cache_handle; |
350 | 0 | _profile_metrics.hit_lookup_cache = true; |
351 | 0 | } else { |
352 | | // Lightweight request: FE may omit reusable query context and rely on uuid cache. |
353 | | // If cache miss and required parameters are absent, ask FE to resend a full request. |
354 | 0 | if (uuid != 0 && (!request->has_desc_tbl() || request->desc_tbl().empty() || |
355 | 0 | !request->has_output_expr() || request->output_expr().empty() || |
356 | 0 | !request->has_query_options() || request->query_options().empty())) { |
357 | 0 | if (VLOG_DEBUG_IS_ON) { |
358 | 0 | VLOG_DEBUG << "lookup connection cache miss, ask FE to resend query context" |
359 | 0 | << ", tablet_id=" << request->tablet_id() |
360 | 0 | << ", uuid_high=" << request->uuid().uuid_high() |
361 | 0 | << ", uuid_low=" << request->uuid().uuid_low(); |
362 | 0 | } |
363 | 0 | response->set_need_resend_query_context(true); |
364 | 0 | return Status::OK(); |
365 | 0 | } |
366 | 0 | if (uuid == 0 && (!request->has_desc_tbl() || request->desc_tbl().empty() || |
367 | 0 | !request->has_output_expr() || request->output_expr().empty())) { |
368 | 0 | return Status::InvalidArgument( |
369 | 0 | "tablet_fetch_data requires desc_tbl/output_expr when uuid is not set"); |
370 | 0 | } |
371 | | // init handle |
372 | 0 | auto reusable_ptr = std::make_shared<Reusable>(); |
373 | 0 | TDescriptorTable t_desc_tbl; |
374 | 0 | TExprList t_output_exprs; |
375 | 0 | auto len = cast_set<uint32_t>(request->desc_tbl().size()); |
376 | 0 | RETURN_IF_ERROR( |
377 | 0 | deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(request->desc_tbl().data()), |
378 | 0 | &len, false, &t_desc_tbl)); |
379 | 0 | len = cast_set<uint32_t>(request->output_expr().size()); |
380 | 0 | RETURN_IF_ERROR(deserialize_thrift_msg( |
381 | 0 | reinterpret_cast<const uint8_t*>(request->output_expr().data()), &len, false, |
382 | 0 | &t_output_exprs)); |
383 | 0 | _reusable = reusable_ptr; |
384 | 0 | TQueryOptions t_query_options; |
385 | 0 | len = cast_set<uint32_t>(request->query_options().size()); |
386 | 0 | if (request->has_query_options()) { |
387 | 0 | RETURN_IF_ERROR(deserialize_thrift_msg( |
388 | 0 | reinterpret_cast<const uint8_t*>(request->query_options().data()), &len, false, |
389 | 0 | &t_query_options)); |
390 | 0 | } |
391 | 0 | RETURN_IF_ERROR(reusable_ptr->init(t_desc_tbl, t_output_exprs.exprs, t_query_options, |
392 | 0 | *_tablet->tablet_schema(), 1)); |
393 | 0 | if (uuid != 0) { |
394 | 0 | LookupConnectionCache::instance()->add(uuid, reusable_ptr); |
395 | 0 | } |
396 | 0 | } |
397 | 0 | _init_remote_scan_cache_write_limiter(); |
398 | | // Set timezone from request for functions like from_unixtime() |
399 | 0 | if (request->has_time_zone() && !request->time_zone().empty()) { |
400 | 0 | _reusable->runtime_state()->set_timezone(request->time_zone()); |
401 | 0 | } |
402 | 0 | if (request->has_version() && request->version() >= 0) { |
403 | 0 | _version = request->version(); |
404 | 0 | } |
405 | 0 | RETURN_IF_ERROR(_init_keys(request)); |
406 | 0 | _result_block = _reusable->get_block(); |
407 | 0 | CHECK(_result_block != nullptr); |
408 | |
|
409 | 0 | return Status::OK(); |
410 | 0 | } |
411 | | |
412 | 0 | Status PointQueryExecutor::lookup_up() { |
413 | 0 | SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->point_query_executor_mem_tracker()); |
414 | 0 | RETURN_IF_ERROR(_lookup_row_key()); |
415 | 0 | RETURN_IF_ERROR(_lookup_row_data()); |
416 | 0 | RETURN_IF_ERROR(_output_data()); |
417 | 0 | return Status::OK(); |
418 | 0 | } |
419 | | |
420 | 0 | void PointQueryExecutor::print_profile() { |
421 | 0 | auto init_us = _profile_metrics.init_ns.value() / 1000; |
422 | 0 | auto init_key_us = _profile_metrics.init_key_ns.value() / 1000; |
423 | 0 | auto lookup_key_us = _profile_metrics.lookup_key_ns.value() / 1000; |
424 | 0 | auto lookup_data_us = _profile_metrics.lookup_data_ns.value() / 1000; |
425 | 0 | auto output_data_us = _profile_metrics.output_data_ns.value() / 1000; |
426 | 0 | auto load_segments_key_us = _profile_metrics.load_segment_key_stage_ns.value() / 1000; |
427 | 0 | auto load_segments_data_us = _profile_metrics.load_segment_data_stage_ns.value() / 1000; |
428 | 0 | auto total_us = init_us + lookup_key_us + lookup_data_us + output_data_us; |
429 | 0 | auto read_stats = _profile_metrics.read_stats; |
430 | 0 | const std::string stats_str = fmt::format( |
431 | 0 | "[lookup profile:{}us] init:{}us, init_key:{}us," |
432 | 0 | " lookup_key:{}us, load_segments_key:{}us, lookup_data:{}us, load_segments_data:{}us," |
433 | 0 | " output_data:{}us, " |
434 | 0 | "hit_lookup_cache:{}" |
435 | 0 | ", is_binary_row:{}, output_columns:{}, total_keys:{}, row_cache_hits:{}" |
436 | 0 | ", hit_cached_pages:{}, total_pages_read:{}, compressed_bytes_read:{}, " |
437 | 0 | "io_latency:{}ns, " |
438 | 0 | "uncompressed_bytes_read:{}, result_data_bytes:{}, row_hits:{}" |
439 | 0 | ", rs_column_uid:{}, bytes_read_from_local:{}, bytes_read_from_remote:{}, " |
440 | 0 | "local_io_timer:{}, remote_io_timer:{}, local_write_timer:{}", |
441 | 0 | total_us, init_us, init_key_us, lookup_key_us, load_segments_key_us, lookup_data_us, |
442 | 0 | load_segments_data_us, output_data_us, _profile_metrics.hit_lookup_cache, |
443 | 0 | _binary_row_format, _reusable->output_exprs().size(), _row_read_ctxs.size(), |
444 | 0 | _profile_metrics.row_cache_hits, read_stats.cached_pages_num, |
445 | 0 | read_stats.total_pages_num, read_stats.compressed_bytes_read, read_stats.io_ns, |
446 | 0 | read_stats.uncompressed_bytes_read, _profile_metrics.result_data_bytes, _row_hits, |
447 | 0 | _reusable->rs_column_uid(), |
448 | 0 | _profile_metrics.read_stats.file_cache_stats.bytes_read_from_local, |
449 | 0 | _profile_metrics.read_stats.file_cache_stats.bytes_read_from_remote, |
450 | 0 | _profile_metrics.read_stats.file_cache_stats.local_io_timer, |
451 | 0 | _profile_metrics.read_stats.file_cache_stats.remote_io_timer, |
452 | 0 | _profile_metrics.read_stats.file_cache_stats.write_cache_io_timer); |
453 | |
|
454 | 0 | constexpr static int kSlowThreholdUs = 50 * 1000; // 50ms |
455 | 0 | if (total_us > kSlowThreholdUs) { |
456 | 0 | LOG(WARNING) << "slow query, " << stats_str; |
457 | 0 | } else if (VLOG_DEBUG_IS_ON) { |
458 | 0 | VLOG_DEBUG << stats_str; |
459 | 0 | } else { |
460 | 0 | LOG_EVERY_N(INFO, 1000) << stats_str; |
461 | 0 | } |
462 | 0 | } |
463 | | |
464 | 0 | Status PointQueryExecutor::_init_keys(const PTabletKeyLookupRequest* request) { |
465 | 0 | SCOPED_TIMER(&_profile_metrics.init_key_ns); |
466 | 0 | const auto& schema = _tablet->tablet_schema(); |
467 | | // Point query is only supported on merge-on-write unique key tables. |
468 | 0 | DCHECK(schema->keys_type() == UNIQUE_KEYS && _tablet->enable_unique_key_merge_on_write()); |
469 | 0 | if (schema->keys_type() != UNIQUE_KEYS || !_tablet->enable_unique_key_merge_on_write()) { |
470 | 0 | return Status::InvalidArgument( |
471 | 0 | "Point query is only supported on merge-on-write unique key tables, " |
472 | 0 | "tablet_id={}", |
473 | 0 | _tablet->tablet_id()); |
474 | 0 | } |
475 | | // 1. get primary key from conditions |
476 | 0 | _row_read_ctxs.resize(request->key_tuples().size()); |
477 | | // get row cursor and encode keys |
478 | 0 | for (int i = 0; i < request->key_tuples().size(); ++i) { |
479 | 0 | const KeyTuple& key_tuple = request->key_tuples(i); |
480 | 0 | if (UNLIKELY(cast_set<size_t>(key_tuple.key_column_literals_size()) != |
481 | 0 | schema->num_key_columns())) { |
482 | 0 | return Status::InvalidArgument( |
483 | 0 | "Key column count mismatch. expected={}, actual={}, tablet_id={}", |
484 | 0 | schema->num_key_columns(), key_tuple.key_column_literals_size(), |
485 | 0 | _tablet->tablet_id()); |
486 | 0 | } |
487 | 0 | RowCursor cursor; |
488 | 0 | std::vector<Field> key_fields; |
489 | 0 | key_fields.reserve(key_tuple.key_column_literals_size()); |
490 | 0 | for (int j = 0; j < key_tuple.key_column_literals_size(); ++j) { |
491 | 0 | const auto& literal_bytes = key_tuple.key_column_literals(j); |
492 | 0 | TExprNode expr_node; |
493 | 0 | auto len = cast_set<uint32_t>(literal_bytes.size()); |
494 | 0 | RETURN_IF_ERROR( |
495 | 0 | deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(literal_bytes.data()), |
496 | 0 | &len, false, &expr_node)); |
497 | 0 | const auto& col = schema->column(j); |
498 | 0 | auto data_type = DataTypeFactory::instance().create_data_type( |
499 | 0 | col.type(), col.precision(), col.frac(), col.length()); |
500 | 0 | key_fields.push_back(data_type->get_field(expr_node)); |
501 | 0 | } |
502 | 0 | RETURN_IF_ERROR(cursor.init_scan_key(_tablet->tablet_schema(), std::move(key_fields))); |
503 | 0 | cursor.encode_key_with_padding<true>(&_row_read_ctxs[i]._primary_key, |
504 | 0 | _tablet->tablet_schema()->num_key_columns(), true); |
505 | 0 | } |
506 | 0 | return Status::OK(); |
507 | 0 | } |
508 | | |
509 | 0 | Status PointQueryExecutor::_lookup_row_key() { |
510 | 0 | SCOPED_TIMER(&_profile_metrics.lookup_key_ns); |
511 | | // 2. lookup row location |
512 | 0 | Status st; |
513 | 0 | if (_version >= 0) { |
514 | 0 | CHECK(config::is_cloud_mode()) << "Only cloud mode support snapshot read at present"; |
515 | 0 | SyncOptions options; |
516 | 0 | options.query_version = _version; |
517 | 0 | RETURN_IF_ERROR(std::dynamic_pointer_cast<CloudTablet>(_tablet)->sync_rowsets(options)); |
518 | 0 | } |
519 | 0 | std::vector<RowsetSharedPtr> specified_rowsets; |
520 | 0 | { |
521 | 0 | std::shared_lock rlock(_tablet->get_header_lock()); |
522 | 0 | specified_rowsets = _tablet->get_rowset_by_ids(nullptr); |
523 | 0 | } |
524 | 0 | io::IOContext io_ctx; |
525 | 0 | io_ctx.reader_type = ReaderType::READER_QUERY; |
526 | 0 | io_ctx.file_cache_stats = &_profile_metrics.read_stats.file_cache_stats; |
527 | 0 | io_ctx.remote_scan_cache_write_limiter = _remote_scan_cache_write_limiter.get(); |
528 | 0 | std::vector<std::unique_ptr<SegmentCacheHandle>> segment_caches(specified_rowsets.size()); |
529 | 0 | for (size_t i = 0; i < _row_read_ctxs.size(); ++i) { |
530 | 0 | RowLocation location; |
531 | | // The row cache contains the physical JSONB row but not the owning rowset's version/TSO. |
532 | | // A query projecting read-time hidden columns must resolve the rowset before decoding it. |
533 | 0 | if (!config::disable_storage_row_cache && !_reusable->has_rowset_derived_hidden_columns()) { |
534 | 0 | RowCache::CacheHandle cache_handle; |
535 | 0 | auto hit_cache = RowCache::instance()->lookup( |
536 | 0 | {_tablet->tablet_id(), _row_read_ctxs[i]._primary_key}, &cache_handle); |
537 | 0 | if (hit_cache) { |
538 | 0 | _row_read_ctxs[i]._cached_row_data = std::move(cache_handle); |
539 | 0 | ++_profile_metrics.row_cache_hits; |
540 | 0 | continue; |
541 | 0 | } |
542 | 0 | } |
543 | | // Get rowlocation and rowset, ctx._rowset_ptr will acquire wrap this ptr |
544 | 0 | auto rowset_ptr = std::make_unique<RowsetSharedPtr>(); |
545 | 0 | st = (_tablet->lookup_row_key(_row_read_ctxs[i]._primary_key, nullptr, false, |
546 | 0 | specified_rowsets, &location, INT32_MAX /*rethink?*/, |
547 | 0 | segment_caches, rowset_ptr.get(), false, nullptr, |
548 | 0 | &_profile_metrics.read_stats, nullptr, &io_ctx)); |
549 | 0 | if (st.is<ErrorCode::KEY_NOT_FOUND>()) { |
550 | 0 | continue; |
551 | 0 | } |
552 | 0 | RETURN_IF_ERROR(st); |
553 | 0 | _row_read_ctxs[i]._row_location = location; |
554 | | // acquire and wrap this rowset |
555 | 0 | (*rowset_ptr)->acquire(); |
556 | 0 | VLOG_DEBUG << "aquire rowset " << (*rowset_ptr)->rowset_id(); |
557 | 0 | _row_read_ctxs[i]._rowset_ptr = std::unique_ptr<RowsetSharedPtr, decltype(&release_rowset)>( |
558 | 0 | rowset_ptr.release(), &release_rowset); |
559 | 0 | _row_hits++; |
560 | 0 | } |
561 | 0 | return Status::OK(); |
562 | 0 | } |
563 | | |
564 | 0 | Status PointQueryExecutor::_lookup_row_data() { |
565 | | // 3. get values |
566 | 0 | SCOPED_TIMER(&_profile_metrics.lookup_data_ns); |
567 | 0 | const auto missing_col_uids = _reusable->missing_col_uids(); |
568 | 0 | { |
569 | 0 | auto result_columns_guard = _result_block->mutate_columns_scoped(); |
570 | 0 | MutableColumns& result_columns = result_columns_guard.mutable_columns(); |
571 | 0 | for (size_t i = 0; i < _row_read_ctxs.size(); ++i) { |
572 | 0 | if (_row_read_ctxs[i]._cached_row_data.valid()) { |
573 | 0 | DORIS_CHECK(!_reusable->has_rowset_derived_hidden_columns()); |
574 | 0 | RETURN_IF_ERROR(JsonbSerializeUtil::jsonb_to_columns( |
575 | 0 | _reusable->get_data_type_serdes(), |
576 | 0 | _row_read_ctxs[i]._cached_row_data.data().data, |
577 | 0 | _row_read_ctxs[i]._cached_row_data.data().size, |
578 | 0 | _reusable->get_col_uid_to_idx(), result_columns, |
579 | 0 | _reusable->get_col_default_values(), _reusable->include_col_uids())); |
580 | 0 | continue; |
581 | 0 | } |
582 | 0 | if (!_row_read_ctxs[i]._row_location.has_value()) { |
583 | 0 | continue; |
584 | 0 | } |
585 | 0 | auto rowset = get_beta_rowset(_row_read_ctxs[i]._rowset_ptr.get()); |
586 | 0 | std::string value; |
587 | | // fill block by row store |
588 | 0 | if (_reusable->decode_row_store()) { |
589 | 0 | bool use_row_cache = !config::disable_storage_row_cache; |
590 | 0 | io::IOContext io_ctx; |
591 | 0 | io_ctx.reader_type = ReaderType::READER_QUERY; |
592 | 0 | io_ctx.file_cache_stats = &_profile_metrics.read_stats.file_cache_stats; |
593 | 0 | io_ctx.remote_scan_cache_write_limiter = _remote_scan_cache_write_limiter.get(); |
594 | 0 | RETURN_IF_ERROR(_tablet->lookup_row_data( |
595 | 0 | _row_read_ctxs[i]._primary_key, _row_read_ctxs[i]._row_location.value(), |
596 | 0 | *(_row_read_ctxs[i]._rowset_ptr), _profile_metrics.read_stats, value, |
597 | 0 | use_row_cache, &io_ctx)); |
598 | | // serialize value to block, currently only jsonb row format |
599 | 0 | RETURN_IF_ERROR(JsonbSerializeUtil::jsonb_to_columns( |
600 | 0 | _reusable->get_data_type_serdes(), value.data(), value.size(), |
601 | 0 | _reusable->get_col_uid_to_idx(), result_columns, |
602 | 0 | _reusable->get_col_default_values(), _reusable->include_col_uids())); |
603 | 0 | } |
604 | 0 | if (!_reusable->column_store_col_uids().empty()) { |
605 | 0 | if (!missing_col_uids.empty() && |
606 | 0 | !_reusable->runtime_state()->enable_short_circuit_query_access_column_store()) { |
607 | 0 | std::string missing_columns; |
608 | 0 | for (int cid : missing_col_uids) { |
609 | | // Named from the query, not the tablet schema: a column a light schema |
610 | | // change just added is not in the tablet schema yet, and column_by_uid |
611 | | // throws on a uid it does not hold. |
612 | 0 | const int pos = _reusable->get_col_uid_to_idx().at(cid); |
613 | 0 | missing_columns += _reusable->tuple_desc()->slots()[pos]->col_name() + ","; |
614 | 0 | } |
615 | 0 | return Status::InternalError( |
616 | 0 | "Not support column store, set store_row_column=true or " |
617 | 0 | "row_store_columns in table properties, missing columns: " + |
618 | 0 | missing_columns + " should be added to row store"); |
619 | 0 | } |
620 | | // A single-version rowset stores only placeholders for VERSION/COMMIT_TSO; the |
621 | | // value every row carries is the rowset's own version/TSO. Everything else comes |
622 | | // from column storage, including these columns once compaction materialized them. |
623 | 0 | const auto tablet_schema = _tablet->tablet_schema(); |
624 | 0 | std::vector<int32_t> column_store_reads; |
625 | 0 | for (int cid : _reusable->column_store_col_uids()) { |
626 | 0 | std::optional<Field> hidden_value; |
627 | 0 | RETURN_IF_ERROR(get_read_time_hidden_column_value( |
628 | 0 | get_read_time_hidden_column_type(*tablet_schema, cid), |
629 | 0 | rowset->version(), rowset->commit_tso(), &hidden_value)); |
630 | 0 | if (hidden_value.has_value()) { |
631 | 0 | result_columns[_reusable->get_col_uid_to_idx().at(cid)]->insert( |
632 | 0 | *hidden_value); |
633 | 0 | continue; |
634 | 0 | } |
635 | 0 | column_store_reads.push_back(cid); |
636 | 0 | } |
637 | 0 | if (column_store_reads.empty()) { |
638 | 0 | continue; |
639 | 0 | } |
640 | 0 | RowLocation row_loc = _row_read_ctxs[i]._row_location.value(); |
641 | 0 | SegmentCacheHandle segment_cache; |
642 | 0 | io::IOContext io_ctx; |
643 | 0 | io_ctx.reader_type = ReaderType::READER_QUERY; |
644 | 0 | io_ctx.file_cache_stats = &_read_stats.file_cache_stats; |
645 | 0 | io_ctx.remote_scan_cache_write_limiter = _remote_scan_cache_write_limiter.get(); |
646 | 0 | { |
647 | 0 | SCOPED_TIMER(&_profile_metrics.load_segment_data_stage_ns); |
648 | 0 | RETURN_IF_ERROR(SegmentLoader::instance()->load_segments( |
649 | 0 | rowset, &segment_cache, true, false, &_read_stats, &io_ctx)); |
650 | 0 | } |
651 | | // find segment |
652 | 0 | auto it = std::find_if(segment_cache.get_segments().cbegin(), |
653 | 0 | segment_cache.get_segments().cend(), |
654 | 0 | [&](const segment_v2::SegmentSharedPtr& seg) { |
655 | 0 | return seg->id() == row_loc.segment_id; |
656 | 0 | }); |
657 | 0 | const auto& segment = *it; |
658 | 0 | for (int cid : column_store_reads) { |
659 | 0 | int pos = _reusable->get_col_uid_to_idx().at(cid); |
660 | 0 | std::vector<segment_v2::rowid_t> row_ids { |
661 | 0 | static_cast<segment_v2::rowid_t>(row_loc.row_id)}; |
662 | 0 | auto& column = result_columns[pos]; |
663 | 0 | std::unique_ptr<ColumnIterator> iter; |
664 | 0 | SlotDescriptor* slot = _reusable->tuple_desc()->slots()[pos]; |
665 | 0 | int32_t index = slot->col_unique_id() >= 0 |
666 | 0 | ? tablet_schema->field_index(slot->col_unique_id()) |
667 | 0 | : tablet_schema->field_index(slot->col_name()); |
668 | | // A light schema change adds a column without writing anything, and |
669 | | // BaseTablet::_max_version_schema only moves forward on write, so until the |
670 | | // next load the tablet schema here does not have it. No segment holds it |
671 | | // either, so describe it from the slot and let the segment answer with its |
672 | | // default -- the same answer it gives once a load does bring the column in. |
673 | 0 | TabletColumn column_awaiting_a_load; |
674 | 0 | if (index < 0) { |
675 | 0 | column_awaiting_a_load = ReadSchema::tablet_column_from_slot(*slot); |
676 | 0 | } |
677 | 0 | const TabletColumn& read_column = |
678 | 0 | index >= 0 ? tablet_schema->column(index) : column_awaiting_a_load; |
679 | 0 | StorageReadOptions storage_read_options(_read_stats); |
680 | 0 | storage_read_options.io_ctx = io_ctx; |
681 | | // A point lookup bypasses TabletReader, so supply the rowset context here. For |
682 | | // example, reading __DORIS_VERSION_COL__ from rowset [7-7] must return 7 |
683 | | // instead of its on-disk placeholder 0. |
684 | 0 | storage_read_options.version = rowset->version(); |
685 | 0 | storage_read_options.commit_tso = rowset->commit_tso(); |
686 | 0 | RETURN_IF_ERROR(segment->seek_and_read_by_rowid( |
687 | 0 | read_column, slot, row_ids, column, storage_read_options, iter)); |
688 | 0 | } |
689 | 0 | } |
690 | 0 | } |
691 | 0 | if (result_columns.size() > _reusable->include_col_uids().size()) { |
692 | | // Padding rows for some columns that no need to output to mysql client |
693 | | // eg. SELECT k1,v1,v2 FROM TABLE WHERE k1 = 1, k1 is not in output slots, tuple as bellow |
694 | | // TupleDescriptor{id=1, tbl=table_with_column_group} |
695 | | // SlotDescriptor{id=8, col=v1, colUniqueId=1 ...} |
696 | | // SlotDescriptor{id=9, col=v2, colUniqueId=2 ...} |
697 | | // thus missing in include_col_uids and missing_col_uids |
698 | 0 | for (auto& column : result_columns) { |
699 | 0 | int padding_rows = _row_hits - cast_set<int>(column->size()); |
700 | 0 | if (padding_rows > 0) { |
701 | 0 | column->insert_many_defaults(padding_rows); |
702 | 0 | } |
703 | 0 | } |
704 | 0 | } |
705 | 0 | } |
706 | | // filter rows by delete sign |
707 | 0 | if (_row_hits > 0 && _reusable->delete_sign_idx() != -1) { |
708 | 0 | size_t filtered = 0; |
709 | 0 | size_t total = 0; |
710 | 0 | { |
711 | | // clear_column_data will check reference of ColumnPtr, so we need to release |
712 | | // reference before clear_column_data |
713 | 0 | ColumnPtr delete_filter_columns = |
714 | 0 | _result_block->get_columns()[_reusable->delete_sign_idx()]; |
715 | 0 | const auto& filter = |
716 | 0 | assert_cast<const ColumnInt8*>(delete_filter_columns.get())->get_data(); |
717 | 0 | filtered = filter.size() - simd::count_zero_num((int8_t*)filter.data(), filter.size()); |
718 | 0 | total = filter.size(); |
719 | 0 | } |
720 | |
|
721 | 0 | if (filtered == total) { |
722 | 0 | _result_block->clear_column_data(); |
723 | 0 | } else if (filtered > 0) { |
724 | 0 | return Status::NotSupported("Not implemented since only single row at present"); |
725 | 0 | } |
726 | 0 | } |
727 | 0 | return Status::OK(); |
728 | 0 | } |
729 | | |
730 | 0 | Status serialize_block(std::shared_ptr<TFetchDataResult> res, PTabletKeyLookupResponse* response) { |
731 | 0 | uint8_t* buf = nullptr; |
732 | 0 | uint32_t len = 0; |
733 | 0 | ThriftSerializer ser(false, 4096); |
734 | 0 | RETURN_IF_ERROR(ser.serialize(&(res->result_batch), &len, &buf)); |
735 | 0 | response->set_row_batch(std::string((const char*)buf, len)); |
736 | 0 | return Status::OK(); |
737 | 0 | } |
738 | | |
739 | 0 | Status PointQueryExecutor::_output_data() { |
740 | | // 4. exprs exec and serialize to mysql row batches |
741 | 0 | SCOPED_TIMER(&_profile_metrics.output_data_ns); |
742 | 0 | if (_result_block->rows()) { |
743 | 0 | RuntimeState state; |
744 | 0 | auto buffer = std::make_shared<PointQueryResultBlockBuffer>(&state); |
745 | | // TODO reuse mysql_writer |
746 | 0 | VMysqlResultWriter mysql_writer(buffer, _reusable->output_exprs(), nullptr, |
747 | 0 | _binary_row_format); |
748 | 0 | RETURN_IF_ERROR(mysql_writer.init(_reusable->runtime_state())); |
749 | 0 | _result_block->clear_names(); |
750 | 0 | RETURN_IF_ERROR(mysql_writer.write(_reusable->runtime_state(), *_result_block)); |
751 | 0 | RETURN_IF_ERROR(serialize_block(buffer->get_block(), _response)); |
752 | 0 | VLOG_DEBUG << "dump block " << _result_block->dump_data(); |
753 | 0 | } else { |
754 | 0 | _response->set_empty_batch(true); |
755 | 0 | } |
756 | 0 | _profile_metrics.result_data_bytes = _result_block->bytes(); |
757 | 0 | _reusable->return_block(_result_block); |
758 | 0 | return Status::OK(); |
759 | 0 | } |
760 | | |
761 | | } // namespace doris |