mirror of
https://github.com/apple/foundationdb.git
synced 2025-05-31 18:19:35 +08:00
* Proactively clean up idempotency ids for successful commits This change also includes some minor changes from my branch working on an idempotency ids cleaner, that I'd like to get merged sooner rather than later. - Adding a timestamp to idempotency values - Making IdempotencyId an actor file - Adding commit_unknown_result_fatal - Checking idempotencyIdsExpiredVersion in determineCommitStatus - Some testing QOL changes * Factor out decodeIdempotencyKey logic * Fix formatting * Update flow/include/flow/error_definitions.h Co-authored-by: A.J. Beamon <aj.beamon@snowflake.com> * Use KeyBackedObjectProperty for idempotencyIdsExpiredVersion * Add IDEMPOTENCY_ID_IN_MEMORY_LIFETIME knob * Rename ExpireIdempotencyKeyValuePairRequest Also add a code probe for the case where an ExpireIdempotencyIdRequest is received before the count is known, and add an assert * Fix formatting and add TODO for nwijetunga Co-authored-by: A.J. Beamon <aj.beamon@snowflake.com>
205 lines
6.8 KiB
C++
205 lines
6.8 KiB
C++
/*
|
|
* IdempotencyId.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 "fdbclient/IdempotencyId.actor.h"
|
|
#include "fdbclient/ReadYourWrites.h"
|
|
#include "fdbclient/SystemData.h"
|
|
#include "flow/UnitTest.h"
|
|
#include "flow/actorcompiler.h" // this has to be the last include
|
|
|
|
struct IdempotencyIdKVBuilderImpl {
|
|
Optional<Version> commitVersion;
|
|
Optional<uint8_t> batchIndexHighOrderByte;
|
|
BinaryWriter value{ IncludeVersion() };
|
|
};
|
|
|
|
IdempotencyIdKVBuilder::IdempotencyIdKVBuilder() : impl(PImpl<IdempotencyIdKVBuilderImpl>::create()) {}
|
|
|
|
void IdempotencyIdKVBuilder::setCommitVersion(Version commitVersion) {
|
|
impl->commitVersion = commitVersion;
|
|
}
|
|
|
|
void IdempotencyIdKVBuilder::add(const IdempotencyIdRef& id, uint16_t batchIndex) {
|
|
ASSERT(id.valid());
|
|
if (impl->batchIndexHighOrderByte.present()) {
|
|
ASSERT((batchIndex >> 8) == impl->batchIndexHighOrderByte.get());
|
|
} else {
|
|
impl->batchIndexHighOrderByte = batchIndex >> 8;
|
|
impl->value << int64_t(now());
|
|
}
|
|
StringRef s = id.asStringRefUnsafe();
|
|
impl->value << uint8_t(s.size());
|
|
impl->value.serializeBytes(s);
|
|
impl->value << uint8_t(batchIndex); // Low order byte of batchIndex
|
|
}
|
|
|
|
Optional<KeyValue> IdempotencyIdKVBuilder::buildAndClear() {
|
|
ASSERT(impl->commitVersion.present());
|
|
if (!impl->batchIndexHighOrderByte.present()) {
|
|
return {};
|
|
}
|
|
|
|
Value v = impl->value.toValue();
|
|
|
|
KeyRef key =
|
|
makeIdempotencySingleKeyRange(v.arena(), impl->commitVersion.get(), impl->batchIndexHighOrderByte.get()).begin;
|
|
|
|
impl->value = BinaryWriter(IncludeVersion());
|
|
impl->batchIndexHighOrderByte = Optional<uint8_t>();
|
|
|
|
Optional<KeyValue> result = KeyValue();
|
|
result.get().arena() = v.arena();
|
|
result.get().key = key;
|
|
result.get().value = v;
|
|
return result;
|
|
}
|
|
|
|
IdempotencyIdKVBuilder::~IdempotencyIdKVBuilder() = default;
|
|
|
|
Optional<CommitResult> kvContainsIdempotencyId(const KeyValueRef& kv, const IdempotencyIdRef& id) {
|
|
ASSERT(id.valid());
|
|
StringRef needle = id.asStringRefUnsafe();
|
|
StringRef haystack = kv.value;
|
|
|
|
#ifndef _WIN32
|
|
// The common case is that the kv does not contain the idempotency id, so early return if memmem is available
|
|
if (memmem(haystack.begin(), haystack.size(), needle.begin(), needle.size()) == nullptr) {
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
// Even if id is a substring of value, it may still not actually contain it.
|
|
BinaryReader reader(kv.value.begin(), kv.value.size(), IncludeVersion());
|
|
int64_t timestamp; // ignored
|
|
reader >> timestamp;
|
|
while (!reader.empty()) {
|
|
uint8_t length;
|
|
reader >> length;
|
|
StringRef candidate{ reinterpret_cast<const uint8_t*>(reader.readBytes(length)), length };
|
|
uint8_t lowOrderBatchIndex;
|
|
reader >> lowOrderBatchIndex;
|
|
if (candidate == needle) {
|
|
Version commitVersion;
|
|
uint8_t highOrderBatchIndex;
|
|
decodeIdempotencyKey(kv.key, commitVersion, highOrderBatchIndex);
|
|
return CommitResult{ commitVersion,
|
|
static_cast<uint16_t>((uint16_t(highOrderBatchIndex) << 8) |
|
|
uint16_t(lowOrderBatchIndex)) };
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void forceLinkIdempotencyIdTests() {}
|
|
|
|
namespace {
|
|
IdempotencyIdRef generate(Arena& arena) {
|
|
int length = deterministicRandom()->coinflip() ? deterministicRandom()->randomInt(16, 256) : 16;
|
|
StringRef id = makeString(length, arena);
|
|
deterministicRandom()->randomBytes(mutateString(id), length);
|
|
return IdempotencyIdRef(id);
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("/fdbclient/IdempotencyId/basic") {
|
|
Arena arena;
|
|
uint16_t firstBatchIndex = deterministicRandom()->randomUInt32();
|
|
firstBatchIndex &= 0xff7f; // ensure firstBatchIndex+5 won't change the higher order byte
|
|
uint16_t batchIndex = firstBatchIndex;
|
|
Version commitVersion = deterministicRandom()->randomInt64(0, std::numeric_limits<Version>::max());
|
|
std::vector<IdempotencyIdRef> idVector; // Reference
|
|
std::unordered_set<IdempotencyIdRef> idSet; // Make sure hash+equals works
|
|
IdempotencyIdKVBuilder builder; // Check kv data format
|
|
builder.setCommitVersion(commitVersion);
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
auto id = generate(arena);
|
|
idVector.emplace_back(id);
|
|
idSet.emplace(id);
|
|
builder.add(id, batchIndex++);
|
|
}
|
|
|
|
batchIndex = firstBatchIndex;
|
|
Optional<KeyValue> kvOpt = builder.buildAndClear();
|
|
ASSERT(kvOpt.present());
|
|
const auto& kv = kvOpt.get();
|
|
|
|
ASSERT(idSet.size() == idVector.size());
|
|
for (const auto& id : idVector) {
|
|
auto commitResult = kvContainsIdempotencyId(kv, id);
|
|
ASSERT(commitResult.present());
|
|
ASSERT(commitResult.get().commitVersion == commitVersion);
|
|
ASSERT(commitResult.get().batchIndex == batchIndex++);
|
|
ASSERT(idSet.find(id) != idSet.end());
|
|
idSet.erase(id);
|
|
ASSERT(idSet.find(id) == idSet.end());
|
|
}
|
|
ASSERT(idSet.size() == 0);
|
|
|
|
ASSERT(!kvContainsIdempotencyId(kv, generate(arena)).present());
|
|
|
|
return Void();
|
|
}
|
|
|
|
TEST_CASE("/fdbclient/IdempotencyId/serialization") {
|
|
ASSERT(ObjectReader::fromStringRef<IdempotencyIdRef>(ObjectWriter::toValue(IdempotencyIdRef(), Unversioned()),
|
|
Unversioned()) == IdempotencyIdRef());
|
|
for (int i = 0; i < 1000; ++i) {
|
|
Arena arena;
|
|
auto id = generate(arena);
|
|
auto serialized = ObjectWriter::toValue(id, Unversioned());
|
|
IdempotencyIdRef t;
|
|
ObjectReader reader(serialized.begin(), Unversioned());
|
|
reader.deserialize(t);
|
|
ASSERT(t == id);
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
KeyRangeRef makeIdempotencySingleKeyRange(Arena& arena, Version version, uint8_t highOrderBatchIndex) {
|
|
static const auto size =
|
|
idempotencyIdKeys.begin.size() + sizeof(version) + sizeof(highOrderBatchIndex) + /*\x00*/ 1;
|
|
|
|
StringRef second = makeString(size, arena);
|
|
auto* dst = mutateString(second);
|
|
|
|
memcpy(dst, idempotencyIdKeys.begin.begin(), idempotencyIdKeys.begin.size());
|
|
dst += idempotencyIdKeys.begin.size();
|
|
|
|
version = bigEndian64(version);
|
|
memcpy(dst, &version, sizeof(version));
|
|
dst += sizeof(version);
|
|
|
|
*dst++ = highOrderBatchIndex;
|
|
|
|
*dst++ = 0;
|
|
|
|
ASSERT_EQ(dst - second.begin(), size);
|
|
|
|
return KeyRangeRef(second.removeSuffix("\x00"_sr), second);
|
|
}
|
|
|
|
void decodeIdempotencyKey(KeyRef key, Version& commitVersion, uint8_t& highOrderBatchIndex) {
|
|
BinaryReader reader(key, Unversioned());
|
|
reader.readBytes(idempotencyIdKeys.begin.size());
|
|
reader >> commitVersion;
|
|
commitVersion = bigEndian64(commitVersion);
|
|
reader >> highOrderBatchIndex;
|
|
} |