be/src/load/group_commit/wal/wal_table.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 "load/group_commit/wal/wal_table.h" |
19 | | |
20 | | #include <absl/strings/str_split.h> |
21 | | #include <thrift/protocol/TDebugProtocol.h> |
22 | | |
23 | | #include "io/fs/local_file_system.h" |
24 | | #include "io/fs/stream_load_pipe.h" |
25 | | #include "load/group_commit/wal/wal_manager.h" |
26 | | #include "runtime/cluster_info.h" |
27 | | #include "runtime/fragment_mgr.h" |
28 | | #include "service/http/action/http_stream.h" |
29 | | #include "service/http/action/stream_load.h" |
30 | | #include "service/http/ev_http_server.h" |
31 | | #include "service/http/http_common.h" |
32 | | #include "service/http/http_headers.h" |
33 | | #include "service/http/utils.h" |
34 | | #include "util/client_cache.h" |
35 | | #include "util/path_util.h" |
36 | | #include "util/thrift_rpc_helper.h" |
37 | | |
38 | | namespace doris { |
39 | | |
40 | | bvar::Adder<uint64_t> wal_fail("group_commit_wal_fail"); |
41 | | |
42 | | WalTable::WalTable(ExecEnv* exec_env, int64_t db_id, int64_t table_id) |
43 | 1 | : _exec_env(exec_env), _db_id(db_id), _table_id(table_id) { |
44 | 1 | _http_stream_action = std::make_shared<HttpStreamAction>(exec_env); |
45 | 1 | } |
46 | 1 | WalTable::~WalTable() {} |
47 | | |
48 | 1 | void WalTable::add_wal(int64_t wal_id, std::string wal) { |
49 | 1 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
50 | 1 | LOG(INFO) << "add replay wal=" << wal; |
51 | 1 | auto wal_info = std::make_shared<WalInfo>(wal_id, wal, 0, UnixMillis()); |
52 | 1 | _replay_wal_map.emplace(wal, wal_info); |
53 | 1 | } |
54 | | |
55 | 2 | void WalTable::_pick_relay_wals() { |
56 | 2 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
57 | 2 | std::vector<std::string> need_replay_wals; |
58 | 2 | std::vector<std::string> need_erase_wals; |
59 | 2 | for (const auto& [wal_path, wal_info] : _replay_wal_map) { |
60 | 2 | if (config::group_commit_wait_replay_wal_finish && |
61 | 2 | wal_info->get_retry_num() >= config::group_commit_replay_wal_retry_num) { |
62 | 0 | LOG(WARNING) << "failed to replay wal=" << wal_path << " after retry " |
63 | 0 | << wal_info->get_retry_num() << " times"; |
64 | 0 | [[maybe_unused]] auto st = _exec_env->wal_mgr()->rename_to_tmp_path( |
65 | 0 | wal_path, _table_id, wal_info->get_wal_id()); |
66 | 0 | auto notify_st = _exec_env->wal_mgr()->notify_relay_wal(wal_info->get_wal_id()); |
67 | 0 | if (!notify_st.ok()) { |
68 | 0 | LOG(WARNING) << "notify wal " << wal_info->get_wal_id() << " fail"; |
69 | 0 | } |
70 | 0 | need_erase_wals.push_back(wal_path); |
71 | 0 | continue; |
72 | 0 | } |
73 | 2 | if (_need_replay(wal_info)) { |
74 | 2 | need_replay_wals.push_back(wal_path); |
75 | 2 | } |
76 | 2 | } |
77 | 2 | for (const auto& wal : need_erase_wals) { |
78 | 0 | _replay_wal_map.erase(wal); |
79 | 0 | } |
80 | 2 | std::sort(need_replay_wals.begin(), need_replay_wals.end()); |
81 | 2 | for (const auto& wal : need_replay_wals) { |
82 | 2 | _replaying_queue.emplace_back(_replay_wal_map[wal]); |
83 | 2 | _replay_wal_map.erase(wal); |
84 | 2 | } |
85 | 2 | } |
86 | | |
87 | 2 | Status WalTable::_relay_wal_one_by_one() { |
88 | 2 | std::vector<std::shared_ptr<WalInfo>> need_retry_wals; |
89 | 2 | for (auto wal_info : _replaying_queue) { |
90 | 2 | wal_info->add_retry_num(); |
91 | 2 | Status st; |
92 | 2 | int64_t file_size = 0; |
93 | 2 | std::filesystem::path file_path(wal_info->get_wal_path()); |
94 | 2 | if (!std::filesystem::exists(file_path)) { |
95 | 0 | st = Status::InternalError("wal file {} does not exist", wal_info->get_wal_path()); |
96 | 2 | } else { |
97 | 2 | file_size = std::filesystem::file_size(file_path); |
98 | 2 | st = _replay_wal_internal(wal_info->get_wal_path()); |
99 | 2 | } |
100 | 2 | auto msg = st.msg(); |
101 | 2 | if (st.ok() || st.is<ErrorCode::PUBLISH_TIMEOUT>() || st.is<ErrorCode::NOT_FOUND>() || |
102 | 2 | st.is<ErrorCode::DATA_QUALITY_ERROR>() || |
103 | 2 | (msg.find("has already been used") != msg.npos && |
104 | 1 | (msg.find("COMMITTED") != msg.npos || msg.find("VISIBLE") != msg.npos))) { |
105 | 1 | LOG(INFO) << "succeed to replay wal=" << wal_info->get_wal_path() |
106 | 1 | << ", st=" << st.to_string() << ", file size=" << file_size; |
107 | | // delete wal |
108 | 1 | WARN_IF_ERROR(_exec_env->wal_mgr()->delete_wal(_table_id, wal_info->get_wal_id()), |
109 | 1 | "failed to delete wal=" + wal_info->get_wal_path()); |
110 | 1 | if (config::group_commit_wait_replay_wal_finish) { |
111 | 0 | RETURN_IF_ERROR(_exec_env->wal_mgr()->notify_relay_wal(wal_info->get_wal_id())); |
112 | 0 | } |
113 | 1 | } else { |
114 | 1 | doris::wal_fail << 1; |
115 | 1 | LOG(WARNING) << "failed to replay wal=" << wal_info->get_wal_path() |
116 | 1 | << ", st=" << st.to_string(); |
117 | 1 | { |
118 | 1 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
119 | 1 | _last_replay_wal_failed_reason = |
120 | 1 | "failed to replay wal=" + wal_info->get_wal_path() + |
121 | 1 | ", st=" + st.to_string().substr(0, 100); |
122 | 1 | } |
123 | 1 | need_retry_wals.push_back(wal_info); |
124 | 1 | } |
125 | 2 | } |
126 | 2 | { |
127 | 2 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
128 | 2 | _replaying_queue.clear(); |
129 | 2 | for (auto retry_wal_info : need_retry_wals) { |
130 | 1 | _replay_wal_map.emplace(retry_wal_info->get_wal_path(), retry_wal_info); |
131 | 1 | } |
132 | 2 | if (_replay_wal_map.empty()) { |
133 | 1 | _last_replay_wal_failed_reason.clear(); |
134 | 1 | } |
135 | 2 | } |
136 | 2 | return Status::OK(); |
137 | 2 | } |
138 | | |
139 | 2 | Status WalTable::replay_wals() { |
140 | 2 | { |
141 | 2 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
142 | 2 | if (_replay_wal_map.empty()) { |
143 | 0 | LOG(INFO) << "_replay_wal_map is empty, skip relaying for table_id=" << _table_id; |
144 | 0 | return Status::OK(); |
145 | 0 | } |
146 | 2 | if (!_replaying_queue.empty()) { |
147 | 0 | LOG(INFO) << "_replaying_queue is not empty, skip relaying for table_id=" << _table_id; |
148 | 0 | return Status::OK(); |
149 | 0 | } |
150 | 2 | } |
151 | 2 | VLOG_DEBUG << "Start replay wals for db=" << _db_id << ", table=" << _table_id |
152 | 0 | << ", wal size=" << _replay_wal_map.size(); |
153 | 2 | _pick_relay_wals(); |
154 | 2 | RETURN_IF_ERROR(_relay_wal_one_by_one()); |
155 | 2 | return Status::OK(); |
156 | 2 | } |
157 | | |
158 | 2 | bool WalTable::_need_replay(std::shared_ptr<WalInfo> wal_info) { |
159 | 2 | if (config::group_commit_wait_replay_wal_finish) { |
160 | 0 | return true; |
161 | 0 | } |
162 | | #ifndef BE_TEST |
163 | | int64_t replay_interval = 0; |
164 | | if (wal_info->get_retry_num() >= config::group_commit_replay_wal_retry_num) { |
165 | | replay_interval = |
166 | | int64_t(pow(2, config::group_commit_replay_wal_retry_num) * |
167 | | config::group_commit_replay_wal_retry_interval_seconds * 1000 + |
168 | | (wal_info->get_retry_num() - config::group_commit_replay_wal_retry_num) * |
169 | | config::group_commit_replay_wal_retry_interval_max_seconds * 1000); |
170 | | } else { |
171 | | replay_interval = int64_t(pow(2, wal_info->get_retry_num()) * |
172 | | config::group_commit_replay_wal_retry_interval_seconds * 1000); |
173 | | } |
174 | | return UnixMillis() - wal_info->get_start_time_ms() >= replay_interval; |
175 | | #else |
176 | 2 | return true; |
177 | 2 | #endif |
178 | 2 | } |
179 | | |
180 | 0 | Status WalTable::_try_abort_txn(int64_t db_id, std::string& label) { |
181 | 0 | TLoadTxnRollbackRequest request; |
182 | | // this is a fake, fe not check it now |
183 | | // should be removed in 3.1, use token instead |
184 | 0 | request.__set_auth_code(0); |
185 | 0 | request.__set_token(_exec_env->cluster_info()->curr_auth_token); |
186 | 0 | request.__set_db_id(db_id); |
187 | 0 | request.__set_label(label); |
188 | 0 | request.__set_reason("relay wal with label " + label); |
189 | 0 | TLoadTxnRollbackResult result; |
190 | 0 | TNetworkAddress master_addr = _exec_env->cluster_info()->master_fe_addr; |
191 | 0 | auto st = ThriftRpcHelper::rpc<FrontendServiceClient>( |
192 | 0 | master_addr.hostname, master_addr.port, |
193 | 0 | [&request, &result](FrontendServiceConnection& client) { |
194 | 0 | client->loadTxnRollback(result, request); |
195 | 0 | }); |
196 | 0 | if (st.ok()) { |
197 | 0 | auto result_status = Status::create<false>(result.status); |
198 | 0 | LOG(INFO) << "abort label " << label << ", result_status:" << result_status; |
199 | 0 | return result_status; |
200 | 0 | } else { |
201 | 0 | LOG(WARNING) << "abort label " << label << ", rpc error:" << st; |
202 | 0 | return st; |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | 2 | Status WalTable::_replay_wal_internal(const std::string& wal) { |
207 | 2 | LOG(INFO) << "start replay wal=" << wal; |
208 | 2 | int64_t version = -1; |
209 | 2 | int64_t backend_id = -1; |
210 | 2 | int64_t wal_id = -1; |
211 | 2 | std::string label = ""; |
212 | 2 | io::Path wal_path = wal; |
213 | 2 | auto file_name = wal_path.filename().string(); |
214 | 2 | RETURN_IF_ERROR(WalManager::parse_wal_path(file_name, version, backend_id, wal_id, label)); |
215 | | #ifndef BE_TEST |
216 | | if (!config::group_commit_wait_replay_wal_finish) { |
217 | | [[maybe_unused]] auto st = _try_abort_txn(_db_id, label); |
218 | | } |
219 | | #endif |
220 | 2 | DBUG_EXECUTE_IF("WalTable.replay_wals.stop", |
221 | 2 | { return Status::InternalError("WalTable.replay_wals.stop"); }); |
222 | 1 | return _replay_one_wal_with_streamload(wal_id, wal, label); |
223 | 2 | } |
224 | | |
225 | | Status WalTable::_construct_sql_str(const std::string& wal, const std::string& label, |
226 | 0 | std::string& sql_str) { |
227 | 0 | std::string columns; |
228 | 0 | RETURN_IF_ERROR(_read_wal_header(wal, columns)); |
229 | 0 | std::vector<std::string> column_id_vector = |
230 | 0 | absl::StrSplit(columns, ",", absl::SkipWhitespace()); |
231 | 0 | std::map<int64_t, std::string> column_info_map; |
232 | 0 | RETURN_IF_ERROR(_get_column_info(_db_id, _table_id, column_info_map)); |
233 | 0 | std::stringstream ss_name; |
234 | 0 | for (auto column_id_str : column_id_vector) { |
235 | 0 | try { |
236 | 0 | int64_t column_id = std::strtoll(column_id_str.c_str(), NULL, 10); |
237 | 0 | auto it = column_info_map.find(column_id); |
238 | 0 | if (it != column_info_map.end()) { |
239 | 0 | ss_name << "`" << it->second << "`,"; |
240 | 0 | column_info_map.erase(column_id); |
241 | 0 | } |
242 | 0 | } catch (const std::invalid_argument& e) { |
243 | 0 | return Status::InvalidArgument("Invalid format, {}", e.what()); |
244 | 0 | } |
245 | 0 | } |
246 | 0 | auto name = ss_name.str().substr(0, ss_name.str().size() - 1); |
247 | 0 | std::stringstream ss; |
248 | 0 | ss << "insert into doris_internal_table_id(" << _table_id << ") WITH LABEL " << label << " (" |
249 | 0 | << name << ") select " << name << " from http_stream(\"format\" = \"wal\", \"table_id\" = \"" |
250 | 0 | << std::to_string(_table_id) << "\")"; |
251 | 0 | sql_str = ss.str().data(); |
252 | 0 | return Status::OK(); |
253 | 0 | } |
254 | | |
255 | | Status WalTable::_handle_stream_load(int64_t wal_id, const std::string& wal, |
256 | 0 | const std::string& label) { |
257 | 0 | std::string sql_str; |
258 | 0 | RETURN_IF_ERROR(_construct_sql_str(wal, label, sql_str)); |
259 | 0 | std::shared_ptr<StreamLoadContext> ctx = std::make_shared<StreamLoadContext>(_exec_env); |
260 | 0 | ctx->sql_str = sql_str; |
261 | 0 | ctx->db_id = _db_id; |
262 | 0 | ctx->table_id = _table_id; |
263 | 0 | ctx->wal_id = wal_id; |
264 | 0 | ctx->label = label; |
265 | 0 | ctx->need_commit_self = false; |
266 | 0 | ctx->auth.token = _exec_env->cluster_info()->curr_auth_token; |
267 | 0 | ctx->auth.user = "admin"; |
268 | 0 | ctx->group_commit = false; |
269 | 0 | ctx->load_type = TLoadType::MANUL_LOAD; |
270 | 0 | ctx->load_src_type = TLoadSourceType::RAW; |
271 | 0 | ctx->max_filter_ratio = 1; |
272 | 0 | auto st = _http_stream_action->process_put(nullptr, ctx); |
273 | 0 | DBUG_EXECUTE_IF("WalTable::_handle_stream_load.fail", |
274 | 0 | { st = Status::InternalError("WalTable::_handle_stream_load.fail"); }); |
275 | 0 | if (st.ok()) { |
276 | | // wait stream load finish |
277 | 0 | RETURN_IF_ERROR(ctx->load_status_future.get()); |
278 | 0 | if (ctx->status.ok()) { |
279 | | // deprecated and should be removed in 3.1, use token instead. |
280 | 0 | ctx->auth.auth_code = wal_id; |
281 | 0 | st = _exec_env->stream_load_executor()->commit_txn(ctx.get()); |
282 | 0 | } else { |
283 | 0 | st = ctx->status; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | if (!st.ok()) { |
287 | 0 | _exec_env->stream_load_executor()->rollback_txn(ctx.get()); |
288 | 0 | } |
289 | 0 | return st; |
290 | 0 | } |
291 | | |
292 | | Status WalTable::_replay_one_wal_with_streamload(int64_t wal_id, const std::string& wal, |
293 | 1 | const std::string& label) { |
294 | | #ifndef BE_TEST |
295 | | return _handle_stream_load(wal_id, wal, label); |
296 | | #else |
297 | 1 | return Status::OK(); |
298 | 1 | #endif |
299 | 1 | } |
300 | | |
301 | 0 | void WalTable::stop() { |
302 | 0 | do { |
303 | 0 | { |
304 | 0 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
305 | 0 | if (_replay_wal_map.empty() && _replaying_queue.empty()) { |
306 | 0 | break; |
307 | 0 | } |
308 | 0 | LOG(INFO) << "stopping wal_table,wait for relay wal task done, now " |
309 | 0 | << _replay_wal_map.size() << " wals wait to replay, " |
310 | 0 | << _replaying_queue.size() << " wals are replaying"; |
311 | 0 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); |
312 | 0 | } |
313 | 0 | } while (true); |
314 | 0 | } |
315 | | |
316 | 0 | size_t WalTable::size() { |
317 | 0 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
318 | 0 | return _replay_wal_map.size() + _replaying_queue.size(); |
319 | 0 | } |
320 | | |
321 | 2 | std::string WalTable::get_last_replay_wal_failed_reason() const { |
322 | 2 | std::lock_guard<std::mutex> lock(_replay_wal_lock); |
323 | 2 | return _last_replay_wal_failed_reason; |
324 | 2 | } |
325 | | |
326 | | Status WalTable::_get_column_info(int64_t db_id, int64_t tb_id, |
327 | 0 | std::map<int64_t, std::string>& column_info_map) { |
328 | 0 | TGetColumnInfoRequest request; |
329 | 0 | request.__set_db_id(db_id); |
330 | 0 | request.__set_table_id(tb_id); |
331 | 0 | TGetColumnInfoResult result; |
332 | 0 | Status status; |
333 | 0 | TNetworkAddress master_addr = _exec_env->cluster_info()->master_fe_addr; |
334 | 0 | if (master_addr.hostname.empty() || master_addr.port == 0) { |
335 | 0 | status = Status::InternalError<false>("Have not get FE Master heartbeat yet"); |
336 | 0 | } else { |
337 | 0 | RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>( |
338 | 0 | master_addr.hostname, master_addr.port, |
339 | 0 | [&request, &result](FrontendServiceConnection& client) { |
340 | 0 | client->getColumnInfo(result, request); |
341 | 0 | })); |
342 | 0 | status = Status::create<false>(result.status); |
343 | 0 | if (!status.ok()) { |
344 | 0 | return status; |
345 | 0 | } |
346 | 0 | std::vector<TColumnInfo> column_element = result.columns; |
347 | 0 | for (auto column : column_element) { |
348 | 0 | auto column_name = column.column_name; |
349 | 0 | auto column_id = column.column_id; |
350 | 0 | column_info_map.emplace(column_id, column_name); |
351 | 0 | } |
352 | 0 | } |
353 | 0 | return status; |
354 | 0 | } |
355 | | |
356 | 0 | Status WalTable::_read_wal_header(const std::string& wal_path, std::string& columns) { |
357 | 0 | std::shared_ptr<doris::WalFileReader> wal_reader = |
358 | 0 | std::make_shared<doris::WalFileReader>(wal_path); |
359 | 0 | RETURN_IF_ERROR(wal_reader->init()); |
360 | 0 | uint32_t version = 0; |
361 | 0 | RETURN_IF_ERROR(wal_reader->read_header(version, columns)); |
362 | 0 | VLOG_DEBUG << "wal=" << wal_path << ",version=" << std::to_string(version) |
363 | 0 | << ",columns=" << columns; |
364 | 0 | RETURN_IF_ERROR(wal_reader->finalize()); |
365 | 0 | return Status::OK(); |
366 | 0 | } |
367 | | |
368 | | } // namespace doris |