Skip to content

Commit 3ec56d7

Browse files
Userwhite曹俊辉dataroaring
authored
[Enhancement] use stream load async return to optimize the performance under high concurrency (#53144)
Issue Number: close #53139 Co-authored-by: 曹俊辉 <[email protected]> Co-authored-by: Yongqiang YANG <[email protected]>
1 parent 7ccdbc1 commit 3ec56d7

18 files changed

+227
-81
lines changed

be/src/common/config.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,14 @@ DEFINE_Bool(enable_all_http_auth, "false");
567567
// Number of webserver workers
568568
DEFINE_Int32(webserver_num_workers, "128");
569569

570+
// Async replies: stream load only now
571+
// reply wait timeout only happens if:
572+
// 1. Stream load fragment execution times out
573+
// HTTP request freed → stream load canceled
574+
// 2. Client disconnects
575+
DEFINE_mInt32(async_reply_timeout_s, "60");
576+
DEFINE_Validator(async_reply_timeout_s, [](const int config) -> bool { return config >= 3; });
577+
570578
DEFINE_Bool(enable_single_replica_load, "true");
571579
// Number of download workers for single replica load
572580
DEFINE_Int32(single_replica_load_download_num_workers, "64");

be/src/common/config.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,13 @@ DECLARE_Bool(enable_all_http_auth);
611611
// Number of webserver workers
612612
DECLARE_Int32(webserver_num_workers);
613613

614+
// Async replies: stream load only now
615+
// reply wait timeout only happens if:
616+
// 1. Stream load fragment execution times out
617+
// HTTP request freed → stream load canceled
618+
// 2. Client disconnects
619+
DECLARE_mInt32(async_reply_timeout_s);
620+
614621
DECLARE_Bool(enable_single_replica_load);
615622
// Number of download workers for single replica load
616623
DECLARE_Int32(single_replica_load_download_num_workers);

be/src/http/action/http_stream.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Status HttpStreamAction::_handle(HttpRequest* http_req, std::shared_ptr<StreamLo
133133
RETURN_IF_ERROR(ctx->body_sink->finish());
134134

135135
// wait stream load finish
136-
RETURN_IF_ERROR(ctx->future.get());
136+
RETURN_IF_ERROR(ctx->load_status_future.get());
137137

138138
if (ctx->group_commit) {
139139
LOG(INFO) << "skip commit because this is group commit, pipe_id=" << ctx->id.to_string();

be/src/http/action/stream_load.cpp

Lines changed: 88 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
#include <sys/time.h>
3030
#include <thrift/protocol/TDebugProtocol.h>
3131

32+
#include <algorithm>
3233
#include <cstdint>
3334
#include <cstdlib>
3435
#include <ctime>
36+
#include <functional>
3537
#include <future>
3638
#include <sstream>
3739
#include <stdexcept>
@@ -106,17 +108,85 @@ void StreamLoadAction::handle(HttpRequest* req) {
106108
return;
107109
}
108110

111+
{
112+
std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
113+
ctx->_can_send_reply = true;
114+
ctx->_can_send_reply_cv.notify_all();
115+
}
116+
109117
// status already set to fail
110118
if (ctx->status.ok()) {
111-
ctx->status = _handle(ctx);
119+
ctx->status = _handle(ctx, req);
112120
if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
113-
LOG(WARNING) << "handle streaming load failed, id=" << ctx->id
114-
<< ", errmsg=" << ctx->status;
121+
_send_reply(ctx, req);
115122
}
116123
}
124+
}
125+
126+
Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
127+
if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) {
128+
LOG(WARNING) << "recevie body don't equal with body bytes, body_bytes=" << ctx->body_bytes
129+
<< ", receive_bytes=" << ctx->receive_bytes << ", id=" << ctx->id;
130+
return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't equal with body bytes");
131+
}
132+
133+
// if we use non-streaming, MessageBodyFileSink.finish will close the file
134+
RETURN_IF_ERROR(ctx->body_sink->finish());
135+
if (!ctx->use_streaming) {
136+
// we need to close file first, then execute_plan_fragment here
137+
ctx->body_sink.reset();
138+
TPipelineFragmentParamsList mocked;
139+
RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment(
140+
ctx, mocked,
141+
[req, this](std::shared_ptr<StreamLoadContext> ctx) { _on_finish(ctx, req); }));
142+
}
143+
144+
return Status::OK();
145+
}
146+
147+
void StreamLoadAction::_on_finish(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
148+
ctx->status = ctx->load_status_future.get();
149+
if (ctx->status.ok()) {
150+
if (ctx->group_commit) {
151+
LOG(INFO) << "skip commit because this is group commit, pipe_id="
152+
<< ctx->id.to_string();
153+
} else if (ctx->two_phase_commit) {
154+
int64_t pre_commit_start_time = MonotonicNanos();
155+
ctx->status = _exec_env->stream_load_executor()->pre_commit_txn(ctx.get());
156+
ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - pre_commit_start_time;
157+
} else {
158+
// If put file success we need commit this load
159+
int64_t commit_and_publish_start_time = MonotonicNanos();
160+
ctx->status = _exec_env->stream_load_executor()->commit_txn(ctx.get());
161+
ctx->commit_and_publish_txn_cost_nanos =
162+
MonotonicNanos() - commit_and_publish_start_time;
163+
g_stream_load_commit_and_publish_latency_ms
164+
<< ctx->commit_and_publish_txn_cost_nanos / 1000000;
165+
}
166+
}
167+
_send_reply(ctx, req);
168+
}
169+
170+
void StreamLoadAction::_send_reply(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
171+
std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
172+
// 1. _can_send_reply: ensure `send_reply` is invoked only after on_header/handle complete,
173+
// avoid client errors (e.g., broken pipe).
174+
// 2. _finish_send_reply: Prevent duplicate reply sending; skip reply if HTTP request is canceled
175+
// due to long import execution time.
176+
while (!ctx->_finish_send_reply && !ctx->_can_send_reply) {
177+
ctx->_can_send_reply_cv.wait(lock1);
178+
}
179+
if (ctx->_finish_send_reply) {
180+
return;
181+
}
182+
DCHECK(ctx->_can_send_reply);
183+
ctx->_finish_send_reply = true;
184+
ctx->_can_send_reply_cv.notify_all();
117185
ctx->load_cost_millis = UnixMillis() - ctx->start_millis;
118186

119187
if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
188+
LOG(WARNING) << "handle streaming load failed, id=" << ctx->id
189+
<< ", errmsg=" << ctx->status;
120190
if (ctx->need_rollback) {
121191
_exec_env->stream_load_executor()->rollback_txn(ctx.get());
122192
ctx->need_rollback = false;
@@ -129,7 +199,7 @@ void StreamLoadAction::handle(HttpRequest* req) {
129199
auto str = ctx->to_json();
130200
// add new line at end
131201
str = str + '\n';
132-
HttpChannel::send_reply(req, str);
202+
133203
#ifndef BE_TEST
134204
if (config::enable_stream_load_record || config::enable_stream_load_record_to_audit_log_table) {
135205
if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) {
@@ -139,6 +209,8 @@ void StreamLoadAction::handle(HttpRequest* req) {
139209
}
140210
#endif
141211

212+
HttpChannel::send_reply(req, str);
213+
142214
LOG(INFO) << "finished to execute stream load. label=" << ctx->label
143215
<< ", txn_id=" << ctx->txn_id << ", query_id=" << ctx->id
144216
<< ", load_cost_ms=" << ctx->load_cost_millis << ", receive_data_cost_ms="
@@ -160,46 +232,9 @@ void StreamLoadAction::handle(HttpRequest* req) {
160232
}
161233
}
162234

163-
Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx) {
164-
if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) {
165-
LOG(WARNING) << "recevie body don't equal with body bytes, body_bytes=" << ctx->body_bytes
166-
<< ", receive_bytes=" << ctx->receive_bytes << ", id=" << ctx->id;
167-
return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't equal with body bytes");
168-
}
169-
170-
// if we use non-streaming, MessageBodyFileSink.finish will close the file
171-
RETURN_IF_ERROR(ctx->body_sink->finish());
172-
if (!ctx->use_streaming) {
173-
// we need to close file first, then execute_plan_fragment here
174-
ctx->body_sink.reset();
175-
TPipelineFragmentParamsList mocked;
176-
RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment(ctx, mocked));
177-
}
178-
179-
// wait stream load finish
180-
RETURN_IF_ERROR(ctx->future.get());
181-
182-
if (ctx->group_commit) {
183-
LOG(INFO) << "skip commit because this is group commit, pipe_id=" << ctx->id.to_string();
184-
return Status::OK();
185-
}
186-
187-
if (ctx->two_phase_commit) {
188-
int64_t pre_commit_start_time = MonotonicNanos();
189-
RETURN_IF_ERROR(_exec_env->stream_load_executor()->pre_commit_txn(ctx.get()));
190-
ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - pre_commit_start_time;
191-
} else {
192-
// If put file success we need commit this load
193-
int64_t commit_and_publish_start_time = MonotonicNanos();
194-
RETURN_IF_ERROR(_exec_env->stream_load_executor()->commit_txn(ctx.get()));
195-
ctx->commit_and_publish_txn_cost_nanos = MonotonicNanos() - commit_and_publish_start_time;
196-
g_stream_load_commit_and_publish_latency_ms
197-
<< ctx->commit_and_publish_txn_cost_nanos / 1000000;
198-
}
199-
return Status::OK();
200-
}
201-
202235
int StreamLoadAction::on_header(HttpRequest* req) {
236+
req->mark_send_reply();
237+
203238
streaming_load_current_processing->increment(1);
204239

205240
std::shared_ptr<StreamLoadContext> ctx = std::make_shared<StreamLoadContext>(_exec_env);
@@ -228,26 +263,12 @@ int StreamLoadAction::on_header(HttpRequest* req) {
228263
}
229264
if (!st.ok()) {
230265
ctx->status = std::move(st);
231-
if (ctx->need_rollback) {
232-
_exec_env->stream_load_executor()->rollback_txn(ctx.get());
233-
ctx->need_rollback = false;
266+
{
267+
std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
268+
ctx->_can_send_reply = true;
269+
ctx->_can_send_reply_cv.notify_all();
234270
}
235-
if (ctx->body_sink != nullptr) {
236-
ctx->body_sink->cancel(ctx->status.to_string());
237-
}
238-
auto str = ctx->to_json();
239-
// add new line at end
240-
str = str + '\n';
241-
HttpChannel::send_reply(req, str);
242-
#ifndef BE_TEST
243-
if (config::enable_stream_load_record ||
244-
config::enable_stream_load_record_to_audit_log_table) {
245-
if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) {
246-
str = ctx->prepare_stream_load_record(str);
247-
_save_stream_load_record(ctx, str);
248-
}
249-
}
250-
#endif
271+
_send_reply(ctx, req);
251272
return -1;
252273
}
253274
return 0;
@@ -821,8 +842,12 @@ Status StreamLoadAction::_process_put(HttpRequest* http_req,
821842
if (!ctx->use_streaming) {
822843
return Status::OK();
823844
}
845+
824846
TPipelineFragmentParamsList mocked;
825-
return _exec_env->stream_load_executor()->execute_plan_fragment(ctx, mocked);
847+
return _exec_env->stream_load_executor()->execute_plan_fragment(
848+
ctx, mocked, [http_req, this](std::shared_ptr<StreamLoadContext> ctx) {
849+
_on_finish(ctx, http_req);
850+
});
826851
}
827852

828853
Status StreamLoadAction::_data_saved_path(HttpRequest* req, std::string* file_path,

be/src/http/action/stream_load.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#pragma once
1919

2020
#include <memory>
21+
#include <mutex>
2122
#include <string>
2223

2324
#include "http/http_handler.h"
@@ -46,11 +47,13 @@ class StreamLoadAction : public HttpHandler {
4647

4748
private:
4849
Status _on_header(HttpRequest* http_req, std::shared_ptr<StreamLoadContext> ctx);
49-
Status _handle(std::shared_ptr<StreamLoadContext> ctx);
50+
Status _handle(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
5051
Status _data_saved_path(HttpRequest* req, std::string* file_path, int64_t file_bytes);
5152
Status _process_put(HttpRequest* http_req, std::shared_ptr<StreamLoadContext> ctx);
5253
void _save_stream_load_record(std::shared_ptr<StreamLoadContext> ctx, const std::string& str);
5354
Status _handle_group_commit(HttpRequest* http_req, std::shared_ptr<StreamLoadContext> ctx);
55+
void _on_finish(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
56+
void _send_reply(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req);
5457

5558
private:
5659
ExecEnv* _exec_env;

be/src/http/ev_http_server.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static void on_chunked(struct evhttp_request* ev_req, void* param) {
5656

5757
static void on_free(struct evhttp_request* ev_req, void* arg) {
5858
HttpRequest* request = (HttpRequest*)arg;
59+
request->wait_finish_send_reply();
5960
delete request;
6061
}
6162

be/src/http/http_channel.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void HttpChannel::send_error(HttpRequest* request, HttpStatus status) {
5353
void HttpChannel::send_reply(HttpRequest* request, HttpStatus status) {
5454
evhttp_send_reply(request->get_evhttp_request(), status, default_reason(status).c_str(),
5555
nullptr);
56+
request->finish_send_reply();
5657
}
5758

5859
void HttpChannel::send_reply(HttpRequest* request, HttpStatus status, const std::string& content) {
@@ -66,6 +67,7 @@ void HttpChannel::send_reply(HttpRequest* request, HttpStatus status, const std:
6667
evbuffer_add(evb, content.c_str(), content.size());
6768
}
6869
evhttp_send_reply(request->get_evhttp_request(), status, default_reason(status).c_str(), evb);
70+
request->finish_send_reply();
6971
evbuffer_free(evb);
7072
}
7173

@@ -80,6 +82,7 @@ void HttpChannel::send_file(HttpRequest* request, int fd, size_t off, size_t siz
8082
bufferevent_add_to_rate_limit_group(buffer_event, rate_limit_group);
8183
}
8284
evhttp_send_reply(evhttp_request, HttpStatus::OK, default_reason(HttpStatus::OK).c_str(), evb);
85+
request->finish_send_reply();
8386
evbuffer_free(evb);
8487
}
8588

be/src/http/http_request.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@
2222
#include <event2/http_struct.h>
2323
#include <event2/keyvalq_struct.h>
2424

25+
#include <memory>
2526
#include <sstream>
2627
#include <string>
2728
#include <unordered_map>
2829
#include <utility>
2930

3031
#include "http/http_handler.h"
32+
#include "runtime/stream_load/stream_load_context.h"
33+
#include "util/stack_util.h"
3134

3235
namespace doris {
3336

@@ -141,4 +144,48 @@ const char* HttpRequest::remote_host() const {
141144
return _ev_req->remote_host;
142145
}
143146

147+
void HttpRequest::finish_send_reply() {
148+
if (_send_reply_type == REPLY_SYNC) {
149+
return;
150+
}
151+
152+
std::string infos;
153+
if (_handler_ctx != nullptr) {
154+
infos = reinterpret_cast<StreamLoadContext*>(_handler_ctx.get())->brief();
155+
}
156+
_http_reply_promise.set_value(true);
157+
}
158+
159+
void HttpRequest::wait_finish_send_reply() {
160+
if (_send_reply_type == REPLY_SYNC) {
161+
return;
162+
}
163+
164+
std::string infos;
165+
StreamLoadContext* ctx = nullptr;
166+
if (_handler_ctx != nullptr) {
167+
ctx = reinterpret_cast<StreamLoadContext*>(_handler_ctx.get());
168+
infos = ctx->brief();
169+
_handler->free_handler_ctx(_handler_ctx);
170+
}
171+
172+
VLOG_NOTICE << "start to wait send reply, infos=" << infos;
173+
auto status = _http_reply_future.wait_for(std::chrono::seconds(config::async_reply_timeout_s));
174+
// if request is timeout and can't cancel fragment in time, it will cause some new request block
175+
// so we will free cancelled request in time.
176+
if (status != std::future_status::ready) {
177+
LOG(WARNING) << "wait for send reply timeout, " << this->debug_string();
178+
std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
179+
// do not send_reply after free current request
180+
ctx->_can_send_reply = false;
181+
ctx->_finish_send_reply = true;
182+
ctx->_can_send_reply_cv.notify_all();
183+
} else {
184+
VLOG_NOTICE << "wait send reply finished";
185+
}
186+
187+
// delete _handler_ctx at the end, in case that finish_send_reply can't get detailed info
188+
_handler_ctx = nullptr;
189+
}
190+
144191
} // namespace doris

0 commit comments

Comments
 (0)