mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-28 15:33:54 +00:00
340ed4fac7
Summary: It's useful to be able to (optionally) associate key-value pairs with user-provided timestamps. This PR is an early effort towards this goal and continues the work of facebook#4942. A suite of new unit tests exist in DBBasicTestWithTimestampWithParam. Support for timestamp requires the user to provide timestamp as a slice in `ReadOptions` and `WriteOptions`. All timestamps of the same database must share the same length, format, etc. The format of the timestamp is the same throughout the same database, and the user is responsible for providing a comparator function (Comparator) to order the <key, timestamp> tuples. Once created, the format and length of the timestamp cannot change (at least for now). Test plan (on devserver): ``` $COMPILE_WITH_ASAN=1 make -j32 all $./db_basic_test --gtest_filter=Timestamp/DBBasicTestWithTimestampWithParam.PutAndGet/* $make check ``` All tests must pass. We also run the following db_bench tests to verify whether there is regression on Get/Put while timestamp is not enabled. ``` $TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=fillseq,readrandom -num=1000000 $TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=fillrandom -num=1000000 ``` Repeat for 6 times for both versions. Results are as follows: ``` | | readrandom | fillrandom | | master | 16.77 MB/s | 47.05 MB/s | | PR5079 | 16.44 MB/s | 47.03 MB/s | ``` Pull Request resolved: https://github.com/facebook/rocksdb/pull/5079 Differential Revision: D15132946 Pulled By: riversand963 fbshipit-source-id: 833a0d657eac21182f0f206c910a6438154c742c
217 lines
6.4 KiB
C++
217 lines
6.4 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
#include "rocksdb/comparator.h"
|
|
#include <stdint.h>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include "logging/logging.h"
|
|
#include "port/port.h"
|
|
#include "rocksdb/slice.h"
|
|
|
|
namespace rocksdb {
|
|
|
|
namespace {
|
|
class BytewiseComparatorImpl : public Comparator {
|
|
public:
|
|
BytewiseComparatorImpl() { }
|
|
|
|
const char* Name() const override { return "leveldb.BytewiseComparator"; }
|
|
|
|
int Compare(const Slice& a, const Slice& b) const override {
|
|
return a.compare(b);
|
|
}
|
|
|
|
bool Equal(const Slice& a, const Slice& b) const override { return a == b; }
|
|
|
|
void FindShortestSeparator(std::string* start,
|
|
const Slice& limit) const override {
|
|
// Find length of common prefix
|
|
size_t min_length = std::min(start->size(), limit.size());
|
|
size_t diff_index = 0;
|
|
while ((diff_index < min_length) &&
|
|
((*start)[diff_index] == limit[diff_index])) {
|
|
diff_index++;
|
|
}
|
|
|
|
if (diff_index >= min_length) {
|
|
// Do not shorten if one string is a prefix of the other
|
|
} else {
|
|
uint8_t start_byte = static_cast<uint8_t>((*start)[diff_index]);
|
|
uint8_t limit_byte = static_cast<uint8_t>(limit[diff_index]);
|
|
if (start_byte >= limit_byte) {
|
|
// Cannot shorten since limit is smaller than start or start is
|
|
// already the shortest possible.
|
|
return;
|
|
}
|
|
assert(start_byte < limit_byte);
|
|
|
|
if (diff_index < limit.size() - 1 || start_byte + 1 < limit_byte) {
|
|
(*start)[diff_index]++;
|
|
start->resize(diff_index + 1);
|
|
} else {
|
|
// v
|
|
// A A 1 A A A
|
|
// A A 2
|
|
//
|
|
// Incrementing the current byte will make start bigger than limit, we
|
|
// will skip this byte, and find the first non 0xFF byte in start and
|
|
// increment it.
|
|
diff_index++;
|
|
|
|
while (diff_index < start->size()) {
|
|
// Keep moving until we find the first non 0xFF byte to
|
|
// increment it
|
|
if (static_cast<uint8_t>((*start)[diff_index]) <
|
|
static_cast<uint8_t>(0xff)) {
|
|
(*start)[diff_index]++;
|
|
start->resize(diff_index + 1);
|
|
break;
|
|
}
|
|
diff_index++;
|
|
}
|
|
}
|
|
assert(Compare(*start, limit) < 0);
|
|
}
|
|
}
|
|
|
|
void FindShortSuccessor(std::string* key) const override {
|
|
// Find first character that can be incremented
|
|
size_t n = key->size();
|
|
for (size_t i = 0; i < n; i++) {
|
|
const uint8_t byte = (*key)[i];
|
|
if (byte != static_cast<uint8_t>(0xff)) {
|
|
(*key)[i] = byte + 1;
|
|
key->resize(i+1);
|
|
return;
|
|
}
|
|
}
|
|
// *key is a run of 0xffs. Leave it alone.
|
|
}
|
|
|
|
bool IsSameLengthImmediateSuccessor(const Slice& s,
|
|
const Slice& t) const override {
|
|
if (s.size() != t.size() || s.size() == 0) {
|
|
return false;
|
|
}
|
|
size_t diff_ind = s.difference_offset(t);
|
|
// same slice
|
|
if (diff_ind >= s.size()) return false;
|
|
uint8_t byte_s = static_cast<uint8_t>(s[diff_ind]);
|
|
uint8_t byte_t = static_cast<uint8_t>(t[diff_ind]);
|
|
// first different byte must be consecutive, and remaining bytes must be
|
|
// 0xff for s and 0x00 for t
|
|
if (byte_s != uint8_t{0xff} && byte_s + 1 == byte_t) {
|
|
for (size_t i = diff_ind + 1; i < s.size(); ++i) {
|
|
byte_s = static_cast<uint8_t>(s[i]);
|
|
byte_t = static_cast<uint8_t>(t[i]);
|
|
if (byte_s != uint8_t{0xff} || byte_t != uint8_t{0x00}) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CanKeysWithDifferentByteContentsBeEqual() const override {
|
|
return false;
|
|
}
|
|
|
|
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
|
|
return a.compare(b);
|
|
}
|
|
};
|
|
|
|
class ReverseBytewiseComparatorImpl : public BytewiseComparatorImpl {
|
|
public:
|
|
ReverseBytewiseComparatorImpl() { }
|
|
|
|
const char* Name() const override {
|
|
return "rocksdb.ReverseBytewiseComparator";
|
|
}
|
|
|
|
int Compare(const Slice& a, const Slice& b) const override {
|
|
return -a.compare(b);
|
|
}
|
|
|
|
void FindShortestSeparator(std::string* start,
|
|
const Slice& limit) const override {
|
|
// Find length of common prefix
|
|
size_t min_length = std::min(start->size(), limit.size());
|
|
size_t diff_index = 0;
|
|
while ((diff_index < min_length) &&
|
|
((*start)[diff_index] == limit[diff_index])) {
|
|
diff_index++;
|
|
}
|
|
|
|
assert(diff_index <= min_length);
|
|
if (diff_index == min_length) {
|
|
// Do not shorten if one string is a prefix of the other
|
|
//
|
|
// We could handle cases like:
|
|
// V
|
|
// A A 2 X Y
|
|
// A A 2
|
|
// in a similar way as BytewiseComparator::FindShortestSeparator().
|
|
// We keep it simple by not implementing it. We can come back to it
|
|
// later when needed.
|
|
} else {
|
|
uint8_t start_byte = static_cast<uint8_t>((*start)[diff_index]);
|
|
uint8_t limit_byte = static_cast<uint8_t>(limit[diff_index]);
|
|
if (start_byte > limit_byte && diff_index < start->size() - 1) {
|
|
// Case like
|
|
// V
|
|
// A A 3 A A
|
|
// A A 1 B B
|
|
//
|
|
// or
|
|
// v
|
|
// A A 2 A A
|
|
// A A 1 B B
|
|
// In this case "AA2" will be good.
|
|
#ifndef NDEBUG
|
|
std::string old_start = *start;
|
|
#endif
|
|
start->resize(diff_index + 1);
|
|
#ifndef NDEBUG
|
|
assert(old_start >= *start);
|
|
#endif
|
|
assert(Slice(*start).compare(limit) > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FindShortSuccessor(std::string* /*key*/) const override {
|
|
// Don't do anything for simplicity.
|
|
}
|
|
|
|
bool CanKeysWithDifferentByteContentsBeEqual() const override {
|
|
return false;
|
|
}
|
|
|
|
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
|
|
return -a.compare(b);
|
|
}
|
|
};
|
|
}// namespace
|
|
|
|
const Comparator* BytewiseComparator() {
|
|
static BytewiseComparatorImpl bytewise;
|
|
return &bytewise;
|
|
}
|
|
|
|
const Comparator* ReverseBytewiseComparator() {
|
|
static ReverseBytewiseComparatorImpl rbytewise;
|
|
return &rbytewise;
|
|
}
|
|
|
|
} // namespace rocksdb
|