Coverage Report

Created: 2026-08-06 18:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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