foundationdb/fdbserver/KeyValueStoreCompressTestDa...

143 lines
5.4 KiB
C++

/*
* KeyValueStoreCompressTestData.actor.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 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() override { return store->getError(); }
Future<Void> onClosed() 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<UID> debugID = Optional<UID>()) override {
return doReadValue(store, key, debugID);
}
// 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<UID> debugID = Optional<UID>()) override {
return doReadValuePrefix( store, key, maxLength, debugID );
}
// 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<Standalone<RangeResultRef>> readRange(KeyRangeRef keys, int rowLimit = 1 << 30,
int byteLimit = 1 << 30) override {
return doReadRange(store, keys, rowLimit, byteLimit);
}
private:
ACTOR static Future<Optional<Value>> doReadValue(IKeyValueStore* store, Key key, Optional<UID> debugID) {
Optional<Value> v = wait(store->readValue(key, debugID));
if (!v.present()) return v;
return unpack(v.get());
}
ACTOR static Future<Optional<Value>> doReadValuePrefix( IKeyValueStore* store, Key key, int maxLength, Optional<UID> debugID ) {
Optional<Value> v = wait( doReadValue(store, key, debugID) );
if (!v.present()) return v;
if (maxLength < v.get().size()) {
return v.get().substr(0, maxLength);
}
else {
return v;
}
}
ACTOR Future<Standalone<RangeResultRef>> doReadRange( IKeyValueStore* store, KeyRangeRef keys, int rowLimit, int byteLimit ) {
Standalone<RangeResultRef> _vs = wait( store->readRange(keys, rowLimit, byteLimit) );
Standalone<RangeResultRef> 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(LiteralStringRef("\x00"));
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(LiteralStringRef("\x00"));
}
}
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);
}