Skip to content

Commit 18453fa

Browse files
authored
upstream: avoid quadratic time complexity in server initialization (#15347)
Currently ClusterManagerInitHelper.secondary_init_clusters_ is a list. Upon every insert the list gets searched for an already added cluster with the same name. Since in normal circumstances all new clusters have unique names the worst case scenario is triggered and all elements of the list are checked sequentially upon every cluster added. Replace the list with a hash map. Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
1 parent 471c609 commit 18453fa

File tree

3 files changed

+24
-21
lines changed

3 files changed

+24
-21
lines changed

source/common/upstream/cluster_manager_impl.cc

+15-19
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include <chrono>
44
#include <cstdint>
55
#include <functional>
6-
#include <list>
76
#include <memory>
87
#include <string>
98
#include <vector>
@@ -67,20 +66,12 @@ void ClusterManagerInitHelper::addCluster(ClusterManagerCluster& cm_cluster) {
6766
Cluster& cluster = cm_cluster.cluster();
6867
if (cluster.initializePhase() == Cluster::InitializePhase::Primary) {
6968
// Remove the previous cluster before the cluster object is destroyed.
70-
primary_init_clusters_.remove_if(
71-
[name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) {
72-
return cluster_iter->cluster().info()->name() == name_to_remove;
73-
});
74-
primary_init_clusters_.push_back(&cm_cluster);
69+
primary_init_clusters_.insert_or_assign(cm_cluster.cluster().info()->name(), &cm_cluster);
7570
cluster.initialize(initialize_cb);
7671
} else {
7772
ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary);
7873
// Remove the previous cluster before the cluster object is destroyed.
79-
secondary_init_clusters_.remove_if(
80-
[name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) {
81-
return cluster_iter->cluster().info()->name() == name_to_remove;
82-
});
83-
secondary_init_clusters_.push_back(&cm_cluster);
74+
secondary_init_clusters_.insert_or_assign(cm_cluster.cluster().info()->name(), &cm_cluster);
8475
if (started_secondary_initialize_) {
8576
// This can happen if we get a second CDS update that adds new clusters after we have
8677
// already started secondary init. In this case, just immediately initialize.
@@ -105,17 +96,22 @@ void ClusterManagerInitHelper::removeCluster(ClusterManagerCluster& cluster) {
10596

10697
// There is a remote edge case where we can remove a cluster via CDS that has not yet been
10798
// initialized. When called via the remove cluster API this code catches that case.
108-
std::list<ClusterManagerCluster*>* cluster_list;
99+
absl::flat_hash_map<std::string, ClusterManagerCluster*>* cluster_map;
109100
if (cluster.cluster().initializePhase() == Cluster::InitializePhase::Primary) {
110-
cluster_list = &primary_init_clusters_;
101+
cluster_map = &primary_init_clusters_;
111102
} else {
112103
ASSERT(cluster.cluster().initializePhase() == Cluster::InitializePhase::Secondary);
113-
cluster_list = &secondary_init_clusters_;
104+
cluster_map = &secondary_init_clusters_;
114105
}
115106

116107
// It is possible that the cluster we are removing has already been initialized, and is not
117-
// present in the initializer list. If so, this is fine.
118-
cluster_list->remove(&cluster);
108+
// present in the initializer map. If so, this is fine as a CDS update may happen for a
109+
// cluster with the same name. See the case "UpdateAlreadyInitialized" of the
110+
// target //test/common/upstream:cluster_manager_impl_test.
111+
auto iter = cluster_map->find(cluster.cluster().info()->name());
112+
if (iter != cluster_map->end() && iter->second == &cluster) {
113+
cluster_map->erase(iter);
114+
}
119115
ENVOY_LOG(debug, "cm init: init complete: cluster={} primary={} secondary={}",
120116
cluster.cluster().info()->name(), primary_init_clusters_.size(),
121117
secondary_init_clusters_.size());
@@ -124,13 +120,13 @@ void ClusterManagerInitHelper::removeCluster(ClusterManagerCluster& cluster) {
124120

125121
void ClusterManagerInitHelper::initializeSecondaryClusters() {
126122
started_secondary_initialize_ = true;
127-
// Cluster::initialize() method can modify the list of secondary_init_clusters_ to remove
123+
// Cluster::initialize() method can modify the map of secondary_init_clusters_ to remove
128124
// the item currently being initialized, so we eschew range-based-for and do this complicated
129125
// dance to increment the iterator before calling initialize.
130126
for (auto iter = secondary_init_clusters_.begin(); iter != secondary_init_clusters_.end();) {
131-
ClusterManagerCluster* cluster = *iter;
127+
ClusterManagerCluster* cluster = iter->second;
128+
ENVOY_LOG(debug, "initializing secondary cluster {}", iter->first);
132129
++iter;
133-
ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->cluster().info()->name());
134130
cluster->cluster().initialize([cluster, this] { onClusterInit(*cluster); });
135131
}
136132
}

source/common/upstream/cluster_manager_impl.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ class ClusterManagerInitHelper : Logger::Loggable<Logger::Id::upstream> {
188188
CdsApi* cds_{};
189189
ClusterManager::PrimaryClustersReadyCallback primary_clusters_initialized_callback_;
190190
ClusterManager::InitializationCompleteCallback initialized_callback_;
191-
std::list<ClusterManagerCluster*> primary_init_clusters_;
192-
std::list<ClusterManagerCluster*> secondary_init_clusters_;
191+
absl::flat_hash_map<std::string, ClusterManagerCluster*> primary_init_clusters_;
192+
absl::flat_hash_map<std::string, ClusterManagerCluster*> secondary_init_clusters_;
193193
State state_{State::Loading};
194194
bool started_secondary_initialize_{};
195195
};

test/common/upstream/cluster_manager_impl_test.cc

+7
Original file line numberDiff line numberDiff line change
@@ -3318,6 +3318,13 @@ TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) {
33183318
init_helper_.startInitializingSecondaryClusters();
33193319
}
33203320

3321+
TEST_F(ClusterManagerInitHelperTest, RemoveUnknown) {
3322+
InSequence s;
3323+
3324+
NiceMock<MockClusterManagerCluster> cluster;
3325+
init_helper_.removeCluster(cluster);
3326+
}
3327+
33213328
// If secondary clusters initialization triggered outside of CdsApiImpl::onConfigUpdate()'s
33223329
// callback flows, sending ClusterLoadAssignment should not be paused before calling
33233330
// ClusterManagerInitHelper::maybeFinishInitialize(). This case tests that

0 commit comments

Comments
 (0)