362 lines
12 KiB
C++
362 lines
12 KiB
C++
/*
|
|
* RyowCorrectness.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 <vector>
|
|
|
|
#include "fdbserver/TesterInterface.actor.h"
|
|
#include "fdbserver/workloads/workloads.actor.h"
|
|
#include "fdbserver/workloads/MemoryKeyValueStore.h"
|
|
#include "fdbserver/workloads/ApiWorkload.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
#define TRACE_TRANSACTION 0
|
|
|
|
#if TRACE_TRANSACTION
|
|
StringRef debugKey = LiteralStringRef("0000000000uldlamzpspf");
|
|
#endif
|
|
|
|
//A struct representing an operation to be performed on a transaction
|
|
struct Operation {
|
|
|
|
//An enum of API operation types to perform in a transaction
|
|
enum OperationType {
|
|
SET,
|
|
GET,
|
|
GET_RANGE,
|
|
GET_RANGE_SELECTOR,
|
|
GET_KEY,
|
|
CLEAR,
|
|
CLEAR_RANGE
|
|
};
|
|
|
|
OperationType type;
|
|
|
|
Key beginKey;
|
|
Key endKey;
|
|
|
|
KeySelector beginSelector;
|
|
KeySelector endSelector;
|
|
|
|
Value value;
|
|
|
|
int limit;
|
|
bool reverse = false;
|
|
};
|
|
|
|
//A workload which executes random sequences of operations on RYOW transactions and confirms the results
|
|
struct RyowCorrectnessWorkload : ApiWorkload {
|
|
|
|
//How long the test should run
|
|
int duration;
|
|
|
|
//The number of operations to perform on a transaction in between commits
|
|
int opsPerTransaction;
|
|
|
|
RyowCorrectnessWorkload(WorkloadContext const& wcx) : ApiWorkload(wcx, 1) {
|
|
duration = getOption(options, LiteralStringRef("duration"), 60);
|
|
opsPerTransaction = getOption(options, LiteralStringRef("opsPerTransaction"), 50);
|
|
}
|
|
|
|
virtual std::string description() {
|
|
return "RyowCorrectness";
|
|
}
|
|
|
|
ACTOR Future<Void> performSetup(Database cx, RyowCorrectnessWorkload *self) {
|
|
std::vector<TransactionType> types;
|
|
types.push_back(READ_YOUR_WRITES);
|
|
|
|
wait(self->chooseTransactionFactory(cx, types));
|
|
return Void();
|
|
}
|
|
|
|
Future<Void> performSetup(Database const& cx) {
|
|
return performSetup(cx, this);
|
|
}
|
|
|
|
//Generates a random sequence of operations to perform in a single transaction
|
|
std::vector<Operation> generateOperationSequence(Standalone<VectorRef<KeyValueRef>> const& data) {
|
|
std::vector<Operation> sequence;
|
|
|
|
int pdfArray[] = { 0, 100, 100, 50, 50, 20, 100, 5 };
|
|
vector<int> pdf = vector<int>(pdfArray, pdfArray + 8);
|
|
|
|
//Choose a random operation type (SET, GET, GET_RANGE, GET_RANGE_SELECTOR, GET_KEY, CLEAR, CLEAR_RANGE).
|
|
int totalDensity = 0;
|
|
for(int i = 0; i < pdf.size(); i++)
|
|
totalDensity += pdf[i];
|
|
|
|
for(int i = 0; i < opsPerTransaction; ++i) {
|
|
int cumulativeDensity = 0;
|
|
int random = deterministicRandom()->randomInt(0, totalDensity);
|
|
for(int i = 0; i < pdf.size() - 1; i++) {
|
|
if(cumulativeDensity + pdf[i] <= random && random < cumulativeDensity + pdf[i] + pdf[i + 1]) {
|
|
Operation info;
|
|
info.type = (Operation::OperationType)i;
|
|
|
|
switch(info.type) {
|
|
case Operation::GET:
|
|
case Operation::CLEAR:
|
|
info.beginKey = selectRandomKey(data, .8);
|
|
break;
|
|
case Operation::SET:
|
|
info.beginKey = selectRandomKey(data, .5);
|
|
info.value = generateValue();
|
|
break;
|
|
case Operation::GET_RANGE:
|
|
case Operation::CLEAR_RANGE:
|
|
info.beginKey = selectRandomKey(data, .8);
|
|
info.endKey = selectRandomKey(data, .8);
|
|
info.limit = deterministicRandom()->randomInt(0, 1000);
|
|
info.reverse = (bool)deterministicRandom()->randomInt(0, 2);
|
|
|
|
if(info.beginKey > info.endKey)
|
|
std::swap(info.beginKey, info.endKey);
|
|
|
|
break;
|
|
case Operation::GET_RANGE_SELECTOR:
|
|
case Operation::GET_KEY:
|
|
info.beginSelector = generateKeySelector(data, 1000);
|
|
info.endSelector = generateKeySelector(data, 1000);
|
|
info.limit = deterministicRandom()->randomInt(0, 1000);
|
|
info.reverse = (bool)deterministicRandom()->randomInt(0, 2);
|
|
break;
|
|
}
|
|
|
|
sequence.push_back(info);
|
|
break;
|
|
}
|
|
|
|
cumulativeDensity += pdf[i];
|
|
}
|
|
}
|
|
|
|
return sequence;
|
|
}
|
|
|
|
//Adds a single KV-pair to the list of results
|
|
void pushKVPair(std::vector<Standalone<RangeResultRef>> &results, Key const& key, Optional<Value> const& value) {
|
|
Standalone<RangeResultRef> result;
|
|
if(!value.present())
|
|
result.push_back_deep(result.arena(), KeyValueRef(key, LiteralStringRef("VALUE_NOT_PRESENT")));
|
|
else
|
|
result.push_back_deep(result.arena(), KeyValueRef(key, value.get()));
|
|
|
|
results.push_back(result);
|
|
}
|
|
|
|
//Applies a sequence of operations to the memory store and returns the results
|
|
std::vector<Standalone<RangeResultRef>> applySequenceToStore(std::vector<Operation> sequence) {
|
|
std::vector<Standalone<RangeResultRef>> results;
|
|
Key key;
|
|
|
|
#if TRACE_TRANSACTION
|
|
printf("NEW_TRANSACTION\n");
|
|
#endif
|
|
|
|
for(auto op : sequence) {
|
|
switch(op.type) {
|
|
case Operation::SET:
|
|
store.set(op.beginKey, op.value);
|
|
#if TRACE_TRANSACTION
|
|
if(op.beginKey == debugKey)
|
|
printf("SET: %s = %d\n", printable(op.beginKey).c_str(), op.value.size());
|
|
#endif
|
|
break;
|
|
case Operation::GET:
|
|
pushKVPair(results, op.beginKey, store.get(op.beginKey));
|
|
#if TRACE_TRANSACTION && 0
|
|
if(op.beginKey == debugKey)
|
|
printf("GET: %s\n", printable(op.beginKey).c_str());
|
|
#endif
|
|
break;
|
|
case Operation::GET_RANGE_SELECTOR:
|
|
op.beginKey = store.getKey(op.beginSelector);
|
|
op.endKey = store.getKey(op.endSelector);
|
|
|
|
if(op.beginKey > op.endKey)
|
|
op.endKey = op.beginKey;
|
|
//Fall-through
|
|
case Operation::GET_RANGE:
|
|
results.push_back(store.getRange(KeyRangeRef(op.beginKey, op.endKey), op.limit, op.reverse));
|
|
#if TRACE_TRANSACTION
|
|
if(op.beginKey <= debugKey && debugKey < op.endKey)
|
|
printf("%s: %s - %s (limit=%d, reverse=%d)\n", op.type == Operation::GET_RANGE ? "GET_RANGE" : "GET_RANGE_SELECTOR", printable(op.beginKey).c_str(), printable(op.endKey).c_str(), op.limit, op.reverse);
|
|
#endif
|
|
break;
|
|
case Operation::GET_KEY:
|
|
key = store.getKey(op.beginSelector);
|
|
pushKVPair(results, key, Value());
|
|
#if TRACE_TRANSACTION
|
|
if(key == debugKey)
|
|
printf("GET_KEY: %s = %s\n", op.beginSelector.toString().c_str(), printable(key).c_str());
|
|
#endif
|
|
break;
|
|
case Operation::CLEAR:
|
|
store.clear(op.beginKey);
|
|
#if TRACE_TRANSACTION
|
|
if(op.beginKey == debugKey)
|
|
printf("CLEAR: %s\n", printable(op.beginKey).c_str());
|
|
#endif
|
|
break;
|
|
case Operation::CLEAR_RANGE:
|
|
store.clear(KeyRangeRef(op.beginKey, op.endKey));
|
|
#if TRACE_TRANSACTION
|
|
if(op.beginKey <= debugKey && debugKey < op.endKey)
|
|
printf("CLEAR_RANGE: %s - %s\n", printable(op.beginKey).c_str(), printable(op.endKey).c_str());
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
//Applies a sequence of operations to the database and returns the results
|
|
ACTOR Future<std::vector<Standalone<RangeResultRef>>> applySequenceToDatabase(Reference<TransactionWrapper> transaction, std::vector<Operation> sequence, RyowCorrectnessWorkload *self) {
|
|
state bool dontUpdateResults = false;
|
|
state std::vector<Standalone<RangeResultRef>> results;
|
|
loop {
|
|
try {
|
|
state int i;
|
|
for(i = 0; i < sequence.size(); ++i) {
|
|
state Operation op = sequence[i];
|
|
|
|
if(op.type == Operation::SET) {
|
|
transaction->set(op.beginKey, op.value);
|
|
}
|
|
else if(op.type == Operation::GET) {
|
|
Optional<Value> val = wait(transaction->get(op.beginKey));
|
|
if(!dontUpdateResults)
|
|
self->pushKVPair(results, op.beginKey, val);
|
|
}
|
|
else if(op.type == Operation::GET_RANGE) {
|
|
KeyRangeRef range(op.beginKey, op.endKey);
|
|
Standalone<RangeResultRef> result = wait(transaction->getRange(range, op.limit, op.reverse));
|
|
if(!dontUpdateResults)
|
|
results.push_back((RangeResultRef)result);
|
|
}
|
|
else if(op.type == Operation::GET_RANGE_SELECTOR) {
|
|
Standalone<RangeResultRef> result = wait(transaction->getRange(op.beginSelector, op.endSelector, op.limit, op.reverse));
|
|
if(!dontUpdateResults)
|
|
results.push_back((RangeResultRef)result);
|
|
}
|
|
else if(op.type == Operation::GET_KEY) {
|
|
Key key = wait(transaction->getKey(op.beginSelector));
|
|
if(!dontUpdateResults)
|
|
self->pushKVPair(results, key, Value());
|
|
}
|
|
else if(op.type == Operation::CLEAR) {
|
|
transaction->clear(op.beginKey);
|
|
}
|
|
else if(op.type == Operation::CLEAR_RANGE) {
|
|
KeyRangeRef range(op.beginKey, op.endKey);
|
|
transaction->clear(range);
|
|
}
|
|
}
|
|
|
|
wait(transaction->commit());
|
|
return results;
|
|
}
|
|
catch(Error &e) {
|
|
//If the transaction was possibly committed, then keep the results that we got (since they might change the next time around the loop),
|
|
//but try to commit the transaction again
|
|
if(e.code() == error_code_commit_unknown_result)
|
|
dontUpdateResults = true;
|
|
else if(!dontUpdateResults)
|
|
results.clear();
|
|
|
|
wait(transaction->onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Compares a sequence of results from the database and the memory store
|
|
bool compareResults(std::vector<Standalone<RangeResultRef>> dbResults, std::vector<Standalone<RangeResultRef>> storeResults, std::vector<Operation> sequence, Version readVersion) {
|
|
ASSERT(storeResults.size() == dbResults.size());
|
|
|
|
int currentResult = 0;
|
|
for(int i = 0; i < sequence.size(); ++i) {
|
|
Operation op = sequence[i];
|
|
if(op.type == Operation::SET || op.type == Operation::CLEAR || op.type == Operation::CLEAR_RANGE)
|
|
continue;
|
|
|
|
if(!ApiWorkload::compareResults(dbResults[currentResult], storeResults[currentResult], readVersion)) {
|
|
switch(op.type) {
|
|
case Operation::GET:
|
|
printf("Operation GET failed: key = %s\n", printable(op.beginKey).c_str());
|
|
break;
|
|
case Operation::GET_RANGE:
|
|
printf("Operation GET_RANGE failed: begin = %s, end = %s, limit = %d, reverse = %d\n", printable(op.beginKey).c_str(), printable(op.endKey).c_str(), op.limit, op.reverse);
|
|
break;
|
|
case Operation::GET_RANGE_SELECTOR:
|
|
printf("Operation GET_RANGE_SELECTOR failed: begin = %s, end = %s, limit = %d, reverse = %d\n", op.beginSelector.toString().c_str(), op.endSelector.toString().c_str(), op.limit, op.reverse);
|
|
break;
|
|
case Operation::GET_KEY:
|
|
printf("Operation GET_KEY failed: selector = %s\n", op.beginSelector.toString().c_str());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
++currentResult;
|
|
}
|
|
|
|
ASSERT(currentResult == storeResults.size());
|
|
|
|
return true;
|
|
}
|
|
|
|
//Execute transactions with multiple random operations each
|
|
ACTOR Future<Void> performTest(Database cx, Standalone<VectorRef<KeyValueRef>> data, RyowCorrectnessWorkload *self) {
|
|
loop {
|
|
state Reference<TransactionWrapper> transaction = self->createTransaction();
|
|
state std::vector<Operation> sequence = self->generateOperationSequence(data);
|
|
state std::vector<Standalone<RangeResultRef>> storeResults = self->applySequenceToStore(sequence);
|
|
state std::vector<Standalone<RangeResultRef>> dbResults = wait(self->applySequenceToDatabase(transaction, sequence, self));
|
|
|
|
Version readVersion = wait(transaction->getReadVersion());
|
|
|
|
state bool result = self->compareResults(dbResults, storeResults, sequence, readVersion);
|
|
if(!result)
|
|
self->testFailure("Transaction results did not match");
|
|
|
|
bool result2 = wait(self->compareDatabaseToMemory());
|
|
if(result && !result2)
|
|
self->testFailure("Database contents did not match");
|
|
|
|
if(!result || !result2)
|
|
return Void();
|
|
}
|
|
}
|
|
|
|
Future<Void> performTest(Database const& cx, Standalone<VectorRef<KeyValueRef>> const& data) {
|
|
return ::success(timeout(performTest(cx, data, this), duration));
|
|
}
|
|
|
|
virtual void getMetrics( vector<PerfMetric>& m ) {
|
|
|
|
}
|
|
};
|
|
|
|
WorkloadFactory<RyowCorrectnessWorkload> RyowCorrectnessWorkloadFactory("RyowCorrectness");
|