foundationdb/fdbserver/KeyValueStoreCompressTestDa...

160 lines
5.9 KiB
C++

/*
* KeyValueStoreCompressTestData.actor.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "fdbserver/IKeyValueStore.h"
#include "flow/actorcompiler.h" // has to be last include
// KeyValueStoreCompressTestData wraps an existing IKeyValueStore and
// implements the following rudimentary compression scheme:
// An arbitrarily long value which consists entirely of a single repeated nonzero byte is mapped to
// a 5-byte value consisting of that byte followed by a little-endian integer giving the number
// of repetitions.
// All other values are mapped to a zero byte followed by the value.
// This store is used in testing to let us simulate having much bigger disks than we actually
// have, in order to test really big databases.
struct KeyValueStoreCompressTestData final : IKeyValueStore {
IKeyValueStore* store;
KeyValueStoreCompressTestData(IKeyValueStore* store) : store(store) {}
Future<Void> getError() const override { return store->getError(); }
Future<Void> onClosed() const override { return store->onClosed(); }
void dispose() override {
store->dispose();
delete this;
}
void close() override {
store->close();
delete this;
}
KeyValueStoreType getType() const override { return store->getType(); }
StorageBytes getStorageBytes() const override { return store->getStorageBytes(); }
void set(KeyValueRef keyValue, const Arena* arena = nullptr) override {
store->set(KeyValueRef(keyValue.key, pack(keyValue.value)), arena);
}
void clear(KeyRangeRef range, const Arena* arena = nullptr) override { store->clear(range, arena); }
Future<Void> commit(bool sequential = false) override { return store->commit(sequential); }
Future<Optional<Value>> readValue(KeyRef key, Optional<ReadOptions> options) override {
return doReadValue(store, key, options);
}
// Note that readValuePrefix doesn't do anything in this implementation of IKeyValueStore, so the "atomic bomb"
// problem is still present if you are using this storage interface, but this storage interface is not used by
// customers ever. However, if you want to try to test malicious atomic op workloads with compressed values for some
// reason, you will need to fix this.
Future<Optional<Value>> readValuePrefix(KeyRef key, int maxLength, Optional<ReadOptions> options) override {
return doReadValuePrefix(store, key, maxLength, options);
}
// If rowLimit>=0, reads first rows sorted ascending, otherwise reads last rows sorted descending
// The total size of the returned value (less the last entry) will be less than byteLimit
Future<RangeResult> readRange(KeyRangeRef keys,
int rowLimit,
int byteLimit,
Optional<ReadOptions> options = Optional<ReadOptions>()) override {
return doReadRange(store, keys, rowLimit, byteLimit, options);
}
Future<EncryptionAtRestMode> encryptionMode() override {
return EncryptionAtRestMode(EncryptionAtRestMode::DISABLED);
}
private:
ACTOR static Future<Optional<Value>> doReadValue(IKeyValueStore* store, Key key, Optional<ReadOptions> options) {
Optional<Value> v = wait(store->readValue(key, options));
if (!v.present())
return v;
return unpack(v.get());
}
ACTOR static Future<Optional<Value>> doReadValuePrefix(IKeyValueStore* store,
Key key,
int maxLength,
Optional<ReadOptions> options) {
Optional<Value> v = wait(doReadValue(store, key, options));
if (!v.present())
return v;
if (maxLength < v.get().size()) {
return v.get().substr(0, maxLength);
} else {
return v;
}
}
ACTOR Future<RangeResult> doReadRange(IKeyValueStore* store,
KeyRangeRef keys,
int rowLimit,
int byteLimit,
Optional<ReadOptions> options) {
RangeResult _vs = wait(store->readRange(keys, rowLimit, byteLimit, options));
RangeResult vs = _vs; // Get rid of implicit const& from wait statement
Arena& a = vs.arena();
for (int i = 0; i < vs.size(); i++)
vs[i].value = ValueRef(a, (ValueRef const&)unpack(vs[i].value));
return vs;
}
// These implement the actual "compression" scheme
static Value pack(Value val) {
if (!val.size())
return val;
uint8_t c = val[0];
// If the value starts with a 0-byte, then we don't compress it
if (c == 0)
return val.withPrefix("\x00"_sr);
for (int i = 1; i < val.size(); i++) {
if (val[i] != c) {
// The value is something other than a single repeated character, so not compressible :-)
return val.withPrefix("\x00"_sr);
}
}
int n = val.size();
val = makeString(5);
uint8_t* p = mutateString(val);
p[0] = c;
*(int*)(p + 1) = n;
return val;
}
static Value unpack(Value val) {
if (!val.size())
return val;
if (val[0] == 0)
return val.substr(1); // Uncompressed value
ASSERT(val.size() == 5);
uint8_t c = val[0];
int n = *(int*)(val.begin() + 1);
val = makeString(n);
uint8_t* p = mutateString(val);
memset(p, c, n);
return val;
}
};
IKeyValueStore* keyValueStoreCompressTestData(IKeyValueStore* store) {
return new KeyValueStoreCompressTestData(store);
}