Skip to content

Commit 54b1ca6

Browse files
committed
kv: add bounded snapshot scan API
Signed-off-by: lance6716 <lance6716@gmail.com>
1 parent 48009c6 commit 54b1ca6

3 files changed

Lines changed: 298 additions & 0 deletions

File tree

include/pingcap/kv/Snapshot.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,31 @@
33
#include <pingcap/kv/Cluster.h>
44
#include <pingcap/kv/RegionClient.h>
55

6+
#include <vector>
7+
68
namespace pingcap
79
{
810
namespace kv
911
{
1012
struct Scanner;
1113

14+
struct ScanOptions
15+
{
16+
std::string start_key;
17+
std::string end_key;
18+
uint32_t limit = 0;
19+
bool reverse = false;
20+
bool key_only = false;
21+
uint64_t version = 0;
22+
};
23+
24+
struct ScanResult
25+
{
26+
std::vector<kvrpcpb::KvPair> pairs;
27+
bool has_more = false;
28+
std::string next_start_key;
29+
};
30+
1231
struct Snapshot
1332
{
1433
Cluster * cluster;
@@ -31,6 +50,9 @@ struct Snapshot
3150

3251
kvrpcpb::MvccInfo mvccGet(Backoffer & bo, const std::string & key);
3352

53+
ScanResult ScanOnce(const ScanOptions & options);
54+
ScanResult ScanOnce(Backoffer & bo, const ScanOptions & options);
55+
3456
Scanner Scan(const std::string & begin, const std::string & end);
3557
};
3658

src/kv/Snapshot.cc

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,67 @@
44
#include <pingcap/kv/Scanner.h>
55
#include <pingcap/kv/Snapshot.h>
66

7+
#include <algorithm>
8+
#include <limits>
9+
710
namespace pingcap
811
{
912
namespace kv
1013
{
1114
constexpr int scan_batch_size = 256;
1215

16+
namespace
17+
{
18+
19+
uint32_t requestLimit(uint32_t remaining)
20+
{
21+
if (remaining == std::numeric_limits<uint32_t>::max())
22+
return remaining;
23+
return remaining + 1;
24+
}
25+
26+
std::string keyBefore(const std::string & upper_bound)
27+
{
28+
// Used to locate the region containing keys immediately below a reverse scan upper bound.
29+
if (upper_bound.empty())
30+
return std::string(1, static_cast<char>(0xff));
31+
32+
std::string key = upper_bound;
33+
auto last = static_cast<unsigned char>(key.back());
34+
if (last == 0)
35+
{
36+
key.pop_back();
37+
return key;
38+
}
39+
40+
key.back() = static_cast<char>(last - 1);
41+
key.push_back(static_cast<char>(0xff));
42+
return key;
43+
}
44+
45+
bool forwardRangeFinished(const std::string & cursor, const std::string & end_key)
46+
{
47+
return cursor.empty() || (!end_key.empty() && cursor >= end_key);
48+
}
49+
50+
bool reverseRangeFinished(const std::string & cursor, const std::string & end_key)
51+
{
52+
return cursor.empty() || (!end_key.empty() && cursor <= end_key);
53+
}
54+
55+
void setScanContext(kvrpcpb::ScanRequest & request, const MinCommitTSPushed & min_commit_ts_pushed)
56+
{
57+
auto * context = request.mutable_context();
58+
context->set_priority(::kvrpcpb::Normal);
59+
context->set_not_fill_cache(false);
60+
for (auto ts : min_commit_ts_pushed.getTimestamps())
61+
{
62+
context->add_resolved_locks(ts);
63+
}
64+
}
65+
66+
} // namespace
67+
1368
//bool extractLockFromKeyErr()
1469

1570
kvrpcpb::MvccInfo Snapshot::mvccGet(const std::string & key)
@@ -113,6 +168,164 @@ std::string Snapshot::Get(Backoffer & bo, const std::string & key)
113168
}
114169
}
115170

171+
ScanResult Snapshot::ScanOnce(const ScanOptions & options)
172+
{
173+
Backoffer bo(scanMaxBackoff);
174+
return ScanOnce(bo, options);
175+
}
176+
177+
ScanResult Snapshot::ScanOnce(Backoffer & bo, const ScanOptions & options)
178+
{
179+
ScanResult result;
180+
if (options.limit == 0)
181+
return result;
182+
183+
const uint64_t read_version = options.version == 0 ? version : options.version;
184+
std::string cursor = options.start_key;
185+
186+
if (!options.reverse && !options.end_key.empty() && cursor >= options.end_key)
187+
return result;
188+
if (options.reverse && !cursor.empty() && !options.end_key.empty() && options.end_key >= cursor)
189+
return result;
190+
191+
while (result.pairs.size() < options.limit)
192+
{
193+
auto loc = options.reverse ? cluster->region_cache->locateKey(bo, keyBefore(cursor)) : cluster->region_cache->locateKey(bo, cursor);
194+
195+
kvrpcpb::ScanRequest request;
196+
if (options.reverse)
197+
{
198+
std::string request_end_key = loc.start_key;
199+
if (!options.end_key.empty() && options.end_key > request_end_key)
200+
request_end_key = options.end_key;
201+
202+
std::string request_start_key = cursor;
203+
if (!loc.end_key.empty() && (request_start_key.empty() || request_start_key > loc.end_key))
204+
request_start_key = loc.end_key;
205+
206+
if (!request_start_key.empty() && request_start_key <= request_end_key)
207+
{
208+
cursor = loc.start_key;
209+
if (reverseRangeFinished(cursor, options.end_key))
210+
return result;
211+
continue;
212+
}
213+
214+
request.set_start_key(request_start_key);
215+
request.set_end_key(request_end_key);
216+
request.set_reverse(true);
217+
}
218+
else
219+
{
220+
std::string request_end_key = options.end_key;
221+
if (!request_end_key.empty() && !loc.end_key.empty() && loc.end_key < request_end_key)
222+
request_end_key = loc.end_key;
223+
224+
request.set_start_key(cursor);
225+
request.set_end_key(request_end_key);
226+
}
227+
228+
const auto remaining = static_cast<uint32_t>(options.limit - result.pairs.size());
229+
request.set_limit(requestLimit(remaining));
230+
request.set_version(read_version);
231+
request.set_key_only(options.key_only);
232+
setScanContext(request, min_commit_ts_pushed);
233+
234+
kvrpcpb::ScanResponse response;
235+
RegionClient region_client(cluster, loc.region);
236+
try
237+
{
238+
region_client.sendReqToRegion<RPC_NAME(KvScan)>(bo, request, &response);
239+
}
240+
catch (Exception & e)
241+
{
242+
bo.backoff(boRegionMiss, e);
243+
continue;
244+
}
245+
246+
if (response.has_error())
247+
{
248+
auto lock = extractLockFromKeyErr(response.error());
249+
std::vector<LockPtr> locks{lock};
250+
std::vector<uint64_t> pushed{};
251+
auto ms_before_expired = cluster->lock_resolver->resolveLocks(bo, read_version, locks, pushed);
252+
if (!pushed.empty())
253+
{
254+
min_commit_ts_pushed.addTimestamps(pushed);
255+
}
256+
if (ms_before_expired > 0)
257+
{
258+
bo.backoffWithMaxSleep(
259+
BackoffType::boTxnLockFast,
260+
ms_before_expired,
261+
Exception("key is locked during scanning", ErrorCodes::LockError));
262+
}
263+
continue;
264+
}
265+
266+
std::vector<LockPtr> locks;
267+
for (int i = 0; i < response.pairs_size(); i++)
268+
{
269+
const auto & pair = response.pairs(i);
270+
if (pair.has_error())
271+
locks.push_back(extractLockFromKeyErr(pair.error()));
272+
}
273+
if (!locks.empty())
274+
{
275+
std::vector<uint64_t> pushed{};
276+
auto ms_before_expired = cluster->lock_resolver->resolveLocks(bo, read_version, locks, pushed);
277+
if (!pushed.empty())
278+
{
279+
min_commit_ts_pushed.addTimestamps(pushed);
280+
}
281+
if (ms_before_expired > 0)
282+
{
283+
bo.backoffWithMaxSleep(
284+
BackoffType::boTxnLockFast,
285+
ms_before_expired,
286+
Exception("key is locked during scanning", ErrorCodes::LockError));
287+
}
288+
continue;
289+
}
290+
291+
const auto pairs_size = static_cast<size_t>(response.pairs_size());
292+
const auto append_size = std::min<size_t>(pairs_size, remaining);
293+
for (size_t i = 0; i < append_size; i++)
294+
{
295+
result.pairs.push_back(response.pairs(static_cast<int>(i)));
296+
}
297+
298+
if (pairs_size > remaining)
299+
{
300+
result.has_more = true;
301+
result.next_start_key = options.reverse ? result.pairs.back().key() : alphabeticalNext(result.pairs.back().key());
302+
return result;
303+
}
304+
305+
if (options.reverse)
306+
{
307+
cursor = loc.start_key;
308+
if (reverseRangeFinished(cursor, options.end_key))
309+
return result;
310+
}
311+
else
312+
{
313+
cursor = loc.end_key;
314+
if (forwardRangeFinished(cursor, options.end_key))
315+
return result;
316+
}
317+
318+
if (result.pairs.size() == options.limit)
319+
{
320+
result.has_more = true;
321+
result.next_start_key = cursor;
322+
return result;
323+
}
324+
}
325+
326+
return result;
327+
}
328+
116329
Scanner Snapshot::Scan(const std::string & begin, const std::string & end)
117330
{
118331
return Scanner(*this, begin, end, scan_batch_size);

src/test/region_split_test.cc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ namespace pingcap::tests
1313
using namespace pingcap;
1414
using namespace pingcap::kv;
1515

16+
std::vector<std::string> scanKeys(const ScanResult & result)
17+
{
18+
std::vector<std::string> keys;
19+
for (const auto & pair : result.pairs)
20+
{
21+
keys.push_back(pair.key());
22+
}
23+
return keys;
24+
}
25+
1626
class TestWithMockKVRegionSplit : public testing::Test
1727
{
1828
protected:
@@ -116,4 +126,57 @@ TEST_F(TestWithMockKVRegionSplit, testSplitRegionScan)
116126
ASSERT_EQ(answer, 6);
117127
}
118128

129+
TEST_F(TestWithMockKVRegionSplit, testScanOnce)
130+
{
131+
Txn txn(test_cluster.get());
132+
133+
txn.set("abc", "1");
134+
txn.set("abd", "2");
135+
txn.set("abe", "3");
136+
txn.set("abf", "4");
137+
txn.set("abg", "5");
138+
txn.set("abh", "6");
139+
txn.set("zzz", "7");
140+
txn.commit();
141+
142+
control_cluster->splitRegion("abe");
143+
144+
Snapshot snap(test_cluster.get(), test_cluster->pd_client->getTS());
145+
146+
ScanOptions forward_options;
147+
forward_options.start_key = "ab";
148+
forward_options.end_key = "ac";
149+
forward_options.limit = 3;
150+
151+
auto forward_result = snap.ScanOnce(forward_options);
152+
ASSERT_EQ(scanKeys(forward_result), std::vector<std::string>({"abc", "abd", "abe"}));
153+
ASSERT_TRUE(forward_result.has_more);
154+
155+
forward_options.start_key = forward_result.next_start_key;
156+
forward_result = snap.ScanOnce(forward_options);
157+
ASSERT_EQ(scanKeys(forward_result), std::vector<std::string>({"abf", "abg", "abh"}));
158+
ASSERT_FALSE(forward_result.has_more);
159+
160+
ScanOptions reverse_options;
161+
reverse_options.start_key = "ac";
162+
reverse_options.end_key = "ab";
163+
reverse_options.limit = 10;
164+
reverse_options.reverse = true;
165+
166+
auto reverse_result = snap.ScanOnce(reverse_options);
167+
ASSERT_EQ(scanKeys(reverse_result), std::vector<std::string>({"abh", "abg", "abf", "abe", "abd", "abc"}));
168+
ASSERT_FALSE(reverse_result.has_more);
169+
170+
reverse_options.limit = 2;
171+
reverse_result = snap.ScanOnce(reverse_options);
172+
ASSERT_EQ(scanKeys(reverse_result), std::vector<std::string>({"abh", "abg"}));
173+
ASSERT_TRUE(reverse_result.has_more);
174+
175+
reverse_options.start_key = reverse_result.next_start_key;
176+
reverse_options.limit = 10;
177+
reverse_result = snap.ScanOnce(reverse_options);
178+
ASSERT_EQ(scanKeys(reverse_result), std::vector<std::string>({"abf", "abe", "abd", "abc"}));
179+
ASSERT_FALSE(reverse_result.has_more);
180+
}
181+
119182
} // namespace pingcap::tests

0 commit comments

Comments
 (0)