be/src/service/http/action/stream_load.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/http/action/stream_load.h" |
19 | | |
20 | | // use string iequal |
21 | | #include <event2/buffer.h> |
22 | | #include <event2/http.h> |
23 | | #include <gen_cpp/FrontendService.h> |
24 | | #include <gen_cpp/FrontendService_types.h> |
25 | | #include <gen_cpp/HeartbeatService_types.h> |
26 | | #include <gen_cpp/PaloInternalService_types.h> |
27 | | #include <gen_cpp/PlanNodes_types.h> |
28 | | #include <gen_cpp/Types_types.h> |
29 | | #include <sys/time.h> |
30 | | #include <thrift/protocol/TDebugProtocol.h> |
31 | | |
32 | | #include <algorithm> |
33 | | #include <cstdint> |
34 | | #include <cstdlib> |
35 | | #include <ctime> |
36 | | #include <functional> |
37 | | #include <future> |
38 | | #include <sstream> |
39 | | #include <stdexcept> |
40 | | #include <utility> |
41 | | |
42 | | #include "cloud/config.h" |
43 | | #include "common/config.h" |
44 | | #include "common/consts.h" |
45 | | #include "common/logging.h" |
46 | | #include "common/metrics/doris_metrics.h" |
47 | | #include "common/metrics/metrics.h" |
48 | | #include "common/status.h" |
49 | | #include "common/utils.h" |
50 | | #include "io/fs/stream_load_pipe.h" |
51 | | #include "load/group_commit/group_commit_mgr.h" |
52 | | #include "load/load_path_mgr.h" |
53 | | #include "load/message_body_sink.h" |
54 | | #include "load/stream_load/new_load_stream_mgr.h" |
55 | | #include "load/stream_load/stream_load_context.h" |
56 | | #include "load/stream_load/stream_load_executor.h" |
57 | | #include "load/stream_load/stream_load_recorder.h" |
58 | | #include "runtime/cluster_info.h" |
59 | | #include "runtime/exec_env.h" |
60 | | #include "service/http/http_channel.h" |
61 | | #include "service/http/http_common.h" |
62 | | #include "service/http/http_headers.h" |
63 | | #include "service/http/http_request.h" |
64 | | #include "service/http/utils.h" |
65 | | #include "storage/storage_engine.h" |
66 | | #include "util/byte_buffer.h" |
67 | | #include "util/client_cache.h" |
68 | | #include "util/load_util.h" |
69 | | #include "util/string_util.h" |
70 | | #include "util/thrift_rpc_helper.h" |
71 | | #include "util/time.h" |
72 | | #include "util/uid_util.h" |
73 | | #include "util/url_coding.h" |
74 | | |
75 | | namespace doris { |
76 | | using namespace ErrorCode; |
77 | | |
78 | | DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(streaming_load_requests_total, MetricUnit::REQUESTS); |
79 | | DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(streaming_load_duration_ms, MetricUnit::MILLISECONDS); |
80 | | DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(streaming_load_current_processing, MetricUnit::REQUESTS); |
81 | | |
82 | | bvar::LatencyRecorder g_stream_load_receive_data_latency_ms("stream_load_receive_data_latency_ms"); |
83 | | bvar::LatencyRecorder g_stream_load_commit_and_publish_latency_ms("stream_load", |
84 | | "commit_and_publish_ms"); |
85 | | |
86 | | static constexpr size_t MIN_CHUNK_SIZE = 64 * 1024; |
87 | | static constexpr size_t MEBIBYTE = 1024 * 1024; |
88 | | static const std::string CHUNK = "chunked"; |
89 | | static const std::string OFF_MODE = "off_mode"; |
90 | | static const std::string SYNC_MODE = "sync_mode"; |
91 | | static const std::string ASYNC_MODE = "async_mode"; |
92 | | |
93 | | #ifdef BE_TEST |
94 | | TStreamLoadPutResult k_stream_load_put_result; |
95 | | #endif |
96 | | |
97 | | StreamLoadAction::StreamLoadAction(ExecEnv* exec_env) |
98 | 10 | : HttpHandlerWithAuth(exec_env, TPrivilegeHier::GLOBAL, TPrivilegeType::LOAD) { |
99 | | // Use LOAD privilege type: requires LOAD permission |
100 | 10 | _stream_load_entity = |
101 | 10 | DorisMetrics::instance()->metric_registry()->register_entity("stream_load"); |
102 | 10 | INT_COUNTER_METRIC_REGISTER(_stream_load_entity, streaming_load_requests_total); |
103 | 10 | INT_COUNTER_METRIC_REGISTER(_stream_load_entity, streaming_load_duration_ms); |
104 | 10 | INT_GAUGE_METRIC_REGISTER(_stream_load_entity, streaming_load_current_processing); |
105 | 10 | } |
106 | | |
107 | 6 | StreamLoadAction::~StreamLoadAction() { |
108 | 6 | DorisMetrics::instance()->metric_registry()->deregister_entity(_stream_load_entity); |
109 | 6 | } |
110 | | |
111 | 2.26k | void StreamLoadAction::handle(HttpRequest* req) { |
112 | 2.26k | std::shared_ptr<StreamLoadContext> ctx = |
113 | 2.26k | std::static_pointer_cast<StreamLoadContext>(req->handler_ctx()); |
114 | 2.26k | if (ctx == nullptr) { |
115 | 0 | return; |
116 | 0 | } |
117 | | |
118 | 2.26k | { |
119 | 2.26k | std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock); |
120 | 2.26k | ctx->_can_send_reply = true; |
121 | 2.26k | ctx->_can_send_reply_cv.notify_all(); |
122 | 2.26k | } |
123 | | |
124 | | // status already set to fail |
125 | 2.26k | if (ctx->status.ok()) { |
126 | 2.26k | ctx->status = _handle(ctx, req); |
127 | 2.26k | if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) { |
128 | 0 | _send_reply(ctx, req); |
129 | 0 | } |
130 | 2.26k | } |
131 | 2.26k | } |
132 | | |
133 | 2.26k | Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) { |
134 | 2.26k | if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) { |
135 | 0 | LOG(WARNING) << "recevie body don't equal with body bytes, body_bytes=" << ctx->body_bytes |
136 | 0 | << ", receive_bytes=" << ctx->receive_bytes << ", id=" << ctx->id; |
137 | 0 | return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't equal with body bytes"); |
138 | 0 | } |
139 | | |
140 | | // if we use non-streaming, MessageBodyFileSink.finish will close the file |
141 | 2.26k | RETURN_IF_ERROR(ctx->body_sink->finish()); |
142 | 2.26k | if (!ctx->use_streaming) { |
143 | | // we need to close file first, then execute_plan_fragment here |
144 | 17 | ctx->body_sink.reset(); |
145 | 17 | TPipelineFragmentParamsList mocked; |
146 | 17 | RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment( |
147 | 17 | ctx, mocked, |
148 | 17 | [req, this](std::shared_ptr<StreamLoadContext> ctx) { _on_finish(ctx, req); })); |
149 | 17 | } |
150 | | |
151 | 2.26k | return Status::OK(); |
152 | 2.26k | } |
153 | | |
154 | 2.26k | void StreamLoadAction::_on_finish(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) { |
155 | 2.26k | ctx->status = ctx->load_status_future.get(); |
156 | 2.26k | if (ctx->status.ok()) { |
157 | 1.94k | if (ctx->group_commit) { |
158 | 423 | LOG(INFO) << "skip commit because this is group commit, pipe_id=" |
159 | 423 | << ctx->id.to_string(); |
160 | 1.52k | } else if (ctx->two_phase_commit) { |
161 | 32 | int64_t pre_commit_start_time = MonotonicNanos(); |
162 | 32 | ctx->status = _exec_env->stream_load_executor()->pre_commit_txn(ctx.get()); |
163 | 32 | ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - pre_commit_start_time; |
164 | 1.49k | } else { |
165 | | // If put file success we need commit this load |
166 | 1.49k | int64_t commit_and_publish_start_time = MonotonicNanos(); |
167 | 1.49k | ctx->status = _exec_env->stream_load_executor()->commit_txn(ctx.get()); |
168 | 1.49k | ctx->commit_and_publish_txn_cost_nanos = |
169 | 1.49k | MonotonicNanos() - commit_and_publish_start_time; |
170 | 1.49k | g_stream_load_commit_and_publish_latency_ms |
171 | 1.49k | << ctx->commit_and_publish_txn_cost_nanos / 1000000; |
172 | 1.49k | } |
173 | 1.94k | } |
174 | 2.26k | _send_reply(ctx, req); |
175 | 2.26k | } |
176 | | |
177 | 2.44k | void StreamLoadAction::_send_reply(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) { |
178 | 2.44k | std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock); |
179 | | // 1. _can_send_reply: ensure `send_reply` is invoked only after on_header/handle complete, |
180 | | // avoid client errors (e.g., broken pipe). |
181 | | // 2. _finish_send_reply: Prevent duplicate reply sending; skip reply if HTTP request is canceled |
182 | | // due to long import execution time. |
183 | 2.45k | while (!ctx->_finish_send_reply && !ctx->_can_send_reply) { |
184 | 3 | ctx->_can_send_reply_cv.wait(lock1); |
185 | 3 | } |
186 | 2.44k | if (ctx->_finish_send_reply) { |
187 | 0 | return; |
188 | 0 | } |
189 | 2.44k | DCHECK(ctx->_can_send_reply); |
190 | 2.44k | ctx->_finish_send_reply = true; |
191 | 2.44k | ctx->_can_send_reply_cv.notify_all(); |
192 | 2.44k | ctx->load_cost_millis = UnixMillis() - ctx->start_millis; |
193 | | |
194 | 2.44k | if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) { |
195 | 499 | LOG(WARNING) << "handle streaming load failed, id=" << ctx->id |
196 | 499 | << ", errmsg=" << ctx->status; |
197 | 499 | if (ctx->need_rollback) { |
198 | 468 | _exec_env->stream_load_executor()->rollback_txn(ctx.get()); |
199 | 468 | ctx->need_rollback = false; |
200 | 468 | } |
201 | 499 | if (ctx->body_sink != nullptr) { |
202 | 468 | ctx->body_sink->cancel(ctx->status.to_string()); |
203 | 468 | } |
204 | 499 | } |
205 | | |
206 | 2.44k | auto str = ctx->to_json(); |
207 | | // add new line at end |
208 | 2.44k | str = str + '\n'; |
209 | | |
210 | 2.44k | #ifndef BE_TEST |
211 | 2.44k | if (config::enable_stream_load_record || config::enable_stream_load_record_to_audit_log_table) { |
212 | 2.44k | if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) { |
213 | 2.40k | str = ctx->prepare_stream_load_record(str); |
214 | 2.40k | _save_stream_load_record(ctx, str); |
215 | 2.40k | } |
216 | 2.44k | } |
217 | 2.44k | #endif |
218 | | |
219 | 2.44k | HttpChannel::send_reply(req, str); |
220 | | |
221 | 2.44k | LOG(INFO) << "finished to execute stream load. label=" << ctx->label |
222 | 2.44k | << ", txn_id=" << ctx->txn_id << ", query_id=" << ctx->id |
223 | 2.44k | << ", load_cost_ms=" << ctx->load_cost_millis << ", receive_data_cost_ms=" |
224 | 2.44k | << (ctx->receive_and_read_data_cost_nanos - ctx->read_data_cost_nanos) / 1000000 |
225 | 2.44k | << ", read_data_cost_ms=" << ctx->read_data_cost_nanos / 1000000 |
226 | 2.44k | << ", write_data_cost_ms=" << ctx->write_data_cost_nanos / 1000000 |
227 | 2.44k | << ", commit_and_publish_txn_cost_ms=" |
228 | 2.44k | << ctx->commit_and_publish_txn_cost_nanos / 1000000 |
229 | 2.44k | << ", number_total_rows=" << ctx->number_total_rows |
230 | 2.44k | << ", number_loaded_rows=" << ctx->number_loaded_rows |
231 | 2.44k | << ", receive_bytes=" << ctx->receive_bytes << ", loaded_bytes=" << ctx->loaded_bytes |
232 | 2.44k | << ", error_url=" << ctx->error_url; |
233 | | |
234 | | // update statistics |
235 | 2.44k | streaming_load_requests_total->increment(1); |
236 | 2.44k | streaming_load_duration_ms->increment(ctx->load_cost_millis); |
237 | 2.44k | if (!ctx->data_saved_path.empty()) { |
238 | 18 | _exec_env->load_path_mgr()->clean_tmp_files(ctx->data_saved_path); |
239 | 18 | } |
240 | 2.44k | } |
241 | | |
242 | 2.44k | int StreamLoadAction::on_header(HttpRequest* req) { |
243 | | // Call parent's auth check first |
244 | 2.44k | int ret = HttpHandlerWithAuth::on_header(req); |
245 | 2.44k | if (ret != 0) { |
246 | 0 | return ret; // Auth failed, return error |
247 | 0 | } |
248 | | |
249 | | // Continue with stream load specific header processing |
250 | 2.44k | req->mark_send_reply(); |
251 | | |
252 | 2.44k | streaming_load_current_processing->increment(1); |
253 | | |
254 | 2.44k | std::shared_ptr<StreamLoadContext> ctx = std::make_shared<StreamLoadContext>(_exec_env); |
255 | 2.44k | req->set_handler_ctx(ctx); |
256 | | |
257 | 2.44k | ctx->load_type = TLoadType::MANUL_LOAD; |
258 | 2.44k | ctx->load_src_type = TLoadSourceType::RAW; |
259 | | |
260 | 2.44k | url_decode(req->param(HTTP_DB_KEY), &ctx->db); |
261 | 2.44k | url_decode(req->param(HTTP_TABLE_KEY), &ctx->table); |
262 | 2.44k | ctx->label = req->header(HTTP_LABEL_KEY); |
263 | 2.44k | ctx->two_phase_commit = req->header(HTTP_TWO_PHASE_COMMIT) == "true"; |
264 | 2.44k | Status st = _handle_group_commit(req, ctx); |
265 | 2.44k | if (!ctx->group_commit && ctx->label.empty()) { |
266 | 310 | ctx->label = generate_uuid_string(); |
267 | 310 | } |
268 | | |
269 | 2.44k | LOG(INFO) << "new income streaming load request." << ctx->brief() << ", db=" << ctx->db |
270 | 2.44k | << ", tbl=" << ctx->table << ", group_commit=" << ctx->group_commit |
271 | 2.44k | << ", group_commit_mode=" << ctx->group_commit_mode |
272 | 2.44k | << ", HTTP headers=" << req->get_all_headers(); |
273 | 2.44k | ctx->begin_receive_and_read_data_cost_nanos = MonotonicNanos(); |
274 | | |
275 | 2.44k | if (st.ok()) { |
276 | 2.44k | st = _on_header(req, ctx); |
277 | 2.44k | LOG(INFO) << "finished to handle HTTP header, " << ctx->brief(); |
278 | 2.44k | } |
279 | 2.44k | if (!st.ok()) { |
280 | 180 | ctx->status = std::move(st); |
281 | 180 | { |
282 | 180 | std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock); |
283 | 180 | ctx->_can_send_reply = true; |
284 | 180 | ctx->_can_send_reply_cv.notify_all(); |
285 | 180 | } |
286 | 180 | _send_reply(ctx, req); |
287 | 180 | return -1; |
288 | 180 | } |
289 | 2.26k | return 0; |
290 | 2.44k | } |
291 | | |
292 | 2.44k | Status StreamLoadAction::_on_header(HttpRequest* http_req, std::shared_ptr<StreamLoadContext> ctx) { |
293 | | // auth information |
294 | 2.44k | if (!parse_basic_auth(*http_req, &ctx->auth)) { |
295 | 0 | LOG(WARNING) << "parse basic authorization failed." << ctx->brief(); |
296 | 0 | return Status::NotAuthorized("no valid Basic authorization"); |
297 | 0 | } |
298 | | |
299 | | // get format of this put |
300 | 2.44k | std::string format_str = http_req->header(HTTP_FORMAT_KEY); |
301 | 2.44k | if (iequal(format_str, BeConsts::CSV_WITH_NAMES) || |
302 | 2.44k | iequal(format_str, BeConsts::CSV_WITH_NAMES_AND_TYPES)) { |
303 | 8 | ctx->header_type = format_str; |
304 | | //treat as CSV |
305 | 8 | format_str = BeConsts::CSV; |
306 | 8 | } |
307 | 2.44k | LoadUtil::parse_format(format_str, http_req->header(HTTP_COMPRESS_TYPE), &ctx->format, |
308 | 2.44k | &ctx->compress_type); |
309 | 2.44k | if (ctx->format == TFileFormatType::FORMAT_UNKNOWN) { |
310 | 1 | return Status::Error<ErrorCode::DATA_FILE_TYPE_ERROR>("unknown data format, format={}", |
311 | 1 | http_req->header(HTTP_FORMAT_KEY)); |
312 | 1 | } |
313 | | |
314 | | // check content length |
315 | 2.44k | ctx->body_bytes = 0; |
316 | 2.44k | const auto csv_max_body_mb = config::streaming_load_max_mb; |
317 | 2.44k | size_t csv_max_body_bytes = csv_max_body_mb * MEBIBYTE; |
318 | 2.44k | const auto json_max_body_mb = config::streaming_load_json_max_mb; |
319 | 2.44k | size_t json_max_body_bytes = json_max_body_mb * MEBIBYTE; |
320 | 2.44k | bool read_json_by_line = false; |
321 | 2.44k | if (!http_req->header(HTTP_READ_JSON_BY_LINE).empty()) { |
322 | 331 | if (iequal(http_req->header(HTTP_READ_JSON_BY_LINE), "true")) { |
323 | 329 | read_json_by_line = true; |
324 | 329 | } |
325 | 331 | } |
326 | 2.44k | if (!http_req->header(HttpHeaders::CONTENT_LENGTH).empty()) { |
327 | 2.16k | try { |
328 | 2.16k | ctx->body_bytes = std::stol(http_req->header(HttpHeaders::CONTENT_LENGTH)); |
329 | 2.16k | } catch (const std::exception& e) { |
330 | 0 | return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}", |
331 | 0 | http_req->header(HttpHeaders::CONTENT_LENGTH), e.what()); |
332 | 0 | } |
333 | | // json max body size |
334 | 2.16k | if ((ctx->format == TFileFormatType::FORMAT_JSON) && |
335 | 2.16k | (ctx->body_bytes > json_max_body_bytes) && !read_json_by_line) { |
336 | 1 | return Status::Error<ErrorCode::EXCEEDED_LIMIT>( |
337 | 1 | "json body size {} bytes ({:.2f} MiB) exceeds the limit of {} bytes ({} MiB) " |
338 | 1 | "set by BE's conf streaming_load_json_max_mb. Increase it if you are sure " |
339 | 1 | "this load is reasonable", |
340 | 1 | ctx->body_bytes, static_cast<double>(ctx->body_bytes) / MEBIBYTE, |
341 | 1 | json_max_body_bytes, json_max_body_mb); |
342 | 1 | } |
343 | | // csv max body size |
344 | 2.16k | else if (ctx->body_bytes > csv_max_body_bytes) { |
345 | 1 | LOG(WARNING) << "body exceed max size." << ctx->brief(); |
346 | 1 | return Status::Error<ErrorCode::EXCEEDED_LIMIT>( |
347 | 1 | "body size {} bytes ({:.2f} MiB) exceeds the limit of {} bytes ({} MiB) set " |
348 | 1 | "by BE's conf streaming_load_max_mb. Increase it if you are sure this load is " |
349 | 1 | "reasonable", |
350 | 1 | ctx->body_bytes, static_cast<double>(ctx->body_bytes) / MEBIBYTE, |
351 | 1 | csv_max_body_bytes, csv_max_body_mb); |
352 | 1 | } |
353 | 2.16k | } else { |
354 | 286 | #ifndef BE_TEST |
355 | 286 | evhttp_connection_set_max_body_size( |
356 | 286 | evhttp_request_get_connection(http_req->get_evhttp_request()), csv_max_body_bytes); |
357 | 286 | #endif |
358 | 286 | } |
359 | | |
360 | 2.44k | if (!http_req->header(HttpHeaders::TRANSFER_ENCODING).empty()) { |
361 | 285 | if (http_req->header(HttpHeaders::TRANSFER_ENCODING).find(CHUNK) != std::string::npos) { |
362 | 285 | ctx->is_chunked_transfer = true; |
363 | 285 | } |
364 | 285 | } |
365 | 2.44k | if (UNLIKELY((http_req->header(HttpHeaders::CONTENT_LENGTH).empty() && |
366 | 2.44k | !ctx->is_chunked_transfer))) { |
367 | 1 | LOG(WARNING) << "content_length is empty and transfer-encoding!=chunked, please set " |
368 | 1 | "content_length or transfer-encoding=chunked"; |
369 | 1 | return Status::InvalidArgument( |
370 | 1 | "content_length is empty and transfer-encoding!=chunked, please set content_length " |
371 | 1 | "or transfer-encoding=chunked"); |
372 | 2.44k | } else if (UNLIKELY(!http_req->header(HttpHeaders::CONTENT_LENGTH).empty() && |
373 | 2.44k | ctx->is_chunked_transfer)) { |
374 | 1 | LOG(WARNING) << "please do not set both content_length and transfer-encoding"; |
375 | 1 | return Status::InvalidArgument( |
376 | 1 | "please do not set both content_length and transfer-encoding"); |
377 | 1 | } |
378 | | |
379 | 2.44k | if (!http_req->header(HTTP_TIMEOUT).empty()) { |
380 | 62 | ctx->timeout_second = DORIS_TRY(safe_stoi(http_req->header(HTTP_TIMEOUT), HTTP_TIMEOUT)); |
381 | 61 | } |
382 | 2.44k | if (!http_req->header(HTTP_COMMENT).empty()) { |
383 | 1 | ctx->load_comment = http_req->header(HTTP_COMMENT); |
384 | 1 | } |
385 | | // begin transaction |
386 | 2.44k | if (!ctx->group_commit) { |
387 | 2.01k | int64_t begin_txn_start_time = MonotonicNanos(); |
388 | 2.01k | RETURN_IF_ERROR(_exec_env->stream_load_executor()->begin_txn(ctx.get())); |
389 | 1.99k | ctx->begin_txn_cost_nanos = MonotonicNanos() - begin_txn_start_time; |
390 | 1.99k | if (ctx->group_commit) { |
391 | 2 | RETURN_IF_ERROR(_check_wal_space(ctx->group_commit_mode, ctx->body_bytes)); |
392 | 2 | } |
393 | 1.99k | } |
394 | | |
395 | | // process put file |
396 | 2.41k | return _process_put(http_req, ctx); |
397 | 2.44k | } |
398 | | |
399 | 314k | void StreamLoadAction::on_chunk_data(HttpRequest* req) { |
400 | 314k | std::shared_ptr<StreamLoadContext> ctx = |
401 | 314k | std::static_pointer_cast<StreamLoadContext>(req->handler_ctx()); |
402 | 314k | if (ctx == nullptr || !ctx->status.ok()) { |
403 | 20.8k | return; |
404 | 20.8k | } |
405 | | |
406 | 293k | struct evhttp_request* ev_req = req->get_evhttp_request(); |
407 | 293k | auto evbuf = evhttp_request_get_input_buffer(ev_req); |
408 | | |
409 | 293k | SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->stream_load_pipe_tracker()); |
410 | | |
411 | 293k | int64_t start_read_data_time = MonotonicNanos(); |
412 | 587k | while (evbuffer_get_length(evbuf) > 0) { |
413 | 293k | ByteBufferPtr bb; |
414 | 293k | Status st = ByteBuffer::allocate(128 * 1024, &bb); |
415 | 293k | if (!st.ok()) { |
416 | 0 | ctx->status = st; |
417 | 0 | return; |
418 | 0 | } |
419 | 293k | auto remove_bytes = evbuffer_remove(evbuf, bb->ptr, bb->capacity); |
420 | 293k | bb->pos = remove_bytes; |
421 | 293k | bb->flip(); |
422 | 293k | st = ctx->body_sink->append(bb); |
423 | 293k | if (!st.ok()) { |
424 | 3 | LOG(WARNING) << "append body content failed. errmsg=" << st << ", " << ctx->brief(); |
425 | 3 | ctx->status = st; |
426 | 3 | return; |
427 | 3 | } |
428 | 293k | ctx->receive_bytes += remove_bytes; |
429 | 293k | } |
430 | 293k | int64_t read_data_time = MonotonicNanos() - start_read_data_time; |
431 | 293k | int64_t last_receive_and_read_data_cost_nanos = ctx->receive_and_read_data_cost_nanos; |
432 | 293k | ctx->read_data_cost_nanos += read_data_time; |
433 | 293k | ctx->receive_and_read_data_cost_nanos = |
434 | 293k | MonotonicNanos() - ctx->begin_receive_and_read_data_cost_nanos; |
435 | 293k | g_stream_load_receive_data_latency_ms |
436 | 293k | << (ctx->receive_and_read_data_cost_nanos - last_receive_and_read_data_cost_nanos - |
437 | 293k | read_data_time) / |
438 | 293k | 1000000; |
439 | 293k | } |
440 | | |
441 | 2.44k | void StreamLoadAction::free_handler_ctx(std::shared_ptr<void> param) { |
442 | 2.44k | std::shared_ptr<StreamLoadContext> ctx = std::static_pointer_cast<StreamLoadContext>(param); |
443 | 2.44k | if (ctx == nullptr) { |
444 | 0 | return; |
445 | 0 | } |
446 | | // sender is gone, make receiver know it |
447 | 2.44k | if (ctx->body_sink != nullptr) { |
448 | 2.40k | ctx->body_sink->cancel("sender is gone"); |
449 | 2.40k | } |
450 | | // remove stream load context from stream load manager and the resource will be released |
451 | 2.44k | ctx->exec_env()->new_load_stream_mgr()->remove(ctx->id); |
452 | 2.44k | streaming_load_current_processing->increment(-1); |
453 | 2.44k | } |
454 | | |
455 | | Status StreamLoadAction::_process_put(HttpRequest* http_req, |
456 | 2.41k | std::shared_ptr<StreamLoadContext> ctx) { |
457 | | // Now we use stream |
458 | 2.41k | ctx->use_streaming = LoadUtil::is_format_support_streaming(ctx->format); |
459 | | |
460 | | // put request |
461 | 2.41k | TStreamLoadPutRequest request; |
462 | 2.41k | set_request_auth(&request, ctx->auth); |
463 | 2.41k | request.db = ctx->db; |
464 | 2.41k | request.tbl = ctx->table; |
465 | 2.41k | request.txnId = ctx->txn_id; |
466 | 2.41k | request.formatType = ctx->format; |
467 | 2.41k | request.__set_compress_type(ctx->compress_type); |
468 | 2.41k | request.__set_header_type(ctx->header_type); |
469 | 2.41k | request.__set_loadId(ctx->id.to_thrift()); |
470 | 2.41k | if (ctx->use_streaming) { |
471 | 2.39k | std::shared_ptr<io::StreamLoadPipe> pipe; |
472 | 2.39k | if (ctx->is_chunked_transfer) { |
473 | 284 | pipe = std::make_shared<io::StreamLoadPipe>( |
474 | 284 | io::kMaxPipeBufferedBytes /* max_buffered_bytes */); |
475 | 284 | pipe->set_is_chunked_transfer(true); |
476 | 2.11k | } else { |
477 | 2.11k | pipe = std::make_shared<io::StreamLoadPipe>( |
478 | 2.11k | io::kMaxPipeBufferedBytes /* max_buffered_bytes */, |
479 | 2.11k | MIN_CHUNK_SIZE /* min_chunk_size */, ctx->body_bytes /* total_length */); |
480 | 2.11k | } |
481 | 2.39k | request.fileType = TFileType::FILE_STREAM; |
482 | 2.39k | ctx->body_sink = pipe; |
483 | 2.39k | ctx->pipe = pipe; |
484 | 2.39k | RETURN_IF_ERROR(_exec_env->new_load_stream_mgr()->put(ctx->id, ctx)); |
485 | 2.39k | } else { |
486 | 20 | RETURN_IF_ERROR(_data_saved_path(http_req, &request.path, ctx->body_bytes)); |
487 | 20 | auto file_sink = std::make_shared<MessageBodyFileSink>(request.path); |
488 | 20 | RETURN_IF_ERROR(file_sink->open()); |
489 | 20 | request.__isset.path = true; |
490 | 20 | request.fileType = TFileType::FILE_LOCAL; |
491 | 20 | request.__set_file_size(ctx->body_bytes); |
492 | 20 | ctx->body_sink = file_sink; |
493 | 20 | ctx->data_saved_path = request.path; |
494 | 20 | } |
495 | 2.41k | if (!http_req->header(HTTP_COLUMNS).empty()) { |
496 | 1.01k | request.__set_columns(http_req->header(HTTP_COLUMNS)); |
497 | 1.01k | } |
498 | 2.41k | if (!http_req->header(HTTP_WHERE).empty()) { |
499 | 10 | request.__set_where(http_req->header(HTTP_WHERE)); |
500 | 10 | } |
501 | 2.41k | if (!http_req->header(HTTP_COLUMN_SEPARATOR).empty()) { |
502 | 1.80k | request.__set_columnSeparator(http_req->header(HTTP_COLUMN_SEPARATOR)); |
503 | 1.80k | } |
504 | 2.41k | if (!http_req->header(HTTP_LINE_DELIMITER).empty()) { |
505 | 35 | request.__set_line_delimiter(http_req->header(HTTP_LINE_DELIMITER)); |
506 | 35 | } |
507 | 2.41k | if (!http_req->header(HTTP_ENCLOSE).empty() && !http_req->header(HTTP_ENCLOSE).empty()) { |
508 | 80 | const auto& enclose_str = http_req->header(HTTP_ENCLOSE); |
509 | 80 | if (enclose_str.length() != 1) { |
510 | 0 | return Status::InvalidArgument("enclose must be single-char, actually is {}", |
511 | 0 | enclose_str); |
512 | 0 | } |
513 | 80 | request.__set_enclose(http_req->header(HTTP_ENCLOSE)[0]); |
514 | 80 | } |
515 | 2.41k | if (!http_req->header(HTTP_ESCAPE).empty() && !http_req->header(HTTP_ESCAPE).empty()) { |
516 | 80 | const auto& escape_str = http_req->header(HTTP_ESCAPE); |
517 | 80 | if (escape_str.length() != 1) { |
518 | 0 | return Status::InvalidArgument("escape must be single-char, actually is {}", |
519 | 0 | escape_str); |
520 | 0 | } |
521 | 80 | request.__set_escape(http_req->header(HTTP_ESCAPE)[0]); |
522 | 80 | } |
523 | 2.41k | if (!http_req->header(HTTP_PARTITIONS).empty()) { |
524 | 15 | request.__set_partitions(http_req->header(HTTP_PARTITIONS)); |
525 | 15 | request.__set_isTempPartition(false); |
526 | 15 | if (!http_req->header(HTTP_TEMP_PARTITIONS).empty()) { |
527 | 0 | return Status::InvalidArgument( |
528 | 0 | "Can not specify both partitions and temporary partitions"); |
529 | 0 | } |
530 | 15 | } |
531 | 2.41k | if (!http_req->header(HTTP_TEMP_PARTITIONS).empty()) { |
532 | 1 | request.__set_partitions(http_req->header(HTTP_TEMP_PARTITIONS)); |
533 | 1 | request.__set_isTempPartition(true); |
534 | 1 | if (!http_req->header(HTTP_PARTITIONS).empty()) { |
535 | 0 | return Status::InvalidArgument( |
536 | 0 | "Can not specify both partitions and temporary partitions"); |
537 | 0 | } |
538 | 1 | } |
539 | 2.41k | if (!http_req->header(HTTP_NEGATIVE).empty() && http_req->header(HTTP_NEGATIVE) == "true") { |
540 | 0 | request.__set_negative(true); |
541 | 2.41k | } else { |
542 | 2.41k | request.__set_negative(false); |
543 | 2.41k | } |
544 | 2.41k | bool strictMode = false; |
545 | 2.41k | if (!http_req->header(HTTP_STRICT_MODE).empty()) { |
546 | 351 | if (iequal(http_req->header(HTTP_STRICT_MODE), "false")) { |
547 | 218 | strictMode = false; |
548 | 218 | } else if (iequal(http_req->header(HTTP_STRICT_MODE), "true")) { |
549 | 133 | strictMode = true; |
550 | 133 | } else { |
551 | 0 | return Status::InvalidArgument("Invalid strict mode format. Must be bool type"); |
552 | 0 | } |
553 | 351 | request.__set_strictMode(strictMode); |
554 | 351 | } |
555 | | // timezone first. if not, try system time_zone |
556 | 2.41k | if (!http_req->header(HTTP_TIMEZONE).empty()) { |
557 | 17 | request.__set_timezone(http_req->header(HTTP_TIMEZONE)); |
558 | 2.40k | } else if (!http_req->header(HTTP_TIME_ZONE).empty()) { |
559 | 0 | request.__set_timezone(http_req->header(HTTP_TIME_ZONE)); |
560 | 0 | } |
561 | 2.41k | if (!http_req->header(HTTP_EXEC_MEM_LIMIT).empty()) { |
562 | 22 | try { |
563 | 22 | request.__set_execMemLimit(std::stoll(http_req->header(HTTP_EXEC_MEM_LIMIT))); |
564 | 22 | } catch (const std::invalid_argument& e) { |
565 | 7 | return Status::InvalidArgument("Invalid mem limit format, {}", e.what()); |
566 | 7 | } |
567 | 22 | } |
568 | 2.41k | if (!http_req->header(HTTP_JSONPATHS).empty()) { |
569 | 27 | request.__set_jsonpaths(http_req->header(HTTP_JSONPATHS)); |
570 | 27 | } |
571 | 2.41k | if (!http_req->header(HTTP_JSONROOT).empty()) { |
572 | 7 | request.__set_json_root(http_req->header(HTTP_JSONROOT)); |
573 | 7 | } |
574 | 2.41k | if (!http_req->header(HTTP_STRIP_OUTER_ARRAY).empty()) { |
575 | 69 | if (iequal(http_req->header(HTTP_STRIP_OUTER_ARRAY), "true")) { |
576 | 63 | request.__set_strip_outer_array(true); |
577 | 63 | } else { |
578 | 6 | request.__set_strip_outer_array(false); |
579 | 6 | } |
580 | 2.34k | } else { |
581 | 2.34k | request.__set_strip_outer_array(false); |
582 | 2.34k | } |
583 | | |
584 | 2.41k | if (!http_req->header(HTTP_READ_JSON_BY_LINE).empty()) { |
585 | 324 | if (iequal(http_req->header(HTTP_READ_JSON_BY_LINE), "true")) { |
586 | 322 | request.__set_read_json_by_line(true); |
587 | 322 | } else { |
588 | 2 | request.__set_read_json_by_line(false); |
589 | 2 | } |
590 | 2.08k | } else { |
591 | 2.08k | request.__set_read_json_by_line(false); |
592 | 2.08k | } |
593 | | |
594 | 2.41k | if (http_req->header(HTTP_READ_JSON_BY_LINE).empty() && |
595 | 2.41k | http_req->header(HTTP_STRIP_OUTER_ARRAY).empty()) { |
596 | 2.02k | request.__set_read_json_by_line(true); |
597 | 2.02k | request.__set_strip_outer_array(false); |
598 | 2.02k | } |
599 | | |
600 | 2.41k | if (!http_req->header(HTTP_NUM_AS_STRING).empty()) { |
601 | 2 | if (iequal(http_req->header(HTTP_NUM_AS_STRING), "true")) { |
602 | 1 | request.__set_num_as_string(true); |
603 | 1 | } else { |
604 | 1 | request.__set_num_as_string(false); |
605 | 1 | } |
606 | 2.40k | } else { |
607 | 2.40k | request.__set_num_as_string(false); |
608 | 2.40k | } |
609 | 2.41k | if (!http_req->header(HTTP_FUZZY_PARSE).empty()) { |
610 | 9 | if (iequal(http_req->header(HTTP_FUZZY_PARSE), "true")) { |
611 | 8 | request.__set_fuzzy_parse(true); |
612 | 8 | } else { |
613 | 1 | request.__set_fuzzy_parse(false); |
614 | 1 | } |
615 | 2.40k | } else { |
616 | 2.40k | request.__set_fuzzy_parse(false); |
617 | 2.40k | } |
618 | | |
619 | 2.41k | if (!http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL).empty()) { |
620 | 90 | request.__set_sequence_col( |
621 | 90 | http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL)); |
622 | 90 | } |
623 | | |
624 | 2.41k | if (!http_req->header(HTTP_SEND_BATCH_PARALLELISM).empty()) { |
625 | 4 | int parallelism = DORIS_TRY(safe_stoi(http_req->header(HTTP_SEND_BATCH_PARALLELISM), |
626 | 2 | HTTP_SEND_BATCH_PARALLELISM)); |
627 | 2 | request.__set_send_batch_parallelism(parallelism); |
628 | 2 | } |
629 | | |
630 | 2.40k | if (!http_req->header(HTTP_LOAD_TO_SINGLE_TABLET).empty()) { |
631 | 8 | if (iequal(http_req->header(HTTP_LOAD_TO_SINGLE_TABLET), "true")) { |
632 | 7 | request.__set_load_to_single_tablet(true); |
633 | 7 | } else { |
634 | 1 | request.__set_load_to_single_tablet(false); |
635 | 1 | } |
636 | 8 | } |
637 | | |
638 | 2.40k | if (ctx->timeout_second != -1) { |
639 | 61 | request.__set_timeout(ctx->timeout_second); |
640 | 61 | } |
641 | 2.40k | request.__set_thrift_rpc_timeout_ms(config::thrift_rpc_timeout_ms); |
642 | 2.40k | TMergeType::type merge_type = TMergeType::APPEND; |
643 | 2.40k | StringCaseMap<TMergeType::type> merge_type_map = {{"APPEND", TMergeType::APPEND}, |
644 | 2.40k | {"DELETE", TMergeType::DELETE}, |
645 | 2.40k | {"MERGE", TMergeType::MERGE}}; |
646 | 2.40k | if (!http_req->header(HTTP_MERGE_TYPE).empty()) { |
647 | 75 | std::string merge_type_str = http_req->header(HTTP_MERGE_TYPE); |
648 | 75 | auto iter = merge_type_map.find(merge_type_str); |
649 | 75 | if (iter != merge_type_map.end()) { |
650 | 74 | merge_type = iter->second; |
651 | 74 | } else { |
652 | 1 | return Status::InvalidArgument("Invalid merge type {}", merge_type_str); |
653 | 1 | } |
654 | 74 | if (merge_type == TMergeType::MERGE && http_req->header(HTTP_DELETE_CONDITION).empty()) { |
655 | 0 | return Status::InvalidArgument("Excepted DELETE ON clause when merge type is MERGE."); |
656 | 74 | } else if (merge_type != TMergeType::MERGE && |
657 | 74 | !http_req->header(HTTP_DELETE_CONDITION).empty()) { |
658 | 0 | return Status::InvalidArgument( |
659 | 0 | "Not support DELETE ON clause when merge type is not MERGE."); |
660 | 0 | } |
661 | 74 | } |
662 | 2.40k | request.__set_merge_type(merge_type); |
663 | 2.40k | if (!http_req->header(HTTP_DELETE_CONDITION).empty()) { |
664 | 17 | request.__set_delete_condition(http_req->header(HTTP_DELETE_CONDITION)); |
665 | 17 | } |
666 | | |
667 | 2.40k | if (!http_req->header(HTTP_MAX_FILTER_RATIO).empty()) { |
668 | 212 | ctx->max_filter_ratio = strtod(http_req->header(HTTP_MAX_FILTER_RATIO).c_str(), nullptr); |
669 | 212 | request.__set_max_filter_ratio(ctx->max_filter_ratio); |
670 | 212 | } |
671 | | |
672 | 2.40k | if (!http_req->header(HTTP_HIDDEN_COLUMNS).empty()) { |
673 | 6 | request.__set_hidden_columns(http_req->header(HTTP_HIDDEN_COLUMNS)); |
674 | 6 | } |
675 | 2.40k | if (!http_req->header(HTTP_TRIM_DOUBLE_QUOTES).empty()) { |
676 | 90 | if (iequal(http_req->header(HTTP_TRIM_DOUBLE_QUOTES), "true")) { |
677 | 26 | request.__set_trim_double_quotes(true); |
678 | 64 | } else { |
679 | 64 | request.__set_trim_double_quotes(false); |
680 | 64 | } |
681 | 90 | } |
682 | 2.40k | if (!http_req->header(HTTP_SKIP_LINES).empty()) { |
683 | 81 | int skip_lines = DORIS_TRY(safe_stoi(http_req->header(HTTP_SKIP_LINES), HTTP_SKIP_LINES)); |
684 | 81 | if (skip_lines < 0) { |
685 | 1 | return Status::InvalidArgument("Invalid 'skip_lines': {}", skip_lines); |
686 | 1 | } |
687 | 80 | request.__set_skip_lines(skip_lines); |
688 | 80 | } |
689 | 2.40k | if (!http_req->header(HTTP_ENABLE_PROFILE).empty()) { |
690 | 0 | if (iequal(http_req->header(HTTP_ENABLE_PROFILE), "true")) { |
691 | 0 | request.__set_enable_profile(true); |
692 | 0 | } else { |
693 | 0 | request.__set_enable_profile(false); |
694 | 0 | } |
695 | 0 | } |
696 | | |
697 | 2.40k | if (!http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty()) { |
698 | 170 | static const StringCaseMap<TUniqueKeyUpdateMode::type> unique_key_update_mode_map = { |
699 | 170 | {"UPSERT", TUniqueKeyUpdateMode::UPSERT}, |
700 | 170 | {"UPDATE_FIXED_COLUMNS", TUniqueKeyUpdateMode::UPDATE_FIXED_COLUMNS}, |
701 | 170 | {"UPDATE_FLEXIBLE_COLUMNS", TUniqueKeyUpdateMode::UPDATE_FLEXIBLE_COLUMNS}}; |
702 | 170 | std::string unique_key_update_mode_str = http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE); |
703 | 170 | auto iter = unique_key_update_mode_map.find(unique_key_update_mode_str); |
704 | 170 | if (iter != unique_key_update_mode_map.end()) { |
705 | 169 | TUniqueKeyUpdateMode::type unique_key_update_mode = iter->second; |
706 | 169 | if (unique_key_update_mode == TUniqueKeyUpdateMode::UPDATE_FLEXIBLE_COLUMNS) { |
707 | | // check constraints when flexible partial update is enabled |
708 | 169 | if (ctx->format != TFileFormatType::FORMAT_JSON) { |
709 | 1 | return Status::InvalidArgument( |
710 | 1 | "flexible partial update only support json format as input file " |
711 | 1 | "currently"); |
712 | 1 | } |
713 | 168 | if (!http_req->header(HTTP_FUZZY_PARSE).empty() && |
714 | 168 | iequal(http_req->header(HTTP_FUZZY_PARSE), "true")) { |
715 | 1 | return Status::InvalidArgument( |
716 | 1 | "Don't support flexible partial update when 'fuzzy_parse' is enabled"); |
717 | 1 | } |
718 | 167 | if (!http_req->header(HTTP_COLUMNS).empty()) { |
719 | 1 | return Status::InvalidArgument( |
720 | 1 | "Don't support flexible partial update when 'columns' is specified"); |
721 | 1 | } |
722 | 166 | if (!http_req->header(HTTP_JSONPATHS).empty()) { |
723 | 1 | return Status::InvalidArgument( |
724 | 1 | "Don't support flexible partial update when 'jsonpaths' is specified"); |
725 | 1 | } |
726 | 165 | if (!http_req->header(HTTP_HIDDEN_COLUMNS).empty()) { |
727 | 1 | return Status::InvalidArgument( |
728 | 1 | "Don't support flexible partial update when 'hidden_columns' is " |
729 | 1 | "specified"); |
730 | 1 | } |
731 | 164 | if (!http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL).empty()) { |
732 | 0 | return Status::InvalidArgument( |
733 | 0 | "Don't support flexible partial update when " |
734 | 0 | "'function_column.sequence_col' is specified"); |
735 | 0 | } |
736 | 164 | if (!http_req->header(HTTP_MERGE_TYPE).empty()) { |
737 | 1 | return Status::InvalidArgument( |
738 | 1 | "Don't support flexible partial update when " |
739 | 1 | "'merge_type' is specified"); |
740 | 1 | } |
741 | 163 | if (!http_req->header(HTTP_WHERE).empty()) { |
742 | 1 | return Status::InvalidArgument( |
743 | 1 | "Don't support flexible partial update when " |
744 | 1 | "'where' is specified"); |
745 | 1 | } |
746 | 163 | } |
747 | 162 | request.__set_unique_key_update_mode(unique_key_update_mode); |
748 | 162 | } else { |
749 | 1 | return Status::InvalidArgument( |
750 | 1 | "Invalid unique_key_partial_mode {}, must be one of 'UPSERT', " |
751 | 1 | "'UPDATE_FIXED_COLUMNS' or 'UPDATE_FLEXIBLE_COLUMNS'", |
752 | 1 | unique_key_update_mode_str); |
753 | 1 | } |
754 | 170 | } |
755 | | |
756 | 2.39k | if (http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty() && |
757 | 2.39k | !http_req->header(HTTP_PARTIAL_COLUMNS).empty()) { |
758 | | // only consider `partial_columns` parameter when `unique_key_update_mode` is not set |
759 | 338 | if (iequal(http_req->header(HTTP_PARTIAL_COLUMNS), "true")) { |
760 | 332 | request.__set_unique_key_update_mode(TUniqueKeyUpdateMode::UPDATE_FIXED_COLUMNS); |
761 | | // for backward compatibility |
762 | 332 | request.__set_partial_update(true); |
763 | 332 | } |
764 | 338 | } |
765 | | |
766 | 2.39k | if (!http_req->header(HTTP_PARTIAL_UPDATE_NEW_ROW_POLICY).empty()) { |
767 | 18 | static const std::map<std::string, TPartialUpdateNewRowPolicy::type> policy_map { |
768 | 18 | {"APPEND", TPartialUpdateNewRowPolicy::APPEND}, |
769 | 18 | {"ERROR", TPartialUpdateNewRowPolicy::ERROR}}; |
770 | | |
771 | 18 | auto policy_name = http_req->header(HTTP_PARTIAL_UPDATE_NEW_ROW_POLICY); |
772 | 18 | std::transform(policy_name.begin(), policy_name.end(), policy_name.begin(), |
773 | 92 | [](unsigned char c) { return std::toupper(c); }); |
774 | 18 | auto it = policy_map.find(policy_name); |
775 | 18 | if (it == policy_map.end()) { |
776 | 0 | return Status::InvalidArgument( |
777 | 0 | "Invalid partial_update_new_key_behavior {}, must be one of {'APPEND', " |
778 | 0 | "'ERROR'}", |
779 | 0 | policy_name); |
780 | 0 | } |
781 | 18 | request.__set_partial_update_new_key_policy(it->second); |
782 | 18 | } |
783 | | |
784 | 2.39k | if (!http_req->header(HTTP_MEMTABLE_ON_SINKNODE).empty()) { |
785 | 52 | bool value = iequal(http_req->header(HTTP_MEMTABLE_ON_SINKNODE), "true"); |
786 | 52 | request.__set_memtable_on_sink_node(value); |
787 | 52 | } |
788 | 2.39k | if (!http_req->header(HTTP_LOAD_STREAM_PER_NODE).empty()) { |
789 | 0 | int stream_per_node = DORIS_TRY( |
790 | 0 | safe_stoi(http_req->header(HTTP_LOAD_STREAM_PER_NODE), HTTP_LOAD_STREAM_PER_NODE)); |
791 | 0 | request.__set_stream_per_node(stream_per_node); |
792 | 0 | } |
793 | 2.39k | if (ctx->group_commit) { |
794 | 421 | request.__set_group_commit_mode(ctx->group_commit_mode); |
795 | 421 | } |
796 | | |
797 | | // Keep cloud_cluster for compatibility with old FEs during rolling upgrade. New FEs use |
798 | | // backend_id below to bind planning to the compute group of the receiving BE. |
799 | 2.39k | if (!http_req->header(HTTP_COMPUTE_GROUP).empty()) { |
800 | 0 | request.__set_cloud_cluster(http_req->header(HTTP_COMPUTE_GROUP)); |
801 | 2.39k | } else if (!http_req->header(HTTP_CLOUD_CLUSTER).empty()) { |
802 | 66 | request.__set_cloud_cluster(http_req->header(HTTP_CLOUD_CLUSTER)); |
803 | 66 | } |
804 | | |
805 | 2.39k | if (_exec_env->cluster_info()->backend_id != 0) { |
806 | 2.39k | request.__set_backend_id(_exec_env->cluster_info()->backend_id); |
807 | 2.39k | } else { |
808 | 5 | LOG(WARNING) << "_exec_env->cluster_info not set backend_id"; |
809 | 5 | } |
810 | | |
811 | 2.39k | if (!http_req->header(HTTP_EMPTY_FIELD_AS_NULL).empty()) { |
812 | 1 | if (iequal(http_req->header(HTTP_EMPTY_FIELD_AS_NULL), "true")) { |
813 | 1 | request.__set_empty_field_as_null(true); |
814 | 1 | } |
815 | 1 | } |
816 | | |
817 | 2.39k | #ifndef BE_TEST |
818 | | // plan this load |
819 | 2.39k | TNetworkAddress master_addr = _exec_env->cluster_info()->master_fe_addr; |
820 | 2.39k | int64_t stream_load_put_start_time = MonotonicNanos(); |
821 | 2.39k | RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>( |
822 | 2.39k | master_addr.hostname, master_addr.port, |
823 | 2.39k | [&request, ctx](FrontendServiceConnection& client) { |
824 | 2.39k | client->streamLoadPut(ctx->put_result, request); |
825 | 2.39k | })); |
826 | 2.39k | ctx->stream_load_put_cost_nanos = MonotonicNanos() - stream_load_put_start_time; |
827 | | #else |
828 | | ctx->put_result = k_stream_load_put_result; |
829 | | #endif |
830 | 2.39k | Status plan_status(Status::create(ctx->put_result.status)); |
831 | 2.39k | if (!plan_status.ok()) { |
832 | 130 | LOG(WARNING) << "plan streaming load failed. errmsg=" << plan_status << ctx->brief(); |
833 | 130 | return plan_status; |
834 | 130 | } |
835 | 2.39k | DCHECK(ctx->put_result.__isset.pipeline_params); |
836 | 2.26k | ctx->put_result.pipeline_params.query_options.__set_enable_strict_cast(false); |
837 | 2.26k | ctx->put_result.pipeline_params.query_options.__set_enable_insert_strict(strictMode); |
838 | 2.26k | if (config::is_cloud_mode() && ctx->two_phase_commit && ctx->is_mow_table()) { |
839 | 1 | return Status::NotSupported("stream load 2pc is unsupported for mow table"); |
840 | 1 | } |
841 | 2.26k | if (iequal(ctx->group_commit_mode, ASYNC_MODE)) { |
842 | | // FIXME find a way to avoid chunked stream load write large WALs |
843 | 422 | size_t content_length = 0; |
844 | 422 | if (!http_req->header(HttpHeaders::CONTENT_LENGTH).empty()) { |
845 | 419 | try { |
846 | 419 | content_length = std::stol(http_req->header(HttpHeaders::CONTENT_LENGTH)); |
847 | 419 | } catch (const std::exception& e) { |
848 | 0 | return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}", |
849 | 0 | http_req->header(HttpHeaders::CONTENT_LENGTH), |
850 | 0 | e.what()); |
851 | 0 | } |
852 | 420 | if (LoadUtil::is_compressed_load(ctx->compress_type, ctx->format)) { |
853 | 5 | content_length *= 3; |
854 | 5 | } |
855 | 420 | } |
856 | 423 | ctx->put_result.pipeline_params.__set_content_length(content_length); |
857 | 423 | } |
858 | | |
859 | 2.26k | VLOG_NOTICE << "params is " |
860 | 3 | << apache::thrift::ThriftDebugString(ctx->put_result.pipeline_params); |
861 | | // if we not use streaming, we must download total content before we begin |
862 | | // to process this load |
863 | 2.26k | if (!ctx->use_streaming) { |
864 | 17 | return Status::OK(); |
865 | 17 | } |
866 | | |
867 | 2.25k | TPipelineFragmentParamsList mocked; |
868 | 2.25k | return _exec_env->stream_load_executor()->execute_plan_fragment( |
869 | 2.25k | ctx, mocked, [http_req, this](std::shared_ptr<StreamLoadContext> ctx) { |
870 | 2.25k | _on_finish(ctx, http_req); |
871 | 2.25k | }); |
872 | 2.26k | } |
873 | | |
874 | | Status StreamLoadAction::_data_saved_path(HttpRequest* req, std::string* file_path, |
875 | 18 | int64_t file_bytes) { |
876 | 18 | std::string prefix; |
877 | 18 | RETURN_IF_ERROR(_exec_env->load_path_mgr()->allocate_dir(req->param(HTTP_DB_KEY), "", &prefix, |
878 | 18 | file_bytes)); |
879 | 18 | timeval tv; |
880 | 18 | gettimeofday(&tv, nullptr); |
881 | 18 | struct tm tm; |
882 | 18 | time_t cur_sec = tv.tv_sec; |
883 | 18 | localtime_r(&cur_sec, &tm); |
884 | 18 | char buf[64]; |
885 | 18 | strftime(buf, 64, "%Y%m%d%H%M%S", &tm); |
886 | 18 | std::stringstream ss; |
887 | 18 | ss << prefix << "/" << req->param(HTTP_TABLE_KEY) << "." << buf << "." << tv.tv_usec; |
888 | 18 | *file_path = ss.str(); |
889 | 18 | return Status::OK(); |
890 | 18 | } |
891 | | |
892 | | void StreamLoadAction::_save_stream_load_record(std::shared_ptr<StreamLoadContext> ctx, |
893 | 2.39k | const std::string& str) { |
894 | 2.39k | std::shared_ptr<StreamLoadRecorder> stream_load_recorder = |
895 | 2.39k | ExecEnv::GetInstance()->storage_engine().get_stream_load_recorder(); |
896 | | |
897 | 2.40k | if (stream_load_recorder != nullptr) { |
898 | 2.40k | std::string key = |
899 | 2.40k | std::to_string(ctx->start_millis + ctx->load_cost_millis) + "_" + ctx->label; |
900 | 2.40k | auto st = stream_load_recorder->put(key, str); |
901 | 2.40k | if (st.ok()) { |
902 | 2.40k | LOG(INFO) << "put stream_load_record rocksdb successfully. label: " << ctx->label |
903 | 2.40k | << ", key: " << key; |
904 | 2.40k | } |
905 | 18.4E | } else { |
906 | 18.4E | LOG(WARNING) << "put stream_load_record rocksdb failed. stream_load_recorder is null."; |
907 | 18.4E | } |
908 | 2.39k | } |
909 | | |
910 | | Status StreamLoadAction::_check_wal_space(const std::string& group_commit_mode, |
911 | 1.87k | int64_t content_length) { |
912 | 1.87k | if (iequal(group_commit_mode, ASYNC_MODE) && |
913 | 1.87k | !load_size_smaller_than_wal_limit(content_length)) { |
914 | 0 | std::stringstream ss; |
915 | 0 | ss << "There is no space for group commit stream load async WAL. This stream load " |
916 | 0 | "size is " |
917 | 0 | << content_length |
918 | 0 | << ". WAL dir info: " << ExecEnv::GetInstance()->wal_mgr()->get_wal_dirs_info_string(); |
919 | 0 | LOG(WARNING) << ss.str(); |
920 | 0 | return Status::Error<EXCEEDED_LIMIT>(ss.str()); |
921 | 0 | } |
922 | 1.87k | return Status::OK(); |
923 | 1.87k | } |
924 | | |
925 | | Status StreamLoadAction::_can_group_commit(HttpRequest* req, std::shared_ptr<StreamLoadContext> ctx, |
926 | | std::string& group_commit_header, |
927 | 2.44k | bool& can_group_commit) { |
928 | 2.44k | int64_t content_length = 0; |
929 | 2.44k | const auto& content_length_str = req->header(HttpHeaders::CONTENT_LENGTH); |
930 | 2.44k | if (!content_length_str.empty()) { |
931 | 2.16k | try { |
932 | 2.16k | content_length = std::stoll(content_length_str); |
933 | 2.16k | } catch (const std::exception& e) { |
934 | 0 | return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}", |
935 | 0 | content_length_str, e.what()); |
936 | 0 | } |
937 | 2.16k | } |
938 | 2.44k | if (content_length < 0) { |
939 | 0 | std::stringstream ss; |
940 | 0 | ss << "This stream load content length <0 (" << content_length |
941 | 0 | << "), please check your content length."; |
942 | 0 | LOG(WARNING) << ss.str(); |
943 | 0 | return Status::InvalidArgument(ss.str()); |
944 | 0 | } |
945 | 2.44k | auto is_chunk = !req->header(HttpHeaders::TRANSFER_ENCODING).empty() && |
946 | 2.44k | req->header(HttpHeaders::TRANSFER_ENCODING).find(CHUNK) != std::string::npos; |
947 | 2.44k | if (content_length == 0 && !is_chunk) { |
948 | | // off_mode and empty |
949 | 7 | can_group_commit = false; |
950 | 7 | return Status::OK(); |
951 | 7 | } |
952 | 2.44k | if (is_chunk) { |
953 | 285 | ctx->label = ""; |
954 | 285 | } |
955 | | |
956 | 2.44k | auto partial_columns = !req->header(HTTP_PARTIAL_COLUMNS).empty() && |
957 | 2.44k | iequal(req->header(HTTP_PARTIAL_COLUMNS), "true"); |
958 | 2.44k | auto temp_partitions = !req->header(HTTP_TEMP_PARTITIONS).empty(); |
959 | 2.44k | auto partitions = !req->header(HTTP_PARTITIONS).empty(); |
960 | 2.44k | auto update_mode = |
961 | 2.44k | !req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty() && |
962 | 2.44k | (iequal(req->header(HTTP_UNIQUE_KEY_UPDATE_MODE), "UPDATE_FIXED_COLUMNS") || |
963 | 170 | iequal(req->header(HTTP_UNIQUE_KEY_UPDATE_MODE), "UPDATE_FLEXIBLE_COLUMNS")); |
964 | 2.44k | if (!partial_columns && !partitions && !temp_partitions && !ctx->two_phase_commit && |
965 | 2.44k | !update_mode) { |
966 | 1.87k | if (!config::wait_internal_group_commit_finish && !group_commit_header.empty() && |
967 | 1.86k | !ctx->label.empty()) { |
968 | 1 | return Status::InvalidArgument("label and group_commit can't be set at the same time"); |
969 | 1 | } |
970 | 1.86k | RETURN_IF_ERROR(_check_wal_space(group_commit_header, content_length)); |
971 | 1.86k | can_group_commit = true; |
972 | 1.86k | } |
973 | 2.43k | return Status::OK(); |
974 | 2.44k | } |
975 | | |
976 | | Status StreamLoadAction::_handle_group_commit(HttpRequest* req, |
977 | 2.44k | std::shared_ptr<StreamLoadContext> ctx) { |
978 | 2.44k | std::string group_commit_header = req->header(HTTP_GROUP_COMMIT); |
979 | 2.44k | if (!group_commit_header.empty() && !iequal(group_commit_header, SYNC_MODE) && |
980 | 2.44k | !iequal(group_commit_header, ASYNC_MODE) && !iequal(group_commit_header, OFF_MODE)) { |
981 | 0 | return Status::InvalidArgument( |
982 | 0 | "group_commit can only be [async_mode, sync_mode, off_mode]"); |
983 | 0 | } |
984 | 2.44k | if (config::wait_internal_group_commit_finish) { |
985 | 0 | group_commit_header = SYNC_MODE; |
986 | 0 | } |
987 | | |
988 | | // if group_commit_header is off_mode, we will not use group commit |
989 | 2.44k | if (iequal(group_commit_header, OFF_MODE)) { |
990 | 1 | ctx->group_commit_mode = OFF_MODE; |
991 | 1 | ctx->group_commit = false; |
992 | 1 | return Status::OK(); |
993 | 1 | } |
994 | 2.44k | bool can_group_commit = false; |
995 | 2.44k | RETURN_IF_ERROR(_can_group_commit(req, ctx, group_commit_header, can_group_commit)); |
996 | 2.44k | if (!can_group_commit) { |
997 | 577 | ctx->group_commit_mode = OFF_MODE; |
998 | 577 | ctx->group_commit = false; |
999 | 1.86k | } else { |
1000 | 1.86k | if (!group_commit_header.empty()) { |
1001 | 424 | ctx->group_commit_mode = group_commit_header; |
1002 | 424 | ctx->group_commit = true; |
1003 | 1.44k | } else { |
1004 | | // use table property to decide group commit or not |
1005 | 1.44k | ctx->group_commit_mode = ""; |
1006 | 1.44k | ctx->group_commit = false; |
1007 | 1.44k | } |
1008 | 1.86k | } |
1009 | 2.44k | return Status::OK(); |
1010 | 2.44k | } |
1011 | | |
1012 | | } // namespace doris |