aboutsummaryrefslogtreecommitdiffstats
path: root/storage
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2021-12-15 13:11:02 +0000
committerTor Brede Vekterli <vekterli@yahooinc.com>2021-12-15 13:11:02 +0000
commita6ac649a75684361d4997abde05e22c5e806e5b4 (patch)
tree2f17d73500e5e947dbb97affbfd86da92ffbc0d7 /storage
parent93510e7705a94af9f86e5bfa88b79f58631c44e2 (diff)
Let bucket maintenance priority queue be FIFO ordered within priority class
This avoids a potential starvation issue caused by the existing implementation, which is bucket ID ordered within a given priority class. The latter has the unfortunate effect that frequently reinserting buckets that sort before buckets that are already in the queue may starve these from being popped from the queue. Move to a composite key that first sorts on priority, then on a strictly increasing sequence number. Add a secondary index into this structure that allows for lookups on bucket IDs as before.
Diffstat (limited to 'storage')
-rw-r--r--storage/src/tests/distributor/simplebucketprioritydatabasetest.cpp92
-rw-r--r--storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h10
-rw-r--r--storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp117
-rw-r--r--storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h70
4 files changed, 126 insertions, 163 deletions
diff --git a/storage/src/tests/distributor/simplebucketprioritydatabasetest.cpp b/storage/src/tests/distributor/simplebucketprioritydatabasetest.cpp
index 129695f3e9e..10454ae9850 100644
--- a/storage/src/tests/distributor/simplebucketprioritydatabasetest.cpp
+++ b/storage/src/tests/distributor/simplebucketprioritydatabasetest.cpp
@@ -6,101 +6,109 @@
#include <string>
using document::test::makeDocumentBucket;
+using namespace ::testing;
namespace storage::distributor {
using document::BucketId;
using Priority = MaintenancePriority;
-TEST(SimpleBucketPriorityDatabaseTest, iterator_range_is_equal_on_empty_database) {
- SimpleBucketPriorityDatabase queue;
- auto begin = queue.begin();
- auto end = queue.end();
+struct SimpleBucketPriorityDatabaseTest : Test {
+ SimpleBucketPriorityDatabase _queue;
+};
+
+TEST_F(SimpleBucketPriorityDatabaseTest, iterator_range_is_equal_on_empty_database) {
+ auto begin = _queue.begin();
+ auto end = _queue.end();
EXPECT_TRUE(begin == end);
EXPECT_TRUE(begin == begin);
EXPECT_TRUE(end == end);
}
-TEST(SimpleBucketPriorityDatabaseTest, can_get_prioritized_bucket) {
- SimpleBucketPriorityDatabase queue;
-
+TEST_F(SimpleBucketPriorityDatabaseTest, can_get_prioritized_bucket) {
PrioritizedBucket lowPriBucket(makeDocumentBucket(BucketId(16, 1234)), Priority::VERY_LOW);
- queue.setPriority(lowPriBucket);
+ _queue.setPriority(lowPriBucket);
- PrioritizedBucket highest(*queue.begin());
+ PrioritizedBucket highest(*_queue.begin());
EXPECT_EQ(lowPriBucket, highest);
}
-TEST(SimpleBucketPriorityDatabaseTest, iterate_over_multiple_priorities) {
- SimpleBucketPriorityDatabase queue;
-
+TEST_F(SimpleBucketPriorityDatabaseTest, iterate_over_multiple_priorities) {
PrioritizedBucket lowPriBucket(makeDocumentBucket(BucketId(16, 1234)), Priority::LOW);
PrioritizedBucket highPriBucket(makeDocumentBucket(BucketId(16, 4321)), Priority::HIGH);
- queue.setPriority(lowPriBucket);
- queue.setPriority(highPriBucket);
+ _queue.setPriority(lowPriBucket);
+ _queue.setPriority(highPriBucket);
- auto iter = queue.begin();
+ auto iter = _queue.begin();
ASSERT_EQ(highPriBucket, *iter);
++iter;
- ASSERT_TRUE(iter != queue.end());
+ ASSERT_TRUE(iter != _queue.end());
ASSERT_EQ(lowPriBucket, *iter);
++iter;
- ASSERT_TRUE(iter == queue.end());
+ ASSERT_TRUE(iter == _queue.end());
}
-TEST(SimpleBucketPriorityDatabaseTest, multiple_set_priority_for_one_bucket) {
- SimpleBucketPriorityDatabase queue;
-
+TEST_F(SimpleBucketPriorityDatabaseTest, multiple_set_priority_for_one_bucket) {
PrioritizedBucket lowPriBucket(makeDocumentBucket(BucketId(16, 1234)), Priority::LOW);
PrioritizedBucket highPriBucket(makeDocumentBucket(BucketId(16, 1234)), Priority::HIGH);
- queue.setPriority(lowPriBucket);
- queue.setPriority(highPriBucket);
+ _queue.setPriority(lowPriBucket);
+ _queue.setPriority(highPriBucket);
- auto iter = queue.begin();
+ auto iter = _queue.begin();
ASSERT_EQ(highPriBucket, *iter);
++iter;
- ASSERT_TRUE(iter == queue.end());
+ ASSERT_TRUE(iter == _queue.end());
}
-TEST(SimpleBucketPriorityDatabaseTest, no_maintenance_needed_clears_bucket_from_database) {
- SimpleBucketPriorityDatabase queue;
-
+TEST_F(SimpleBucketPriorityDatabaseTest, no_maintenance_needed_clears_bucket_from_database) {
PrioritizedBucket highPriBucket(makeDocumentBucket(BucketId(16, 1234)), Priority::HIGH);
PrioritizedBucket noPriBucket(makeDocumentBucket(BucketId(16, 1234)),
Priority::NO_MAINTENANCE_NEEDED);
- queue.setPriority(highPriBucket);
- queue.setPriority(noPriBucket);
+ _queue.setPriority(highPriBucket);
+ _queue.setPriority(noPriBucket);
- auto iter = queue.begin();
- ASSERT_TRUE(iter == queue.end());
+ auto iter = _queue.begin();
+ ASSERT_TRUE(iter == _queue.end());
}
-TEST(SimpleBucketPriorityDatabaseTest, iterate_over_multiple_buckets_with_multiple_priorities) {
- SimpleBucketPriorityDatabase queue;
-
+TEST_F(SimpleBucketPriorityDatabaseTest, iterate_over_multiple_buckets_with_multiple_priorities) {
PrioritizedBucket lowPriBucket1(makeDocumentBucket(BucketId(16, 1)), Priority::LOW);
PrioritizedBucket lowPriBucket2(makeDocumentBucket(BucketId(16, 2)), Priority::LOW);
PrioritizedBucket mediumPriBucket(makeDocumentBucket(BucketId(16, 3)), Priority::MEDIUM);
PrioritizedBucket highPriBucket1(makeDocumentBucket(BucketId(16, 4)), Priority::HIGH);
PrioritizedBucket highPriBucket2(makeDocumentBucket(BucketId(16, 5)), Priority::HIGH);
- queue.setPriority(highPriBucket1);
- queue.setPriority(lowPriBucket2);
- queue.setPriority(mediumPriBucket);
- queue.setPriority(highPriBucket2);
- queue.setPriority(lowPriBucket1);
+ _queue.setPriority(highPriBucket1);
+ _queue.setPriority(lowPriBucket2);
+ _queue.setPriority(mediumPriBucket);
+ _queue.setPriority(highPriBucket2);
+ _queue.setPriority(lowPriBucket1);
- auto iter = queue.begin();
+ auto iter = _queue.begin();
PrioritizedBucket lastBucket(makeDocumentBucket(BucketId()), Priority::PRIORITY_LIMIT);
for (int i = 0; i < 5; ++i) {
- ASSERT_TRUE(iter != queue.end());
+ ASSERT_TRUE(iter != _queue.end());
ASSERT_FALSE(iter->moreImportantThan(lastBucket));
lastBucket = *iter;
++iter;
}
- ASSERT_TRUE(iter == queue.end());
+ ASSERT_TRUE(iter == _queue.end());
+}
+
+TEST_F(SimpleBucketPriorityDatabaseTest, buckets_within_same_priority_class_are_fifo_ordered) {
+ // We want FIFO order (2, 1) within the same priority class, not bucket ID order (1, 2).
+ PrioritizedBucket first_bucket(makeDocumentBucket(BucketId(16, 2)), Priority::LOW);
+ PrioritizedBucket second_bucket(makeDocumentBucket(BucketId(16, 1)), Priority::LOW);
+
+ _queue.setPriority(first_bucket);
+ _queue.setPriority(second_bucket);
+
+ auto iter = _queue.begin();
+ EXPECT_EQ(first_bucket, *iter);
+ ++iter;
+ EXPECT_EQ(second_bucket, *iter);
}
}
diff --git a/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h b/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h
index c19268d461a..4c193492f43 100644
--- a/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h
+++ b/storage/src/vespa/storage/distributor/maintenance/bucketprioritydatabase.h
@@ -13,11 +13,9 @@ protected:
class ConstIteratorImpl
{
public:
- virtual ~ConstIteratorImpl() { }
+ virtual ~ConstIteratorImpl() = default;
virtual void increment() = 0;
-
virtual bool equal(const ConstIteratorImpl& other) const = 0;
-
virtual PrioritizedBucket dereference() const = 0;
};
@@ -33,13 +31,13 @@ public:
{
ConstIteratorImplPtr _impl;
public:
- ConstIterator(ConstIteratorImplPtr impl)
+ explicit ConstIterator(ConstIteratorImplPtr impl)
: _impl(std::move(impl))
{}
ConstIterator(const ConstIterator &) = delete;
ConstIterator(ConstIterator &&) = default;
- virtual ~ConstIterator() {}
+ virtual ~ConstIterator() = default;
private:
friend class boost::iterator_core_access;
@@ -61,9 +59,7 @@ public:
virtual ~BucketPriorityDatabase() = default;
virtual const_iterator begin() const = 0;
-
virtual const_iterator end() const = 0;
-
virtual void setPriority(const PrioritizedBucket&) = 0;
};
diff --git a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp
index 547cf26583a..1dc0ff15e81 100644
--- a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp
+++ b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.cpp
@@ -1,22 +1,28 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "simplebucketprioritydatabase.h"
-#include <iostream>
+#include <vespa/vespalib/stllike/hash_map.hpp>
+#include <ostream>
#include <sstream>
namespace storage::distributor {
+SimpleBucketPriorityDatabase::SimpleBucketPriorityDatabase()
+ : _pri_fifo_buckets(),
+ _bucket_to_pri_iterators(),
+ _fifo_seq_num(0)
+{
+}
+
SimpleBucketPriorityDatabase::~SimpleBucketPriorityDatabase() = default;
void
SimpleBucketPriorityDatabase::clearAllEntriesForBucket(const document::Bucket &bucket)
{
- for (PriorityMap::iterator priIter(_prioritizedBuckets.begin()),
- priEnd(_prioritizedBuckets.end());
- priIter != priEnd;
- ++priIter)
- {
- priIter->second.erase(bucket);
+ auto maybe_iter = _bucket_to_pri_iterators.find(bucket);
+ if (maybe_iter != _bucket_to_pri_iterators.end()) {
+ _pri_fifo_buckets.erase(maybe_iter->second);
+ _bucket_to_pri_iterators.erase(maybe_iter);
}
}
@@ -25,112 +31,57 @@ SimpleBucketPriorityDatabase::setPriority(const PrioritizedBucket& bucket)
{
clearAllEntriesForBucket(bucket.getBucket());
if (bucket.requiresMaintenance()) {
- _prioritizedBuckets[bucket.getPriority()].insert(bucket.getBucket());
- }
-}
-
-void
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::initializeBucketIterToFirstAvailableEntry()
-{
- _bucketIter = _priorityIter->second.begin();
- if (currentPriorityAtEnd()) {
- increment();
- }
-}
-
-bool
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::atEnd() const
-{
- return _priorityIter == _priorityEnd;
-}
-
-void
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::stepWithinCurrentPriority()
-{
- ++_bucketIter;
-}
-
-bool
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::currentPriorityAtEnd() const
-{
- return _bucketIter == _priorityIter->second.end();
-}
-
-void
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::stepToNextPriority()
-{
- ++_priorityIter;
- if (atEnd()) {
- return;
- }
- _bucketIter = _priorityIter->second.begin();
-}
-
-void
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::step()
-{
- if (currentPriorityAtEnd()) {
- stepToNextPriority();
- } else {
- stepWithinCurrentPriority();
+ auto pri_insert_res = _pri_fifo_buckets.emplace(PriFifoCompositeKey(bucket.getPriority(), _fifo_seq_num),
+ bucket.getBucket());
+ assert(pri_insert_res.second);
+ ++_fifo_seq_num;
+ auto inv_insert_res = _bucket_to_pri_iterators.insert(std::make_pair(bucket.getBucket(), pri_insert_res.first));
+ assert(inv_insert_res.second);
}
}
void
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::increment()
+SimpleBucketPriorityDatabase::PriFifoMappingConstIteratorImpl::increment()
{
- while (!atEnd()) {
- step();
- if (atEnd() || !currentPriorityAtEnd()) {
- break;
- }
+ if (_pri_fifo_iter != _pri_fifo_end) {
+ ++_pri_fifo_iter;
}
}
bool
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::equal(const ConstIteratorImpl& otherBase) const
+SimpleBucketPriorityDatabase::PriFifoMappingConstIteratorImpl::equal(const ConstIteratorImpl& other) const
{
- const SimpleConstIteratorImpl& other(
- static_cast<const SimpleConstIteratorImpl&>(otherBase));
- if (_priorityIter != other._priorityIter) {
- return false;
- }
- if (atEnd()) {
- return true;
- }
- return _bucketIter == other._bucketIter;
+ auto& typed_other = dynamic_cast<const PriFifoMappingConstIteratorImpl&>(other);
+ return (_pri_fifo_iter == typed_other._pri_fifo_iter);
}
PrioritizedBucket
-SimpleBucketPriorityDatabase::SimpleConstIteratorImpl::dereference() const
+SimpleBucketPriorityDatabase::PriFifoMappingConstIteratorImpl::dereference() const
{
- return PrioritizedBucket(*_bucketIter, _priorityIter->first);
+ assert(_pri_fifo_iter != _pri_fifo_end);
+ return {_pri_fifo_iter->second, _pri_fifo_iter->first._pri};
}
SimpleBucketPriorityDatabase::const_iterator
SimpleBucketPriorityDatabase::begin() const
{
- return const_iterator(ConstIteratorImplPtr(new SimpleConstIteratorImpl(
- _prioritizedBuckets.rbegin(),
- _prioritizedBuckets.rend())));
+ return const_iterator(std::make_unique<PriFifoMappingConstIteratorImpl>(
+ _pri_fifo_buckets.begin(), _pri_fifo_buckets.end()));
}
SimpleBucketPriorityDatabase::const_iterator
SimpleBucketPriorityDatabase::end() const
{
- return const_iterator(ConstIteratorImplPtr(new SimpleConstIteratorImpl(
- _prioritizedBuckets.rend(),
- _prioritizedBuckets.rend())));
+ return const_iterator(std::make_unique<PriFifoMappingConstIteratorImpl>(
+ _pri_fifo_buckets.end(), _pri_fifo_buckets.end()));
}
std::string
SimpleBucketPriorityDatabase::toString() const
{
std::ostringstream ss;
- const_iterator i(begin());
- const_iterator e(end());
- for (; i != e; ++i) {
- ss << *i << '\n';
+ for (const auto& e : *this) {
+ ss << e << '\n';
}
return ss.str();
}
diff --git a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h
index ff5ebd9c6bb..aa2605e94ca 100644
--- a/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h
+++ b/storage/src/vespa/storage/distributor/maintenance/simplebucketprioritydatabase.h
@@ -2,6 +2,7 @@
#pragma once
#include "bucketprioritydatabase.h"
+#include <vespa/vespalib/stllike/hash_map.h>
#include <set>
#include <map>
@@ -10,45 +11,50 @@ namespace storage::distributor {
class SimpleBucketPriorityDatabase : public BucketPriorityDatabase
{
public:
- virtual ~SimpleBucketPriorityDatabase();
+ SimpleBucketPriorityDatabase();
+ ~SimpleBucketPriorityDatabase() override;
using Priority = PrioritizedBucket::Priority;
- virtual void setPriority(const PrioritizedBucket&) override;
- virtual const_iterator begin() const override;
- virtual const_iterator end() const override;
+ void setPriority(const PrioritizedBucket&) override;
+ const_iterator begin() const override;
+ const_iterator end() const override;
std::string toString() const;
private:
- using BucketSet = std::set<document::Bucket>;
- using PriorityMap = std::map<Priority, BucketSet>;
-
- class SimpleConstIteratorImpl : public ConstIteratorImpl
- {
- PriorityMap::const_reverse_iterator _priorityIter;
- PriorityMap::const_reverse_iterator _priorityEnd;
- BucketSet::const_iterator _bucketIter;
- public:
- SimpleConstIteratorImpl(PriorityMap::const_reverse_iterator first,
- PriorityMap::const_reverse_iterator end)
- : _priorityIter(first),
- _priorityEnd(end),
- _bucketIter()
- {
- if (!atEnd()) {
- initializeBucketIterToFirstAvailableEntry();
+ struct PriFifoCompositeKey {
+ Priority _pri;
+ uint64_t _seq_num;
+
+ constexpr PriFifoCompositeKey() noexcept : _pri(Priority::VERY_LOW), _seq_num(0) {}
+ constexpr PriFifoCompositeKey(Priority pri, uint64_t seq_num) noexcept
+ : _pri(pri),
+ _seq_num(seq_num)
+ {}
+
+ constexpr bool operator<(const PriFifoCompositeKey& rhs) const noexcept {
+ if (_pri != rhs._pri) {
+ // Unlike StorageAPI priorities, MaintenancePriority is higher value == higher priority
+ return (_pri > rhs._pri);
}
+ return _seq_num < rhs._seq_num;
}
- SimpleConstIteratorImpl(const SimpleConstIteratorImpl&) = delete;
- SimpleConstIteratorImpl& operator=(const SimpleConstIteratorImpl&) = delete;
- private:
- void initializeBucketIterToFirstAvailableEntry();
+ };
+
+ using PriFifoBucketMap = std::map<PriFifoCompositeKey, document::Bucket>;
+ // Important: the map iterator instances MUST be stable in the presence of other inserts/erases!
+ using BucketToPriIteratorMap = vespalib::hash_map<document::Bucket, PriFifoBucketMap::iterator, document::Bucket::hash>;
- bool atEnd() const;
- void stepWithinCurrentPriority();
- bool currentPriorityAtEnd() const;
- void stepToNextPriority();
- void step();
+ class PriFifoMappingConstIteratorImpl final : public ConstIteratorImpl {
+ PriFifoBucketMap::const_iterator _pri_fifo_iter;
+ PriFifoBucketMap::const_iterator _pri_fifo_end;
+ public:
+ PriFifoMappingConstIteratorImpl(PriFifoBucketMap::const_iterator pri_fifo_iter,
+ PriFifoBucketMap::const_iterator pri_fifo_end)
+ : _pri_fifo_iter(pri_fifo_iter),
+ _pri_fifo_end(pri_fifo_end)
+ {}
+ ~PriFifoMappingConstIteratorImpl() override = default;
void increment() override;
bool equal(const ConstIteratorImpl& other) const override;
@@ -57,7 +63,9 @@ private:
void clearAllEntriesForBucket(const document::Bucket &bucket);
- PriorityMap _prioritizedBuckets;
+ PriFifoBucketMap _pri_fifo_buckets;
+ BucketToPriIteratorMap _bucket_to_pri_iterators;
+ uint64_t _fifo_seq_num;
};
}