couchbase 4.2.11-rc.1 → 4.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deps/couchbase-cxx-client/CMakeLists.txt +1 -0
- package/deps/couchbase-cxx-client/cmake/ThirdPartyDependencies.cmake +2 -0
- package/deps/couchbase-cxx-client/core/bucket.cxx +2 -2
- package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +11 -0
- package/deps/couchbase-cxx-client/core/io/mcbp_session.hxx +1 -0
- package/deps/couchbase-cxx-client/core/range_scan_load_balancer.cxx +141 -0
- package/deps/couchbase-cxx-client/core/range_scan_load_balancer.hxx +64 -0
- package/deps/couchbase-cxx-client/core/range_scan_orchestrator.cxx +224 -336
- package/deps/couchbase-cxx-client/core/range_scan_orchestrator.hxx +5 -6
- package/deps/couchbase-cxx-client/core/range_scan_orchestrator_options.hxx +6 -4
- package/deps/couchbase-cxx-client/core/scan_result.hxx +1 -11
- package/deps/couchbase-cxx-client/couchbase/bucket.hxx +2 -2
- package/deps/couchbase-cxx-client/couchbase/cluster.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/collection.hxx +1 -0
- package/deps/couchbase-cxx-client/couchbase/collection_query_index_manager.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/error_context.hxx +1 -0
- package/deps/couchbase-cxx-client/couchbase/get_links_analytics_options.hxx +2 -2
- package/deps/couchbase-cxx-client/couchbase/scope.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/search_options.hxx +2 -2
- package/deps/couchbase-cxx-client/couchbase/search_result.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/subdocument_error_context.hxx +1 -0
- package/deps/couchbase-cxx-client/couchbase/transactions/transaction_options.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase-sdk-cxx-black-duck-manifest.yaml +1 -0
- package/package.json +7 -7
- package/src/jstocbpp_autogen.hpp +0 -1
- package/deps/couchbase-cxx-client/core/scan_options.hxx +0 -44
@@ -16,19 +16,31 @@
|
|
16
16
|
#include "range_scan_orchestrator.hxx"
|
17
17
|
|
18
18
|
#include "agent.hxx"
|
19
|
-
#include "
|
19
|
+
#include "logger/logger.hxx"
|
20
|
+
#include "range_scan_load_balancer.hxx"
|
21
|
+
#include "range_scan_options.hxx"
|
22
|
+
|
20
23
|
#include "couchbase/error_codes.hxx"
|
24
|
+
#include "utils/movable_function.hxx"
|
21
25
|
|
22
26
|
#include <asio/bind_executor.hpp>
|
27
|
+
#include <asio/experimental/concurrent_channel.hpp>
|
23
28
|
#include <asio/io_context.hpp>
|
24
29
|
#include <asio/post.hpp>
|
25
30
|
|
26
|
-
#include <
|
27
|
-
|
28
|
-
#include <gsl/narrow>
|
31
|
+
#include <gsl/util>
|
29
32
|
|
33
|
+
#include <atomic>
|
34
|
+
#include <chrono>
|
30
35
|
#include <future>
|
31
|
-
#include <
|
36
|
+
#include <limits>
|
37
|
+
#include <map>
|
38
|
+
#include <memory>
|
39
|
+
#include <mutex>
|
40
|
+
#include <optional>
|
41
|
+
#include <system_error>
|
42
|
+
#include <variant>
|
43
|
+
#include <vector>
|
32
44
|
|
33
45
|
namespace couchbase::core
|
34
46
|
{
|
@@ -50,24 +62,26 @@ mutation_state_to_snapshot_requirements(const std::optional<mutation_state>& sta
|
|
50
62
|
return requirements;
|
51
63
|
}
|
52
64
|
|
65
|
+
// Sent by the vbucket scan stream when it either completes or fails with a fatal error
|
66
|
+
struct scan_stream_end_signal {
|
67
|
+
std::uint16_t vbucket_id;
|
68
|
+
std::optional<std::error_code> error{};
|
69
|
+
};
|
70
|
+
|
53
71
|
class range_scan_stream : public std::enable_shared_from_this<range_scan_stream>
|
54
72
|
{
|
73
|
+
// The stream has failed and should not be retried
|
55
74
|
struct failed {
|
56
75
|
std::error_code ec;
|
57
76
|
bool fatal{ true };
|
58
77
|
};
|
59
78
|
|
60
|
-
|
61
|
-
};
|
62
|
-
|
63
|
-
struct awaiting_retry {
|
64
|
-
std::error_code ec;
|
65
|
-
};
|
66
|
-
|
79
|
+
// The stream is currently running
|
67
80
|
struct running {
|
68
81
|
std::vector<std::byte> uuid;
|
69
82
|
};
|
70
83
|
|
84
|
+
// The stream has completed and the items have been retrieved
|
71
85
|
struct completed {
|
72
86
|
};
|
73
87
|
|
@@ -79,8 +93,8 @@ class range_scan_stream : public std::enable_shared_from_this<range_scan_stream>
|
|
79
93
|
range_scan_create_options create_options,
|
80
94
|
range_scan_continue_options continue_options,
|
81
95
|
std::shared_ptr<scan_stream_manager> stream_manager)
|
82
|
-
:
|
83
|
-
,
|
96
|
+
: agent_{ std::move(kv_provider) }
|
97
|
+
, io_{ io }
|
84
98
|
, vbucket_id_{ vbucket_id }
|
85
99
|
, node_id_{ node_id }
|
86
100
|
, create_options_{ std::move(create_options) }
|
@@ -91,147 +105,119 @@ class range_scan_stream : public std::enable_shared_from_this<range_scan_stream>
|
|
91
105
|
|
92
106
|
void start()
|
93
107
|
{
|
94
|
-
// Fail the stream if more time
|
108
|
+
// Fail the stream if more time than the timeout has elapsed since the stream was first attempted (if this is a retry)
|
95
109
|
if (first_attempt_timestamp_.has_value()) {
|
96
110
|
if (std::chrono::steady_clock::now() - first_attempt_timestamp_.value() > create_options_.timeout) {
|
97
|
-
CB_LOG_DEBUG("stream for vbucket_id {} cannot be retried
|
111
|
+
CB_LOG_DEBUG("stream for vbucket_id {} cannot be retried because it has exceeded the timeout", vbucket_id_);
|
98
112
|
state_ = failed{ errc::common::unambiguous_timeout, !is_sampling_scan() };
|
99
|
-
stream_manager_->
|
100
|
-
drain_waiting_queue();
|
113
|
+
stream_manager_->stream_failed(node_id_, vbucket_id_, errc::common::unambiguous_timeout, error_is_fatal());
|
101
114
|
return;
|
102
115
|
}
|
103
116
|
} else {
|
104
117
|
first_attempt_timestamp_ = std::chrono::steady_clock::now();
|
105
118
|
}
|
106
119
|
|
107
|
-
CB_LOG_TRACE("starting stream {} in node {}", vbucket_id_, node_id_);
|
108
|
-
|
120
|
+
CB_LOG_TRACE("starting stream for vbucket {} in node {}", vbucket_id_, node_id_);
|
121
|
+
|
109
122
|
if (std::holds_alternative<range_scan>(create_options_.scan_type) && !last_seen_key_.empty()) {
|
110
123
|
std::get<range_scan>(create_options_.scan_type).from = scan_term{ last_seen_key_ };
|
111
124
|
}
|
112
125
|
|
113
|
-
|
126
|
+
agent_.range_scan_create(vbucket_id_, create_options_, [self = shared_from_this()](auto res, auto ec) {
|
114
127
|
if (ec) {
|
115
128
|
if (ec == errc::key_value::document_not_found) {
|
116
129
|
// Benign error
|
117
|
-
|
118
|
-
CB_LOG_TRACE("setting state for stream {} to FAILED", self->vbucket_id_);
|
130
|
+
CB_LOG_TRACE("ignoring vbucket_id {} because no documents exist for it", self->vbucket_id_);
|
119
131
|
self->state_ = failed{ ec, false };
|
120
|
-
self->stream_manager_->
|
132
|
+
self->stream_manager_->stream_failed(self->node_id_, self->vbucket_id_, ec, self->error_is_fatal());
|
121
133
|
} else if (ec == errc::common::temporary_failure) {
|
122
|
-
// Retryable error
|
123
|
-
CB_LOG_DEBUG("received busy status from vbucket with ID {} - reducing concurrency &
|
124
|
-
|
125
|
-
self->state_ =
|
134
|
+
// Retryable error - server is overwhelmed, retry after reducing concurrency
|
135
|
+
CB_LOG_DEBUG("received busy status during scan from vbucket with ID {} - reducing concurrency & retrying",
|
136
|
+
self->vbucket_id_);
|
137
|
+
self->state_ = std::monostate{};
|
126
138
|
self->stream_manager_->stream_start_failed_awaiting_retry(self->node_id_, self->vbucket_id_);
|
127
139
|
} else if (ec == errc::common::internal_server_failure || ec == errc::common::collection_not_found) {
|
128
140
|
// Fatal errors
|
129
|
-
CB_LOG_TRACE("setting state for stream {} to FAILED", self->vbucket_id_);
|
130
141
|
self->state_ = failed{ ec, true };
|
131
|
-
self->stream_manager_->
|
142
|
+
self->stream_manager_->stream_failed(self->node_id_, self->vbucket_id_, ec, self->error_is_fatal());
|
132
143
|
} else {
|
133
144
|
// Unexpected errors
|
134
|
-
CB_LOG_DEBUG(
|
135
|
-
|
136
|
-
|
145
|
+
CB_LOG_DEBUG("received unexpected error {} from stream for vbucket {} during range scan continue ({})",
|
146
|
+
ec.value(),
|
147
|
+
self->vbucket_id_,
|
148
|
+
ec.message());
|
137
149
|
self->state_ = failed{ ec, true };
|
138
|
-
self->stream_manager_->
|
150
|
+
self->stream_manager_->stream_failed(self->node_id_, self->vbucket_id_, ec, self->error_is_fatal());
|
139
151
|
}
|
140
|
-
self->drain_waiting_queue();
|
141
152
|
return;
|
142
153
|
}
|
154
|
+
|
143
155
|
self->state_ = running{ std::move(res.scan_uuid) };
|
144
|
-
|
145
|
-
self->
|
146
|
-
self->resume();
|
156
|
+
|
157
|
+
return self->resume();
|
147
158
|
});
|
148
159
|
}
|
149
160
|
|
150
|
-
void
|
161
|
+
void should_cancel()
|
151
162
|
{
|
152
|
-
|
153
|
-
if (is_running()) {
|
154
|
-
agent_.range_scan_cancel(uuid(), vbucket_id_, {}, [](auto /* res */, auto /* ec */) {});
|
155
|
-
}
|
156
|
-
|
157
|
-
items_.cancel();
|
158
|
-
items_.close();
|
159
|
-
|
160
|
-
bool fatal{};
|
161
|
-
if (ec == errc::key_value::document_not_found || ec == errc::common::authentication_failure ||
|
162
|
-
ec == errc::common::collection_not_found || ec == errc::common::request_canceled) {
|
163
|
-
// Errors that are fatal unless this is a sampling scan
|
164
|
-
fatal = !is_sampling_scan();
|
165
|
-
} else if (ec == errc::common::feature_not_available || ec == errc::common::invalid_argument ||
|
166
|
-
ec == errc::common::temporary_failure) {
|
167
|
-
// Errors that are always fatal
|
168
|
-
fatal = true;
|
169
|
-
} else {
|
170
|
-
// Unexpected error - always fatal
|
171
|
-
CB_LOG_DEBUG("received unexpected error {} from stream for vbucket during range scan continue {} ({})",
|
172
|
-
ec.value(),
|
173
|
-
vbucket_id_,
|
174
|
-
ec.message());
|
175
|
-
fatal = true;
|
176
|
-
}
|
177
|
-
|
178
|
-
CB_LOG_TRACE("setting state for stream {} to FAILED after range scan continue", vbucket_id_);
|
179
|
-
state_ = failed{ ec, fatal };
|
180
|
-
stream_manager_->stream_continue_failed(node_id_, fatal);
|
181
|
-
}
|
163
|
+
should_cancel_ = true;
|
182
164
|
}
|
183
165
|
|
184
|
-
|
166
|
+
[[nodiscard]] auto node_id() const -> std::int16_t
|
185
167
|
{
|
186
|
-
|
168
|
+
return node_id_;
|
187
169
|
}
|
188
170
|
|
189
|
-
|
171
|
+
private:
|
172
|
+
void fail(std::error_code ec)
|
190
173
|
{
|
191
|
-
if (
|
192
|
-
|
193
|
-
|
194
|
-
stream_manager_->stream_completed(node_id_);
|
195
|
-
state_ = completed{};
|
196
|
-
drain_waiting_queue();
|
174
|
+
if (is_failed()) {
|
175
|
+
return;
|
197
176
|
}
|
198
|
-
}
|
199
177
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
178
|
+
bool fatal;
|
179
|
+
if (ec == errc::key_value::document_not_found || ec == errc::common::authentication_failure ||
|
180
|
+
ec == errc::common::collection_not_found || ec == errc::common::request_canceled) {
|
181
|
+
// Errors that are fatal unless this is a sampling scan
|
182
|
+
fatal = !is_sampling_scan();
|
183
|
+
} else if (ec == errc::common::feature_not_available || ec == errc::common::invalid_argument ||
|
184
|
+
ec == errc::common::temporary_failure) {
|
185
|
+
// Errors that are always fatal
|
186
|
+
fatal = true;
|
187
|
+
} else {
|
188
|
+
// Unexpected error - always fatal
|
189
|
+
CB_LOG_DEBUG("received unexpected error {} from stream for vbucket {} during range scan continue ({})",
|
190
|
+
ec.value(),
|
191
|
+
vbucket_id_,
|
192
|
+
ec.message());
|
193
|
+
fatal = true;
|
206
194
|
}
|
207
|
-
}
|
208
195
|
|
209
|
-
|
210
|
-
|
211
|
-
{
|
212
|
-
do_when_ready([self = shared_from_this(), handler = std::forward<Handler>(handler)]() mutable {
|
213
|
-
self->take_when_ready(std::forward<Handler>(handler));
|
214
|
-
});
|
196
|
+
state_ = failed{ ec, fatal };
|
197
|
+
stream_manager_->stream_failed(node_id_, vbucket_id_, ec, fatal);
|
215
198
|
}
|
216
199
|
|
217
|
-
|
200
|
+
void complete()
|
218
201
|
{
|
219
|
-
|
220
|
-
|
202
|
+
if (is_failed() || is_completed()) {
|
203
|
+
return;
|
204
|
+
}
|
221
205
|
|
222
|
-
|
223
|
-
|
224
|
-
return !std::holds_alternative<std::monostate>(state_);
|
206
|
+
stream_manager_->stream_completed(node_id_, vbucket_id_);
|
207
|
+
state_ = completed{};
|
225
208
|
}
|
226
209
|
|
227
|
-
|
210
|
+
void cancel()
|
228
211
|
{
|
229
|
-
|
230
|
-
|
212
|
+
auto scan_uuid = uuid();
|
213
|
+
if (scan_uuid.empty()) {
|
214
|
+
// The stream is not currently running
|
215
|
+
return;
|
216
|
+
}
|
231
217
|
|
232
|
-
|
233
|
-
|
234
|
-
|
218
|
+
asio::post(asio::bind_executor(io_, [self = shared_from_this(), scan_uuid]() mutable {
|
219
|
+
self->agent_.range_scan_cancel(scan_uuid, self->vbucket_id_, {}, [](auto /* res */, auto /* ec */) {});
|
220
|
+
}));
|
235
221
|
}
|
236
222
|
|
237
223
|
[[nodiscard]] auto is_running() const -> bool
|
@@ -249,94 +235,50 @@ class range_scan_stream : public std::enable_shared_from_this<range_scan_stream>
|
|
249
235
|
return std::holds_alternative<completed>(state_);
|
250
236
|
}
|
251
237
|
|
252
|
-
private:
|
253
|
-
template<typename Handler>
|
254
|
-
void take_when_ready(Handler&& handler)
|
255
|
-
{
|
256
|
-
|
257
|
-
if (is_failed()) {
|
258
|
-
if (error_is_fatal()) {
|
259
|
-
return handler(std::optional<range_scan_item>{}, false, std::optional<std::error_code>{ error() });
|
260
|
-
} else {
|
261
|
-
return handler(std::optional<range_scan_item>{}, false, std::optional<std::error_code>{});
|
262
|
-
}
|
263
|
-
}
|
264
|
-
if (is_awaiting_retry() || is_not_started()) {
|
265
|
-
return handler(std::optional<range_scan_item>{}, true, std::optional<std::error_code>{});
|
266
|
-
}
|
267
|
-
if (!items_.ready()) {
|
268
|
-
return handler(std::optional<range_scan_item>{}, is_running(), std::optional<std::error_code>{});
|
269
|
-
}
|
270
|
-
items_.async_receive(
|
271
|
-
[self = shared_from_this(), handler = std::forward<Handler>(handler)](std::error_code ec, range_scan_item item) mutable {
|
272
|
-
if (ec) {
|
273
|
-
return handler(std::optional<range_scan_item>{}, false, std::optional<std::error_code>{});
|
274
|
-
}
|
275
|
-
handler(std::optional<range_scan_item>{ std::move(item) }, true, std::optional<std::error_code>{});
|
276
|
-
});
|
277
|
-
}
|
278
|
-
|
279
|
-
template<typename Handler>
|
280
|
-
void do_when_ready(Handler&& handler)
|
281
|
-
{
|
282
|
-
if (is_ready()) {
|
283
|
-
drain_waiting_queue();
|
284
|
-
return handler();
|
285
|
-
}
|
286
|
-
waiting_queue_.emplace_back(std::forward<Handler>(handler));
|
287
|
-
}
|
288
|
-
|
289
|
-
void drain_waiting_queue()
|
290
|
-
{
|
291
|
-
auto queue = std::move(waiting_queue_);
|
292
|
-
for (auto const& waiter : queue) {
|
293
|
-
waiter();
|
294
|
-
}
|
295
|
-
}
|
296
|
-
|
297
238
|
void resume()
|
298
239
|
{
|
299
240
|
if (!is_running()) {
|
300
241
|
return;
|
301
242
|
}
|
302
243
|
if (should_cancel_) {
|
303
|
-
|
304
|
-
items_.close();
|
305
|
-
items_.cancel();
|
244
|
+
cancel();
|
306
245
|
return;
|
307
246
|
}
|
308
247
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
self
|
315
|
-
|
248
|
+
asio::post(asio::bind_executor(io_, [self = shared_from_this()]() mutable {
|
249
|
+
self->agent_.range_scan_continue(
|
250
|
+
self->uuid(),
|
251
|
+
self->vbucket_id_,
|
252
|
+
self->continue_options_,
|
253
|
+
[self](auto item) {
|
254
|
+
// The scan has already been cancelled, no need to send items
|
255
|
+
if (self->should_cancel_) {
|
256
|
+
return;
|
257
|
+
}
|
258
|
+
self->last_seen_key_ = item.key;
|
259
|
+
self->stream_manager_->stream_received_item(std::move(item));
|
260
|
+
},
|
261
|
+
[self](auto res, auto ec) {
|
316
262
|
if (ec) {
|
317
|
-
self->fail(ec);
|
263
|
+
return self->fail(ec);
|
264
|
+
}
|
265
|
+
if (res.complete) {
|
266
|
+
return self->complete();
|
267
|
+
}
|
268
|
+
if (res.more) {
|
269
|
+
return self->resume();
|
318
270
|
}
|
319
271
|
});
|
320
|
-
|
321
|
-
[self = shared_from_this()](auto res, auto ec) {
|
322
|
-
if (ec) {
|
323
|
-
return self->fail(ec);
|
324
|
-
}
|
325
|
-
if (res.complete) {
|
326
|
-
return self->complete();
|
327
|
-
}
|
328
|
-
if (res.more) {
|
329
|
-
return self->resume();
|
330
|
-
}
|
331
|
-
});
|
272
|
+
}));
|
332
273
|
}
|
333
274
|
|
334
275
|
[[nodiscard]] auto uuid() const -> std::vector<std::byte>
|
335
276
|
{
|
336
|
-
|
277
|
+
try {
|
337
278
|
return std::get<running>(state_).uuid;
|
279
|
+
} catch (std::bad_variant_access&) {
|
280
|
+
return {};
|
338
281
|
}
|
339
|
-
return {};
|
340
282
|
}
|
341
283
|
|
342
284
|
[[nodiscard]] auto error() const -> std::error_code
|
@@ -360,18 +302,17 @@ class range_scan_stream : public std::enable_shared_from_this<range_scan_stream>
|
|
360
302
|
return std::holds_alternative<sampling_scan>(create_options_.scan_type);
|
361
303
|
}
|
362
304
|
|
363
|
-
asio::experimental::concurrent_channel<void(std::error_code, range_scan_item)> items_;
|
364
305
|
agent agent_;
|
306
|
+
asio::io_context& io_;
|
365
307
|
std::uint16_t vbucket_id_;
|
366
308
|
std::int16_t node_id_;
|
367
309
|
range_scan_create_options create_options_;
|
368
310
|
range_scan_continue_options continue_options_;
|
369
311
|
std::shared_ptr<scan_stream_manager> stream_manager_;
|
370
312
|
std::string last_seen_key_{};
|
371
|
-
std::variant<std::monostate,
|
313
|
+
std::variant<std::monostate, failed, running, completed> state_{};
|
372
314
|
bool should_cancel_{ false };
|
373
315
|
std::optional<std::chrono::time_point<std::chrono::steady_clock>> first_attempt_timestamp_{};
|
374
|
-
std::vector<utils::movable_function<void()>> waiting_queue_{};
|
375
316
|
};
|
376
317
|
|
377
318
|
class range_scan_orchestrator_impl
|
@@ -392,6 +333,8 @@ class range_scan_orchestrator_impl
|
|
392
333
|
, vbucket_map_{ std::move(vbucket_map) }
|
393
334
|
, scope_name_{ std::move(scope_name) }
|
394
335
|
, collection_name_{ std::move(collection_name) }
|
336
|
+
, load_balancer_{ vbucket_map_ }
|
337
|
+
, items_{ io, 1024 }
|
395
338
|
, scan_type_{ std::move(scan_type) }
|
396
339
|
, options_{ std::move(options) }
|
397
340
|
, vbucket_to_snapshot_requirements_{ mutation_state_to_snapshot_requirements(options_.consistent_with) }
|
@@ -399,16 +342,22 @@ class range_scan_orchestrator_impl
|
|
399
342
|
{
|
400
343
|
|
401
344
|
if (std::holds_alternative<sampling_scan>(scan_type_)) {
|
402
|
-
|
345
|
+
auto s = std::get<sampling_scan>(scan_type);
|
346
|
+
item_limit_ = s.limit;
|
347
|
+
|
348
|
+
// Set the seed of the load balancer to ensure that if the sampling scan is run multiple times the vbuckets
|
349
|
+
// are scanned in the same order when concurrency is 1. This guarantees that the items returned will be the
|
350
|
+
// same. We cannot guarantee this when concurrency is greater than 1, as the order of the vbucket scans
|
351
|
+
// depends on how long each scan takes and what the load on a node is at any given time.
|
352
|
+
if (s.seed.has_value()) {
|
353
|
+
load_balancer_.seed(s.seed.value());
|
354
|
+
}
|
403
355
|
}
|
404
356
|
}
|
405
357
|
|
406
358
|
auto scan() -> tl::expected<scan_result, std::error_code>
|
407
359
|
{
|
408
|
-
if (item_limit_ == 0) {
|
409
|
-
return tl::unexpected(errc::common::invalid_argument);
|
410
|
-
}
|
411
|
-
if (concurrency_ <= 0) {
|
360
|
+
if (item_limit_ == 0 || concurrency_ <= 0) {
|
412
361
|
return tl::unexpected(errc::common::invalid_argument);
|
413
362
|
}
|
414
363
|
|
@@ -434,6 +383,7 @@ class range_scan_orchestrator_impl
|
|
434
383
|
range_scan_continue_options const continue_options{
|
435
384
|
options_.batch_item_limit, options_.batch_byte_limit, batch_time_limit, options_.timeout, options_.retry_strategy,
|
436
385
|
};
|
386
|
+
|
437
387
|
for (std::uint16_t vbucket = 0; vbucket < gsl::narrow_cast<std::uint16_t>(vbucket_map_.size()); ++vbucket) {
|
438
388
|
const range_scan_create_options create_options{
|
439
389
|
scope_name_, {},
|
@@ -453,10 +403,6 @@ class range_scan_orchestrator_impl
|
|
453
403
|
continue_options,
|
454
404
|
std::static_pointer_cast<scan_stream_manager>(shared_from_this()));
|
455
405
|
streams_[vbucket] = stream;
|
456
|
-
streams_[vbucket]->mark_not_started();
|
457
|
-
if (stream_count_per_node_.count(node_id) == 0) {
|
458
|
-
stream_count_per_node_[node_id] = 0;
|
459
|
-
}
|
460
406
|
}
|
461
407
|
start_streams(concurrency_);
|
462
408
|
|
@@ -467,7 +413,7 @@ class range_scan_orchestrator_impl
|
|
467
413
|
{
|
468
414
|
cancelled_ = true;
|
469
415
|
for (const auto& [vbucket_id, stream] : streams_) {
|
470
|
-
stream->
|
416
|
+
stream->should_cancel();
|
471
417
|
}
|
472
418
|
}
|
473
419
|
|
@@ -479,212 +425,154 @@ class range_scan_orchestrator_impl
|
|
479
425
|
auto next() -> std::future<tl::expected<range_scan_item, std::error_code>> override
|
480
426
|
{
|
481
427
|
auto barrier = std::make_shared<std::promise<tl::expected<range_scan_item, std::error_code>>>();
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
} else if (ec) {
|
490
|
-
barrier->set_value(tl::unexpected{ ec.value() });
|
491
|
-
} else {
|
492
|
-
barrier->set_value(tl::unexpected{ errc::key_value::range_scan_completed });
|
493
|
-
}
|
494
|
-
});
|
495
|
-
}
|
428
|
+
next([barrier](range_scan_item item, std::error_code ec) mutable {
|
429
|
+
if (ec) {
|
430
|
+
barrier->set_value(tl::unexpected{ ec });
|
431
|
+
} else {
|
432
|
+
barrier->set_value(std::move(item));
|
433
|
+
}
|
434
|
+
});
|
496
435
|
return barrier->get_future();
|
497
436
|
}
|
498
437
|
|
499
438
|
void next(utils::movable_function<void(range_scan_item, std::error_code)> callback) override
|
500
439
|
{
|
501
|
-
auto handler = [callback = std::move(callback)](std::optional<range_scan_item> item, std::optional<std::error_code> ec) mutable {
|
502
|
-
if (item) {
|
503
|
-
callback(std::move(item.value()), {});
|
504
|
-
} else if (ec) {
|
505
|
-
callback({}, ec.value());
|
506
|
-
} else {
|
507
|
-
callback({}, errc::key_value::range_scan_completed);
|
508
|
-
}
|
509
|
-
};
|
510
440
|
if (item_limit_ == 0 || item_limit_-- == 0) {
|
511
|
-
|
441
|
+
callback({}, errc::key_value::range_scan_completed);
|
512
442
|
cancel();
|
513
443
|
} else {
|
514
|
-
next_item(
|
444
|
+
next_item(std::move(callback));
|
515
445
|
}
|
516
446
|
}
|
517
447
|
|
518
|
-
|
448
|
+
template<typename Handler>
|
449
|
+
void next_item(Handler&& handler)
|
519
450
|
{
|
520
|
-
|
451
|
+
if (streams_.empty() || cancelled_) {
|
452
|
+
items_.cancel();
|
453
|
+
items_.close();
|
454
|
+
return handler({}, errc::key_value::range_scan_completed);
|
455
|
+
}
|
456
|
+
items_.async_receive([self = shared_from_this(), handler = std::forward<Handler>(handler)](
|
457
|
+
std::error_code ec, std::variant<range_scan_item, scan_stream_end_signal> it) mutable {
|
458
|
+
if (ec) {
|
459
|
+
return handler({}, ec);
|
460
|
+
}
|
521
461
|
|
462
|
+
if (std::holds_alternative<range_scan_item>(it)) {
|
463
|
+
handler(std::get<range_scan_item>(it), {});
|
464
|
+
} else {
|
465
|
+
auto signal = std::get<scan_stream_end_signal>(it);
|
466
|
+
if (signal.error.has_value()) {
|
467
|
+
// Fatal error
|
468
|
+
handler({}, signal.error.value());
|
469
|
+
} else {
|
470
|
+
// Empty signal means that stream has completed
|
471
|
+
{
|
472
|
+
std::lock_guard<std::mutex> const lock{ self->stream_map_mutex_ };
|
473
|
+
self->streams_.erase(signal.vbucket_id);
|
474
|
+
}
|
475
|
+
return asio::post(asio::bind_executor(self->io_, [self, handler = std::forward<Handler>(handler)]() mutable {
|
476
|
+
self->next_item(std::forward<Handler>(handler));
|
477
|
+
}));
|
478
|
+
}
|
479
|
+
}
|
480
|
+
});
|
481
|
+
}
|
482
|
+
|
483
|
+
void start_streams(std::uint16_t stream_count)
|
484
|
+
{
|
522
485
|
if (cancelled_) {
|
523
486
|
CB_LOG_TRACE("scan has been cancelled, do not start another stream");
|
524
487
|
return;
|
525
488
|
}
|
526
489
|
|
527
|
-
|
528
|
-
CB_LOG_TRACE("no more vbuckets to scan");
|
529
|
-
return;
|
530
|
-
}
|
531
|
-
|
532
|
-
std::uint16_t counter = 0;
|
490
|
+
std::uint16_t counter{ 0 };
|
533
491
|
while (counter < stream_count) {
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
// Pick a random node
|
540
|
-
std::random_device rd;
|
541
|
-
std::mt19937_64 gen(rd());
|
542
|
-
std::uniform_int_distribution<std::size_t> dis(0, stream_count_per_node_.size() - 1);
|
543
|
-
auto it = stream_count_per_node_.begin();
|
544
|
-
std::advance(it, static_cast<decltype(stream_count_per_node_)::difference_type>(dis(gen)));
|
545
|
-
least_busy_node = it->first;
|
546
|
-
|
547
|
-
// If any other node has fewer streams running use that
|
548
|
-
for (const auto& [node_id, count] : stream_count_per_node_) {
|
549
|
-
if (count < stream_count_per_node_[least_busy_node]) {
|
550
|
-
least_busy_node = node_id;
|
551
|
-
}
|
552
|
-
}
|
492
|
+
auto vbucket_id = load_balancer_.select_vbucket();
|
493
|
+
if (!vbucket_id.has_value()) {
|
494
|
+
CB_LOG_TRACE("no more scans, all vbuckets have been scanned");
|
495
|
+
return;
|
553
496
|
}
|
554
497
|
|
498
|
+
auto v = vbucket_id.value();
|
555
499
|
std::shared_ptr<range_scan_stream> stream{};
|
556
500
|
{
|
557
|
-
std::lock_guard<std::mutex> const
|
558
|
-
|
559
|
-
for (const auto& [v, s] : streams_) {
|
560
|
-
if ((s->is_not_started() || s->is_awaiting_retry()) && (s->node_id() == least_busy_node)) {
|
561
|
-
CB_LOG_TRACE("selected vbucket {} to scan", v);
|
562
|
-
stream = s;
|
563
|
-
break;
|
564
|
-
}
|
565
|
-
}
|
566
|
-
}
|
567
|
-
|
568
|
-
if (stream == nullptr) {
|
569
|
-
CB_LOG_TRACE("no vbuckets to scan for node {}", least_busy_node);
|
570
|
-
{
|
571
|
-
std::lock_guard<std::mutex> const stream_count_lock(stream_count_per_node_mutex_);
|
572
|
-
stream_count_per_node_.erase(least_busy_node);
|
573
|
-
}
|
574
|
-
return start_streams(static_cast<std::uint16_t>(stream_count - counter));
|
501
|
+
std::lock_guard<std::mutex> const lock{ stream_map_mutex_ };
|
502
|
+
stream = streams_.at(v);
|
575
503
|
}
|
576
|
-
|
577
|
-
auto node_id = stream->node_id();
|
504
|
+
CB_LOG_TRACE("scanning vbucket {} at node {}", vbucket_id.value(), stream->node_id());
|
578
505
|
active_stream_count_++;
|
579
|
-
stream_count_per_node_[node_id]++;
|
580
|
-
stream->start();
|
581
506
|
counter++;
|
507
|
+
asio::post(asio::bind_executor(io_, [stream]() mutable { stream->start(); }));
|
582
508
|
}
|
583
509
|
}
|
584
510
|
|
585
|
-
void
|
586
|
-
{
|
587
|
-
stream_no_longer_running(node_id);
|
588
|
-
if (fatal) {
|
589
|
-
cancel();
|
590
|
-
} else {
|
591
|
-
start_streams(1);
|
592
|
-
}
|
593
|
-
}
|
594
|
-
|
595
|
-
void stream_start_failed_awaiting_retry(std::int16_t node_id, std::uint16_t /* vbucket_id */) override
|
511
|
+
void stream_received_item(range_scan_item item) override
|
596
512
|
{
|
597
|
-
{
|
598
|
-
|
599
|
-
|
600
|
-
stream_count_per_node_[node_id] = 1;
|
513
|
+
items_.async_send({}, std::move(item), [](std::error_code ec) {
|
514
|
+
if (ec && ec != asio::experimental::error::channel_closed && ec != asio::experimental::error::channel_cancelled) {
|
515
|
+
CB_LOG_WARNING("unexpected error while sending to scan item channel: {} ({})", ec.value(), ec.message());
|
601
516
|
}
|
602
|
-
}
|
603
|
-
stream_no_longer_running(node_id);
|
604
|
-
if (active_stream_count_ == 0) {
|
605
|
-
start_streams(1);
|
606
|
-
}
|
517
|
+
});
|
607
518
|
}
|
608
519
|
|
609
|
-
void
|
520
|
+
void stream_failed(std::int16_t node_id, std::uint16_t vbucket_id, std::error_code ec, bool fatal) override
|
610
521
|
{
|
611
|
-
|
612
|
-
|
613
|
-
cancel();
|
614
|
-
} else {
|
615
|
-
start_streams(1);
|
522
|
+
if (!fatal) {
|
523
|
+
return stream_completed(node_id, vbucket_id);
|
616
524
|
}
|
617
|
-
}
|
618
525
|
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
526
|
+
load_balancer_.notify_stream_ended(node_id);
|
527
|
+
active_stream_count_--;
|
528
|
+
items_.async_send({}, scan_stream_end_signal{ vbucket_id, ec }, [](std::error_code ec) {
|
529
|
+
if (ec && ec != asio::experimental::error::channel_closed && ec != asio::experimental::error::channel_cancelled) {
|
530
|
+
CB_LOG_WARNING("unexpected error while sending to scan item channel: {} ({})", ec.value(), ec.message());
|
531
|
+
}
|
532
|
+
});
|
533
|
+
return cancel();
|
623
534
|
}
|
624
535
|
|
625
|
-
|
626
|
-
void stream_no_longer_running(std::int16_t node_id)
|
536
|
+
void stream_completed(std::int16_t node_id, std::uint16_t vbucket_id) override
|
627
537
|
{
|
628
|
-
|
629
|
-
std::lock_guard<std::mutex> const stream_count_lock(stream_count_per_node_mutex_);
|
630
|
-
if (stream_count_per_node_.count(node_id) > 0) {
|
631
|
-
stream_count_per_node_[node_id]--;
|
632
|
-
}
|
633
|
-
}
|
538
|
+
load_balancer_.notify_stream_ended(node_id);
|
634
539
|
active_stream_count_--;
|
540
|
+
items_.async_send({}, scan_stream_end_signal{ vbucket_id }, [](std::error_code ec) {
|
541
|
+
if (ec && ec != asio::experimental::error::channel_closed && ec != asio::experimental::error::channel_cancelled) {
|
542
|
+
CB_LOG_WARNING("unexpected error while sending to scan item channel: {} ({})", ec.value(), ec.message());
|
543
|
+
}
|
544
|
+
});
|
545
|
+
return start_streams(1);
|
635
546
|
}
|
636
547
|
|
637
|
-
|
638
|
-
void next_item(Iterator it, Handler&& handler)
|
548
|
+
void stream_start_failed_awaiting_retry(std::int16_t node_id, std::uint16_t vbucket_id) override
|
639
549
|
{
|
640
|
-
|
641
|
-
|
550
|
+
load_balancer_.notify_stream_ended(node_id);
|
551
|
+
active_stream_count_--;
|
552
|
+
|
553
|
+
load_balancer_.enqueue_vbucket(node_id, vbucket_id);
|
554
|
+
if (active_stream_count_ == 0) {
|
555
|
+
return start_streams(1);
|
642
556
|
}
|
643
|
-
auto vbucket_id = it->first;
|
644
|
-
auto stream = it->second;
|
645
|
-
stream->take([it = std::next(it), vbucket_id, self = shared_from_this(), handler = std::forward<Handler>(handler)](
|
646
|
-
auto item, bool has_more, auto ec) mutable {
|
647
|
-
if (ec) {
|
648
|
-
// Fatal error
|
649
|
-
self->streams_.clear();
|
650
|
-
return handler({}, ec);
|
651
|
-
}
|
652
|
-
if (!has_more) {
|
653
|
-
std::lock_guard<std::mutex> const lock(self->stream_map_mutex_);
|
654
|
-
self->streams_.erase(vbucket_id);
|
655
|
-
}
|
656
|
-
if (item) {
|
657
|
-
return handler(std::move(item), {});
|
658
|
-
}
|
659
|
-
if (self->streams_.empty()) {
|
660
|
-
return handler({}, {});
|
661
|
-
}
|
662
|
-
if (it == self->streams_.end()) {
|
663
|
-
it = self->streams_.begin();
|
664
|
-
}
|
665
|
-
return asio::post(asio::bind_executor(self->io_, [it, self, handler = std::forward<Handler>(handler)]() mutable {
|
666
|
-
self->next_item(it, std::forward<Handler>(handler));
|
667
|
-
}));
|
668
|
-
});
|
669
557
|
}
|
670
558
|
|
559
|
+
private:
|
671
560
|
asio::io_context& io_;
|
672
561
|
agent agent_;
|
673
562
|
topology::configuration::vbucket_map vbucket_map_;
|
674
563
|
std::string scope_name_;
|
675
564
|
std::string collection_name_;
|
565
|
+
range_scan_load_balancer load_balancer_;
|
566
|
+
asio::experimental::concurrent_channel<void(std::error_code, std::variant<range_scan_item, scan_stream_end_signal>)> items_;
|
676
567
|
std::uint32_t collection_id_{ 0 };
|
677
568
|
std::variant<std::monostate, range_scan, prefix_scan, sampling_scan> scan_type_;
|
678
569
|
range_scan_orchestrator_options options_;
|
679
570
|
std::map<std::size_t, std::optional<range_snapshot_requirements>> vbucket_to_snapshot_requirements_;
|
680
571
|
std::map<std::uint16_t, std::shared_ptr<range_scan_stream>> streams_{};
|
681
|
-
std::map<std::int16_t, std::atomic_uint16_t> stream_count_per_node_{};
|
682
|
-
std::recursive_mutex stream_start_mutex_{};
|
683
572
|
std::mutex stream_map_mutex_{};
|
684
|
-
std::
|
685
|
-
std::
|
686
|
-
std::
|
687
|
-
std::size_t item_limit_{ std::numeric_limits<size_t>::max() };
|
573
|
+
std::atomic_uint16_t active_stream_count_{ 0 };
|
574
|
+
std::uint16_t concurrency_{ 1 };
|
575
|
+
std::size_t item_limit_{ std::numeric_limits<std::size_t>::max() };
|
688
576
|
bool cancelled_{ false };
|
689
577
|
};
|
690
578
|
|