Skip to content

Commit daa4667

Browse files
committed
dynamic_modules: per-cluster persistent cross-priority host map via cluster ABI
The cross-priority host map rebuilds itself with a full O(N) make_shared<HostMap> copy on every membership delta. MainPrioritySetImpl can now back it with an immer persistent map so a delta is applied in O(delta), selected per cluster via setUsePersistentCrossPriorityHostMap(). crossPriorityHostMap() returns an abstract HostLookupTable backed by either the legacy flat map (the default, preserving the exact prior behavior) or the immer map, and updateDynamicHostList takes a host-lookup functor so it stays backing-agnostic. Dynamic-modules clusters opt in through the new envoy_dynamic_module_callback_cluster_use_persistent_host_map ABI callback (the DynamicModuleCluster::setUsePersistentHostMap forwarder) and the Rust SDK EnvoyCluster::set_use_persistent_host_map method. Membership behavior is identical under either backing. Signed-off-by: Rohit Agrawal <rohit.agrawal@databricks.com>
1 parent 1a0b8b5 commit daa4667

41 files changed

Lines changed: 984 additions & 171 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bazel/deps.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,17 @@ fp16:
939939
extensions:
940940
- envoy.wasm.runtime.v8
941941

942+
immer:
943+
project_name: "immer"
944+
project_desc: "Header-only library of persistent and immutable data structures for C++"
945+
project_url: "https://github.com/arximboldi/immer"
946+
release_date: "2026-01-13"
947+
use_category:
948+
- dataplane_core
949+
cpe: "N/A"
950+
license: "BSL-1.0"
951+
license_url: "https://github.com/arximboldi/immer/blob/v{version}/LICENSE"
952+
942953
googleurl:
943954
project_name: "Chrome URL parsing library"
944955
project_desc: "Chrome URL parsing library"

bazel/external/immer.BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_library")
2+
3+
licenses(["notice"]) # BSL-1.0
4+
5+
package(default_visibility = ["//visibility:public"])
6+
7+
cc_library(
8+
name = "immer",
9+
hdrs = glob(["immer/**/*.hpp"]),
10+
includes = ["."],
11+
)

bazel/repositories.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def envoy_dependencies(skip_targets = []):
211211
_highway()
212212
_dragonbox()
213213
_fp16()
214+
_immer()
214215
_simdutf()
215216
_quiche()
216217
_googleurl()
@@ -806,6 +807,12 @@ def _fp16():
806807
build_file = "@envoy//bazel/external:fp16.BUILD",
807808
)
808809

810+
def _immer():
811+
external_http_archive(
812+
name = "immer",
813+
build_file = "@envoy//bazel/external:immer.BUILD",
814+
)
815+
809816
def _simdutf():
810817
external_http_archive(
811818
name = "simdutf",

bazel/repository_locations.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,12 @@ REPOSITORY_LOCATIONS_SPEC = dict(
546546
sha256 = "e2da4f41bae8869f8dee56f4c104e699e7de3a483b5e451fda8e76fbcc66c59a",
547547
urls = ["https://github.com/Maratyszcza/FP16/archive/{version}.zip"],
548548
),
549+
immer = dict(
550+
version = "0.9.1",
551+
strip_prefix = "immer-{version}",
552+
sha256 = "b18b92ba60ec3186dc36ef671d3c2ae542cbb63eb6dc0e258476c6111a67c971",
553+
urls = ["https://github.com/arximboldi/immer/archive/v{version}.tar.gz"],
554+
),
549555
simdutf = dict(
550556
# NOTE: Update together with v8 and proxy_wasm_cpp_host.
551557
version = "8.1.0",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Added the ``envoy_dynamic_module_callback_cluster_use_persistent_host_map`` ABI callback so that a
2+
dynamic-modules cluster can opt into a persistent cross-priority host map in place of the
3+
default flat copy-on-write map, making each membership delta O(delta) instead of an O(N) flat copy.
4+
Membership behavior is identical under either backing, and the flat map remains the default. The
5+
Rust SDK exposes this as ``EnvoyCluster::set_use_persistent_host_map``.

envoy/upstream/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ envoy_cc_library(
170170
"//envoy/ssl:context_interface",
171171
"//envoy/ssl:context_manager_interface",
172172
"//envoy/upstream:types_interface",
173+
"@abseil-cpp//absl/functional:function_ref",
173174
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
174175
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
175176
],

envoy/upstream/upstream.h

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "envoy/upstream/resource_manager.h"
3030
#include "envoy/upstream/types.h"
3131

32+
#include "absl/functional/function_ref.h"
3233
#include "absl/strings/string_view.h"
3334
#include "fmt/format.h"
3435

@@ -362,6 +363,53 @@ using ExcludedHostVector = Phantom<HostVector, Excluded>;
362363
using HostMap = absl::flat_hash_map<std::string, Upstream::HostSharedPtr>;
363364
using HostMapSharedPtr = std::shared_ptr<HostMap>;
364365
using HostMapConstSharedPtr = std::shared_ptr<const HostMap>;
366+
367+
/**
368+
* Read-only host-by-address lookup published to workers. Backed by either the flat HostMap (the
369+
* default) or a persistent map, selected per cluster via
370+
* `MainPrioritySetImpl::setUsePersistentCrossPriorityHostMap`, so the backing is swappable without
371+
* changing consumers.
372+
*/
373+
class HostLookupTable {
374+
public:
375+
virtual ~HostLookupTable() = default;
376+
377+
/**
378+
* @param address the host address to look up.
379+
* @return HostSharedPtr the host for the address, or nullptr if absent.
380+
*/
381+
virtual HostSharedPtr findHost(absl::string_view address) const PURE;
382+
383+
/**
384+
* @return size_t the number of hosts in the table.
385+
*/
386+
virtual size_t size() const PURE;
387+
388+
/**
389+
* @return bool true if the table holds no hosts.
390+
*/
391+
virtual bool empty() const PURE;
392+
393+
/**
394+
* Invokes a callback for each (address, host) entry. Iteration order is unspecified.
395+
*
396+
* @param cb the callback to invoke for each entry.
397+
*/
398+
virtual void
399+
forEach(absl::FunctionRef<void(const std::string&, const HostSharedPtr&)> cb) const PURE;
400+
};
401+
using HostLookupTableConstSharedPtr = std::shared_ptr<const HostLookupTable>;
402+
403+
/**
404+
* Wraps a flat HostMap in the HostLookupTable interface. Defined in upstream_impl.cc so the
405+
* concrete type stays private. Available to tests and consumers that hold a HostMap but need to
406+
* publish a HostLookupTable.
407+
*
408+
* @param map the flat host map to wrap.
409+
* @return HostLookupTableConstSharedPtr a lookup table backed by the given map.
410+
*/
411+
HostLookupTableConstSharedPtr makeFlatHostLookupTable(HostMapConstSharedPtr map);
412+
365413
using HostVectorSharedPtr = std::shared_ptr<HostVector>;
366414
using HostVectorConstSharedPtr = std::shared_ptr<const HostVector>;
367415

@@ -590,10 +638,10 @@ class PrioritySet {
590638
virtual const std::vector<HostSetPtr>& hostSetsPerPriority() const PURE;
591639

592640
/**
593-
* @return HostMapConstSharedPtr read only cross priority host map that indexed by host address
594-
* string.
641+
* @return HostLookupTableConstSharedPtr read only cross priority host lookup table indexed by
642+
* host address string.
595643
*/
596-
virtual HostMapConstSharedPtr crossPriorityHostMap() const PURE;
644+
virtual HostLookupTableConstSharedPtr crossPriorityHostMap() const PURE;
597645

598646
/**
599647
* Parameter class for updateHosts.
@@ -627,7 +675,7 @@ class PrioritySet {
627675
const HostVector& hosts_added, const HostVector& hosts_removed,
628676
std::optional<bool> weighted_priority_health,
629677
std::optional<uint32_t> overprovisioning_factor,
630-
HostMapConstSharedPtr cross_priority_host_map = nullptr) PURE;
678+
HostLookupTableConstSharedPtr cross_priority_host_map = nullptr) PURE;
631679

632680
/**
633681
* Callback provided during batch updates that can be used to update hosts.

source/common/upstream/BUILD

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,24 @@ envoy_cc_library(
385385
],
386386
)
387387

388+
envoy_cc_library(
389+
name = "persistent_host_map_lib",
390+
srcs = ["persistent_host_map.cc"],
391+
hdrs = ["persistent_host_map.h"],
392+
# `immer`'s variable-length CHAMP nodes use a flexible array member, so the shared empty-node
393+
# singleton is constructed in a buffer smaller than `sizeof(node)`. The vptr sanitizer reads
394+
# that as a constructor call on insufficient space and reports a false positive, so `immer` is
395+
# confined to this library with vptr disabled here only. The check stays enabled elsewhere.
396+
copts = ["-fno-sanitize=vptr"],
397+
rbe_pool = "6gig",
398+
deps = [
399+
"//envoy/upstream:upstream_interface",
400+
"@abseil-cpp//absl/functional:function_ref",
401+
"@abseil-cpp//absl/strings:string_view",
402+
"@immer",
403+
],
404+
)
405+
388406
envoy_cc_library(
389407
name = "upstream_lib",
390408
srcs = ["upstream_impl.cc"],
@@ -393,6 +411,7 @@ envoy_cc_library(
393411
":cluster_factory_lib",
394412
":default_local_address_selector_factory",
395413
":health_checker_lib",
414+
":persistent_host_map_lib",
396415
# TODO(mattklein123): Move the clusters to extensions so they can be compiled out.
397416
":upstream_includes",
398417
":transport_socket_match_lib",
@@ -504,6 +523,7 @@ envoy_cc_library(
504523
"//source/extensions/upstreams/http:config",
505524
"//source/extensions/upstreams/tcp:config",
506525
"//source/server:transport_socket_config_lib",
526+
"@abseil-cpp//absl/functional:function_ref",
507527
"@abseil-cpp//absl/synchronization",
508528
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
509529
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",

source/common/upstream/cluster_manager_impl.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,8 @@ void ClusterManagerImpl::postThreadLocalClusterUpdate(ClusterManagerCluster& cm_
11941194
per_priority.overprovisioning_factor_ = host_set->overprovisioningFactor();
11951195
}
11961196

1197-
HostMapConstSharedPtr host_map = cm_cluster.cluster().prioritySet().crossPriorityHostMap();
1197+
HostLookupTableConstSharedPtr host_map =
1198+
cm_cluster.cluster().prioritySet().crossPriorityHostMap();
11981199

11991200
pending_cluster_creations_.erase(cm_cluster.cluster().info()->name());
12001201

@@ -1304,7 +1305,7 @@ void ClusterManagerImpl::postThreadLocalClusterUpdate(ClusterManagerCluster& cm_
13041305
ClusterManagerImpl::ClusterInitializationObjectConstSharedPtr
13051306
ClusterManagerImpl::addOrUpdateClusterInitializationObjectIfSupported(
13061307
const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info,
1307-
LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map,
1308+
LoadBalancerFactorySharedPtr load_balancer_factory, HostLookupTableConstSharedPtr map,
13081309
UnitFloat drop_overload, absl::string_view drop_category) {
13091310
if (!deferralIsSupportedForCluster(cluster_info)) {
13101311
return nullptr;
@@ -1386,7 +1387,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::initializeClusterInlineIfExis
13861387

13871388
ClusterManagerImpl::ClusterInitializationObject::ClusterInitializationObject(
13881389
const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info,
1389-
LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map,
1390+
LoadBalancerFactorySharedPtr load_balancer_factory, HostLookupTableConstSharedPtr map,
13901391
UnitFloat drop_overload, absl::string_view drop_category)
13911392
: cluster_info_(std::move(cluster_info)), load_balancer_factory_(load_balancer_factory),
13921393
cross_priority_host_map_(map), drop_overload_(drop_overload), drop_category_(drop_category) {
@@ -1399,7 +1400,7 @@ ClusterManagerImpl::ClusterInitializationObject::ClusterInitializationObject(
13991400
ClusterManagerImpl::ClusterInitializationObject::ClusterInitializationObject(
14001401
const absl::flat_hash_map<int, ThreadLocalClusterUpdateParams::PerPriority>& per_priority_state,
14011402
const ThreadLocalClusterUpdateParams& update_params, ClusterInfoConstSharedPtr cluster_info,
1402-
LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map,
1403+
LoadBalancerFactorySharedPtr load_balancer_factory, HostLookupTableConstSharedPtr map,
14031404
UnitFloat drop_overload, absl::string_view drop_category)
14041405
: per_priority_state_(per_priority_state), cluster_info_(std::move(cluster_info)),
14051406
load_balancer_factory_(load_balancer_factory), cross_priority_host_map_(map),
@@ -1513,7 +1514,7 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::updateHost
15131514
LocalityWeightsConstSharedPtr locality_weights, const HostVector& hosts_added,
15141515
const HostVector& hosts_removed, std::optional<bool> weighted_priority_health,
15151516
std::optional<uint32_t> overprovisioning_factor,
1516-
HostMapConstSharedPtr cross_priority_host_map) {
1517+
HostLookupTableConstSharedPtr cross_priority_host_map) {
15171518
ENVOY_LOG(debug, "membership update for TLS cluster {} added {} removed {}", name,
15181519
hosts_added.size(), hosts_removed.size());
15191520
priority_set_.updateHosts(priority, std::move(update_hosts_params), std::move(locality_weights),
@@ -1841,7 +1842,7 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::updateClusterMembership(
18411842
const std::string& name, uint32_t priority, PrioritySet::UpdateHostsParams update_hosts_params,
18421843
LocalityWeightsConstSharedPtr locality_weights, const HostVector& hosts_added,
18431844
const HostVector& hosts_removed, bool weighted_priority_health,
1844-
uint64_t overprovisioning_factor, HostMapConstSharedPtr cross_priority_host_map) {
1845+
uint64_t overprovisioning_factor, HostLookupTableConstSharedPtr cross_priority_host_map) {
18451846
ASSERT(thread_local_clusters_.find(name) != thread_local_clusters_.end());
18461847
const auto& cluster_entry = thread_local_clusters_[name];
18471848
cluster_entry->updateHosts(name, priority, std::move(update_hosts_params),

source/common/upstream/cluster_manager_impl.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -454,20 +454,20 @@ class ClusterManagerImpl : public ClusterManager,
454454
ClusterInitializationObject(const ThreadLocalClusterUpdateParams& params,
455455
ClusterInfoConstSharedPtr cluster_info,
456456
LoadBalancerFactorySharedPtr load_balancer_factory,
457-
HostMapConstSharedPtr map, UnitFloat drop_overload,
457+
HostLookupTableConstSharedPtr map, UnitFloat drop_overload,
458458
absl::string_view drop_category);
459459

460460
ClusterInitializationObject(
461461
const absl::flat_hash_map<int, ThreadLocalClusterUpdateParams::PerPriority>&
462462
per_priority_state,
463463
const ThreadLocalClusterUpdateParams& update_params, ClusterInfoConstSharedPtr cluster_info,
464-
LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map,
464+
LoadBalancerFactorySharedPtr load_balancer_factory, HostLookupTableConstSharedPtr map,
465465
UnitFloat drop_overload, absl::string_view drop_category);
466466

467467
absl::flat_hash_map<int, ThreadLocalClusterUpdateParams::PerPriority> per_priority_state_;
468468
const ClusterInfoConstSharedPtr cluster_info_;
469469
const LoadBalancerFactorySharedPtr load_balancer_factory_;
470-
const HostMapConstSharedPtr cross_priority_host_map_;
470+
const HostLookupTableConstSharedPtr cross_priority_host_map_;
471471
UnitFloat drop_overload_{0};
472472
const std::string drop_category_;
473473
};
@@ -628,7 +628,7 @@ class ClusterManagerImpl : public ClusterManager,
628628
const HostVector& hosts_added, const HostVector& hosts_removed,
629629
std::optional<bool> weighted_priority_health,
630630
std::optional<uint32_t> overprovisioning_factor,
631-
HostMapConstSharedPtr cross_priority_host_map);
631+
HostLookupTableConstSharedPtr cross_priority_host_map);
632632

633633
// Drains any connection pools associated with the removed hosts. All connections will be
634634
// closed gracefully and no new connections will be created.
@@ -720,7 +720,7 @@ class ClusterManagerImpl : public ClusterManager,
720720
LocalityWeightsConstSharedPtr locality_weights,
721721
const HostVector& hosts_added, const HostVector& hosts_removed,
722722
bool weighted_priority_health, uint64_t overprovisioning_factor,
723-
HostMapConstSharedPtr cross_priority_host_map);
723+
HostLookupTableConstSharedPtr cross_priority_host_map);
724724
void onHostHealthFailure(const HostSharedPtr& host);
725725

726726
ConnPoolsContainer* getHttpConnPoolsContainer(const HostConstSharedPtr& host,
@@ -931,7 +931,7 @@ class ClusterManagerImpl : public ClusterManager,
931931
*/
932932
ClusterInitializationObjectConstSharedPtr addOrUpdateClusterInitializationObjectIfSupported(
933933
const ThreadLocalClusterUpdateParams& params, ClusterInfoConstSharedPtr cluster_info,
934-
LoadBalancerFactorySharedPtr load_balancer_factory, HostMapConstSharedPtr map,
934+
LoadBalancerFactorySharedPtr load_balancer_factory, HostLookupTableConstSharedPtr map,
935935
UnitFloat drop_overload, absl::string_view drop_category);
936936

937937
bool deferralIsSupportedForCluster(const ClusterInfoConstSharedPtr& info) const;

0 commit comments

Comments
 (0)