mirror of
https://github.com/apple/foundationdb.git
synced 2025-05-14 18:02:31 +08:00
Add GetGenerationQuorum to get generation from a quorum of config nodes
This commit is contained in:
parent
bd5a87e0e3
commit
79ba9c4e3a
@ -55,6 +55,22 @@ bool ConfigGeneration::operator!=(ConfigGeneration const& rhs) const {
|
|||||||
return !(*this == rhs);
|
return !(*this == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConfigGeneration::operator<(ConfigGeneration const& rhs) const {
|
||||||
|
if (committedVersion != rhs.committedVersion) {
|
||||||
|
return committedVersion < rhs.committedVersion;
|
||||||
|
} else {
|
||||||
|
return liveVersion < rhs.liveVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigGeneration::operator>(ConfigGeneration const& rhs) const {
|
||||||
|
if (committedVersion != rhs.committedVersion) {
|
||||||
|
return committedVersion > rhs.committedVersion;
|
||||||
|
} else {
|
||||||
|
return liveVersion > rhs.liveVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigTransactionCommitRequest::set(KeyRef key, ValueRef value) {
|
void ConfigTransactionCommitRequest::set(KeyRef key, ValueRef value) {
|
||||||
if (key == configTransactionDescriptionKey) {
|
if (key == configTransactionDescriptionKey) {
|
||||||
annotation.description = KeyRef(arena, value);
|
annotation.description = KeyRef(arena, value);
|
||||||
|
@ -28,18 +28,20 @@
|
|||||||
#include "flow/flow.h"
|
#include "flow/flow.h"
|
||||||
|
|
||||||
struct ConfigGeneration {
|
struct ConfigGeneration {
|
||||||
// The live version of each node is monotonically increasing
|
|
||||||
Version liveVersion{ 0 };
|
|
||||||
// The committedVersion of each node is the version of the last commit made durable.
|
// The committedVersion of each node is the version of the last commit made durable.
|
||||||
// Each committedVersion was previously given to clients as a liveVersion, prior to commit.
|
// Each committedVersion was previously given to clients as a liveVersion, prior to commit.
|
||||||
Version committedVersion{ 0 };
|
Version committedVersion{ 0 };
|
||||||
|
// The live version of each node is monotonically increasing
|
||||||
|
Version liveVersion{ 0 };
|
||||||
|
|
||||||
bool operator==(ConfigGeneration const&) const;
|
bool operator==(ConfigGeneration const&) const;
|
||||||
bool operator!=(ConfigGeneration const&) const;
|
bool operator!=(ConfigGeneration const&) const;
|
||||||
|
bool operator<(ConfigGeneration const&) const;
|
||||||
|
bool operator>(ConfigGeneration const&) const;
|
||||||
|
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void serialize(Ar& ar) {
|
void serialize(Ar& ar) {
|
||||||
serializer(ar, liveVersion, committedVersion);
|
serializer(ar, committedVersion, liveVersion);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,12 +59,16 @@ struct ConfigTransactionGetGenerationReply {
|
|||||||
|
|
||||||
struct ConfigTransactionGetGenerationRequest {
|
struct ConfigTransactionGetGenerationRequest {
|
||||||
static constexpr FileIdentifier file_identifier = 138941;
|
static constexpr FileIdentifier file_identifier = 138941;
|
||||||
|
// A hint to catch up lagging nodes:
|
||||||
|
Optional<Version> lastSeenLiveVersion;
|
||||||
ReplyPromise<ConfigTransactionGetGenerationReply> reply;
|
ReplyPromise<ConfigTransactionGetGenerationReply> reply;
|
||||||
ConfigTransactionGetGenerationRequest() = default;
|
ConfigTransactionGetGenerationRequest() = default;
|
||||||
|
explicit ConfigTransactionGetGenerationRequest(Optional<Version> const& lastSeenLiveVersion)
|
||||||
|
: lastSeenLiveVersion(lastSeenLiveVersion) {}
|
||||||
|
|
||||||
template <class Ar>
|
template <class Ar>
|
||||||
void serialize(Ar& ar) {
|
void serialize(Ar& ar) {
|
||||||
serializer(ar, reply);
|
serializer(ar, lastSeenLiveVersion, reply);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,24 +22,81 @@
|
|||||||
#include "fdbclient/PaxosConfigTransaction.h"
|
#include "fdbclient/PaxosConfigTransaction.h"
|
||||||
#include "flow/actorcompiler.h" // must be last include
|
#include "flow/actorcompiler.h" // must be last include
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class GetGenerationQuorum {
|
||||||
|
std::vector<Future<Void>> futures;
|
||||||
|
std::map<ConfigGeneration, size_t> seenGenerations;
|
||||||
|
Promise<ConfigGeneration> result;
|
||||||
|
size_t totalRepliesReceived{ 0 };
|
||||||
|
size_t maxAgreement{ 0 };
|
||||||
|
size_t size{ 0 };
|
||||||
|
Optional<Version> lastSeenLiveVersion;
|
||||||
|
|
||||||
|
ACTOR static Future<Void> handleReplyActor(GetGenerationQuorum* self,
|
||||||
|
Future<ConfigTransactionGetGenerationReply> replyFuture) {
|
||||||
|
ConfigTransactionGetGenerationReply reply = wait(replyFuture);
|
||||||
|
++self->totalRepliesReceived;
|
||||||
|
auto gen = reply.generation;
|
||||||
|
self->lastSeenLiveVersion = std::max(gen.liveVersion, self->lastSeenLiveVersion.orDefault(::invalidVersion));
|
||||||
|
auto& count = self->seenGenerations[gen];
|
||||||
|
++count;
|
||||||
|
self->maxAgreement = std::max(count, self->maxAgreement);
|
||||||
|
if (count == self->size / 2 + 1) {
|
||||||
|
self->result.send(gen); // self may be destroyed here
|
||||||
|
} else if (self->maxAgreement + (self->size - self->totalRepliesReceived) < (self->size / 2 + 1)) {
|
||||||
|
self->result.sendError(failed_to_reach_quorum());
|
||||||
|
}
|
||||||
|
return Void();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
GetGenerationQuorum(size_t size, Optional<Version> const& lastSeenLiveVersion)
|
||||||
|
: size(size), lastSeenLiveVersion(lastSeenLiveVersion) {
|
||||||
|
futures.reserve(size);
|
||||||
|
}
|
||||||
|
void addReplyCallback(Future<ConfigTransactionGetGenerationReply> replyFuture) {
|
||||||
|
futures.push_back(handleReplyActor(this, replyFuture));
|
||||||
|
}
|
||||||
|
Future<ConfigGeneration> getGeneration() const { return result.getFuture(); }
|
||||||
|
Optional<Version> getLastSeenLiveVersion() const { return lastSeenLiveVersion; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class PaxosConfigTransactionImpl {
|
class PaxosConfigTransactionImpl {
|
||||||
ConfigTransactionCommitRequest toCommit;
|
ConfigTransactionCommitRequest toCommit;
|
||||||
Future<ConfigGeneration> getGenerationFuture;
|
Future<ConfigGeneration> getGenerationFuture;
|
||||||
std::vector<ConfigTransactionInterface> ctis;
|
std::vector<ConfigTransactionInterface> ctis;
|
||||||
int numRetries{ 0 };
|
int numRetries{ 0 };
|
||||||
bool committed{ false };
|
bool committed{ false };
|
||||||
|
Optional<Version> lastSeenLiveVersion;
|
||||||
Optional<UID> dID;
|
Optional<UID> dID;
|
||||||
Database cx;
|
Database cx;
|
||||||
|
|
||||||
ACTOR static Future<ConfigGeneration> getGeneration(PaxosConfigTransactionImpl* self) {
|
ACTOR static Future<ConfigGeneration> getGeneration(PaxosConfigTransactionImpl* self) {
|
||||||
state std::vector<Future<ConfigTransactionGetGenerationReply>> getGenerationFutures;
|
state GetGenerationQuorum quorum(self->ctis.size(), self->lastSeenLiveVersion);
|
||||||
getGenerationFutures.reserve(self->ctis.size());
|
state int retries = 0;
|
||||||
for (auto const& cti : self->ctis) {
|
loop {
|
||||||
getGenerationFutures.push_back(cti.getGeneration.getReply(ConfigTransactionGetGenerationRequest{}));
|
for (auto const& cti : self->ctis) {
|
||||||
|
quorum.addReplyCallback(
|
||||||
|
cti.getGeneration.getReply(ConfigTransactionGetGenerationRequest{ self->lastSeenLiveVersion }));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
state ConfigGeneration gen = wait(quorum.getGeneration());
|
||||||
|
wait(delay(0.0)); // Let reply callback actors finish before destructing quorum
|
||||||
|
return gen;
|
||||||
|
} catch (Error& e) {
|
||||||
|
if (e.code() == error_code_failed_to_reach_quorum) {
|
||||||
|
TEST(true); // Failed to reach quorum getting generation
|
||||||
|
wait(delayJittered(0.01 * (1 << retries)));
|
||||||
|
++retries;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self->lastSeenLiveVersion = quorum.getLastSeenLiveVersion();
|
||||||
}
|
}
|
||||||
// FIXME: Must tolerate failures and disagreement
|
|
||||||
wait(waitForAll(getGenerationFutures));
|
|
||||||
return getGenerationFutures[0].get().generation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTOR static Future<Optional<Value>> get(PaxosConfigTransactionImpl* self, Key key) {
|
ACTOR static Future<Optional<Value>> get(PaxosConfigTransactionImpl* self, Key key) {
|
||||||
@ -48,7 +105,7 @@ class PaxosConfigTransactionImpl {
|
|||||||
}
|
}
|
||||||
state ConfigKey configKey = ConfigKey::decodeKey(key);
|
state ConfigKey configKey = ConfigKey::decodeKey(key);
|
||||||
ConfigGeneration generation = wait(self->getGenerationFuture);
|
ConfigGeneration generation = wait(self->getGenerationFuture);
|
||||||
// TODO: Load balance
|
// TODO: Load balance, and only send request to replicas that we have gotten the current generation from
|
||||||
ConfigTransactionGetReply reply =
|
ConfigTransactionGetReply reply =
|
||||||
wait(self->ctis[0].get.getReply(ConfigTransactionGetRequest{ generation, configKey }));
|
wait(self->ctis[0].get.getReply(ConfigTransactionGetRequest{ generation, configKey }));
|
||||||
if (reply.value.present()) {
|
if (reply.value.present()) {
|
||||||
|
@ -203,6 +203,10 @@ class ConfigNodeImpl {
|
|||||||
ACTOR static Future<Void> getNewGeneration(ConfigNodeImpl* self, ConfigTransactionGetGenerationRequest req) {
|
ACTOR static Future<Void> getNewGeneration(ConfigNodeImpl* self, ConfigTransactionGetGenerationRequest req) {
|
||||||
state ConfigGeneration generation = wait(getGeneration(self));
|
state ConfigGeneration generation = wait(getGeneration(self));
|
||||||
++generation.liveVersion;
|
++generation.liveVersion;
|
||||||
|
if (req.lastSeenLiveVersion.present()) {
|
||||||
|
TEST(req.lastSeenLiveVersion.get() >= generation.liveVersion); // Node is lagging behind some other node
|
||||||
|
generation.liveVersion = std::max(generation.liveVersion, req.lastSeenLiveVersion.get() + 1);
|
||||||
|
}
|
||||||
self->kvStore->set(KeyValueRef(currentGenerationKey, BinaryWriter::toValue(generation, IncludeVersion())));
|
self->kvStore->set(KeyValueRef(currentGenerationKey, BinaryWriter::toValue(generation, IncludeVersion())));
|
||||||
wait(self->kvStore->commit());
|
wait(self->kvStore->commit());
|
||||||
req.reply.send(ConfigTransactionGetGenerationReply{ generation });
|
req.reply.send(ConfigTransactionGetGenerationReply{ generation });
|
||||||
|
@ -66,13 +66,16 @@ class ConfigIncrementWorkload : public TestWorkload {
|
|||||||
try {
|
try {
|
||||||
state Reference<ISingleThreadTransaction> tr = self->getTransaction(cx);
|
state Reference<ISingleThreadTransaction> tr = self->getTransaction(cx);
|
||||||
state int currentValue = wait(get(tr));
|
state int currentValue = wait(get(tr));
|
||||||
|
ASSERT_GE(currentValue, self->lastKnownValue);
|
||||||
set(tr, currentValue + 1);
|
set(tr, currentValue + 1);
|
||||||
wait(delay(deterministicRandom()->random01() * 2 * self->meanSleepWithinTransactions));
|
wait(delay(deterministicRandom()->random01() * 2 * self->meanSleepWithinTransactions));
|
||||||
wait(tr->commit());
|
wait(tr->commit());
|
||||||
ASSERT_GT(tr->getCommittedVersion(), self->lastKnownCommittedVersion);
|
ASSERT_GT(tr->getCommittedVersion(), self->lastKnownCommittedVersion);
|
||||||
ASSERT_GE(currentValue, self->lastKnownValue);
|
|
||||||
self->lastKnownCommittedVersion = tr->getCommittedVersion();
|
self->lastKnownCommittedVersion = tr->getCommittedVersion();
|
||||||
self->lastKnownValue = currentValue + 1;
|
self->lastKnownValue = currentValue + 1;
|
||||||
|
TraceEvent("ConfigIncrementSucceeded")
|
||||||
|
.detail("CommittedVersion", self->lastKnownCommittedVersion)
|
||||||
|
.detail("CommittedValue", self->lastKnownValue);
|
||||||
++self->transactions;
|
++self->transactions;
|
||||||
++trsComplete;
|
++trsComplete;
|
||||||
wait(delay(deterministicRandom()->random01() * 2 * self->meanSleepBetweenTransactions));
|
wait(delay(deterministicRandom()->random01() * 2 * self->meanSleepBetweenTransactions));
|
||||||
|
@ -77,6 +77,7 @@ ERROR( dd_not_found, 1053, "Data distributor not found")
|
|||||||
ERROR( wrong_connection_file, 1054, "Connection file mismatch")
|
ERROR( wrong_connection_file, 1054, "Connection file mismatch")
|
||||||
ERROR( version_already_compacted, 1055, "The requested changes have been compacted away")
|
ERROR( version_already_compacted, 1055, "The requested changes have been compacted away")
|
||||||
ERROR( local_config_changed, 1056, "Local configuration file has changed. Restart and apply these changes" )
|
ERROR( local_config_changed, 1056, "Local configuration file has changed. Restart and apply these changes" )
|
||||||
|
ERROR( failed_to_reach_quorum, 1057, "Failed to reach quorum from configuration database nodes. Retry sending these requests" )
|
||||||
|
|
||||||
ERROR( broken_promise, 1100, "Broken promise" )
|
ERROR( broken_promise, 1100, "Broken promise" )
|
||||||
ERROR( operation_cancelled, 1101, "Asynchronous operation cancelled" )
|
ERROR( operation_cancelled, 1101, "Asynchronous operation cancelled" )
|
||||||
|
Loading…
x
Reference in New Issue
Block a user