1
0
mirror of https://github.com/apple/foundationdb.git synced 2025-05-31 18:19:35 +08:00
foundationdb/fdbclient/IdempotencyId.actor.cpp
Andrew Noyes 0a15f081a1
Proactively clean up idempotency ids for successful commits ()
* 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>
2022-10-28 09:07:54 -07:00

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;
}