summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIlia Shakhov <[email protected]>2026-07-20 14:58:52 +0300
committerGitHub <[email protected]>2026-07-20 14:58:52 +0300
commitb92b4bf768c6e39fae76cdd05097e1a00a0fc248 (patch)
treeebb656fc5092b0d361b50fe7187ae25680466835
parent5790bd6cf50cc074ab342c5e537fd0bcdce685d4 (diff)
Fix bugs when onlyAliveDynamicNodes is false in DynamicNameserver (#46323)
Co-authored-by: Ilia Shakhov <[email protected]>
-rw-r--r--ydb/core/mind/dynamic_nameserver.cpp31
-rw-r--r--ydb/core/mind/dynamic_nameserver_impl.h6
-rw-r--r--ydb/core/mind/dynamic_nameserver_mon.cpp4
-rw-r--r--ydb/core/mind/lease_holder.cpp9
-rw-r--r--ydb/core/mind/node_broker.cpp23
-rw-r--r--ydb/core/mind/node_broker_impl.h1
-rw-r--r--ydb/core/mind/node_broker_ut.cpp186
-rw-r--r--ydb/core/protos/counters_node_broker.proto1
-rw-r--r--ydb/core/protos/node_broker.proto9
-rw-r--r--ydb/library/actors/core/interconnect.h6
10 files changed, 253 insertions, 23 deletions
diff --git a/ydb/core/mind/dynamic_nameserver.cpp b/ydb/core/mind/dynamic_nameserver.cpp
index c23a3a79aa2..a30a975ccc1 100644
--- a/ydb/core/mind/dynamic_nameserver.cpp
+++ b/ydb/core/mind/dynamic_nameserver.cpp
@@ -291,8 +291,9 @@ void TDynamicNameserver::ReplaceNameserverSetup(TIntrusivePtr<TTableNameserverSe
if (StaticConfig->StaticNodeTable != newStaticConfig->StaticNodeTable) {
StaticConfig = std::move(newStaticConfig);
InvalidateListNodesCache();
- for (const auto& subscriber : StaticNodeChangeSubscribers) {
- TActivationContext::Send(new IEventHandle(SelfId(), subscriber, new TEvInterconnect::TEvListNodes));
+ for (const auto& [subscriber, onlyAliveDynamicNodes] : StaticNodeChangeSubscribers) {
+ TActivationContext::Send(new IEventHandle(SelfId(), subscriber,
+ new TEvInterconnect::TEvListNodes(false, onlyAliveDynamicNodes)));
}
UpdateCounters();
}
@@ -403,10 +404,10 @@ void TDynamicNameserver::InvalidateListNodesCache()
ListNodesCacheAlive->Invalidate();
}
-void TDynamicNameserver::SendNodesList(TActorId recipient, bool onlyAliveNodes, const TActorContext &ctx)
+void TDynamicNameserver::SendNodesList(TActorId recipient, bool onlyAliveDynamicNodes, const TActorContext &ctx)
{
auto now = ctx.Now();
- auto& cache = onlyAliveNodes ? ListNodesCacheAlive : ListNodesCacheAll;
+ auto& cache = onlyAliveDynamicNodes ? ListNodesCacheAlive : ListNodesCacheAll;
if (cache->NeedUpdate(now)) {
auto newNodes = MakeIntrusive<TIntrusiveVector<TEvInterconnect::TNodeInfo>>();
@@ -440,10 +441,14 @@ void TDynamicNameserver::SendNodesList(TActorId recipient, bool onlyAliveNodes,
for (auto &config : DynamicConfigs) {
for (auto &pr : config->DynamicNodes) {
if (EnableLongLease) {
- if (onlyAliveNodes && pr.second.Liveness != ENodeLiveness::Alive) {
- continue; // dead nodes are not included in alive-only list
+ if (onlyAliveDynamicNodes) {
+ if (pr.second.Liveness != ENodeLiveness::Alive) {
+ continue; // dead nodes are not included in alive-only list
+ }
}
- } else if (pr.second.Expire <= now) {
+ }
+
+ if (pr.second.EffectiveExpire(EnableLongLease) <= now) {
continue; // expired nodes are not included
}
@@ -475,8 +480,8 @@ void TDynamicNameserver::SendNodesList(TActorId recipient, bool onlyAliveNodes,
void TDynamicNameserver::SendNodesList(const TActorContext &ctx)
{
- for (auto &[sender, onlyAliveNodes] : ListNodesQueue) {
- SendNodesList(sender, onlyAliveNodes, ctx);
+ for (auto &[sender, onlyAliveDynamicNodes] : ListNodesQueue) {
+ SendNodesList(sender, onlyAliveDynamicNodes, ctx);
}
ListNodesQueue.clear();
}
@@ -627,10 +632,10 @@ void TDynamicNameserver::Handle(TEvInterconnect::TEvListNodes::TPtr &ev,
YDB_LOG_DEBUG("TDynamicNameserver::Handle TEvInterconnect::TEvListNodes",
{"eventString", ev->Get()->ToString()});
- const bool onlyAliveNodes = ev->Get()->OnlyAliveNodes;
+ const bool onlyAliveDynamicNodes = ev->Get()->OnlyAliveDynamicNodes;
if (ProtocolState == EProtocolState::Connecting) {
- SendNodesList(ev->Sender, onlyAliveNodes, ctx);
+ SendNodesList(ev->Sender, onlyAliveDynamicNodes, ctx);
} else {
if (ListNodesQueue.empty()) {
auto dinfo = AppData(ctx)->DomainsInfo;
@@ -646,11 +651,11 @@ void TDynamicNameserver::Handle(TEvInterconnect::TEvListNodes::TPtr &ev,
PendingRequests.Set(domain);
}
}
- ListNodesQueue.emplace_back(ev->Sender, onlyAliveNodes);
+ ListNodesQueue.emplace_back(ev->Sender, onlyAliveDynamicNodes);
}
if (ev->Get()->SubscribeToStaticNodeChanges) {
- StaticNodeChangeSubscribers.insert(ev->Sender);
+ StaticNodeChangeSubscribers[ev->Sender] = onlyAliveDynamicNodes;
}
}
diff --git a/ydb/core/mind/dynamic_nameserver_impl.h b/ydb/core/mind/dynamic_nameserver_impl.h
index d403d01d259..c62988bbe49 100644
--- a/ydb/core/mind/dynamic_nameserver_impl.h
+++ b/ydb/core/mind/dynamic_nameserver_impl.h
@@ -247,7 +247,7 @@ private:
const TActorContext &ctx);
void ResolveStaticNode(ui32 nodeId, TActorId sender, TMonotonic deadline, const TActorContext &ctx);
void ResolveDynamicNode(ui32 nodeId, TAutoPtr<IEventHandle> ev, TMonotonic deadline, const TActorContext &ctx);
- void SendNodesList(TActorId recipient, bool onlyAliveNodes, const TActorContext &ctx);
+ void SendNodesList(TActorId recipient, bool onlyAliveDynamicNodes, const TActorContext &ctx);
void SendNodesList(const TActorContext &ctx);
void InvalidateListNodesCache();
void PendingRequestAnswered(ui32 domain, const TActorContext &ctx);
@@ -296,7 +296,9 @@ private:
// Domain -> Epoch ID.
THashMap<ui32, ui64> EpochUpdates;
ui32 ResolvePoolId;
- THashSet<TActorId> StaticNodeChangeSubscribers;
+ // Subscriber -> its OnlyAliveDynamicNodes preference, so re-notifications
+ // respect the filter the subscriber originally requested.
+ THashMap<TActorId, bool> StaticNodeChangeSubscribers;
bool SubscribedToConsoleNSConfig = false;
bool EnableLongLease = false;
diff --git a/ydb/core/mind/dynamic_nameserver_mon.cpp b/ydb/core/mind/dynamic_nameserver_mon.cpp
index 36267c13d88..cf55a1c1f61 100644
--- a/ydb/core/mind/dynamic_nameserver_mon.cpp
+++ b/ydb/core/mind/dynamic_nameserver_mon.cpp
@@ -215,6 +215,10 @@ void TDynamicNameserver::Handle(NMon::TEvHttpInfo::TPtr &ev, const TActorContext
<< " <td class='right-align'>Protocol state:</td>" << Endl
<< " <td>" << ToString(ProtocolState) << "</td>" << Endl
<< " </tr>" << Endl
+ << " <tr>" << Endl
+ << " <td class='right-align'>EnableNodeBrokerLongLease:</td>" << Endl
+ << " <td>" << (EnableLongLease ? "true" : "false") << "</td>" << Endl
+ << " </tr>" << Endl
<< " </tbody>" << Endl
<< "</table></div>" << Endl;
diff --git a/ydb/core/mind/lease_holder.cpp b/ydb/core/mind/lease_holder.cpp
index 4dd60552cd4..2678358486f 100644
--- a/ydb/core/mind/lease_holder.cpp
+++ b/ydb/core/mind/lease_holder.cpp
@@ -2,6 +2,7 @@
#include "node_broker.h"
#include <ydb/core/base/appdata.h>
+#include <ydb/core/base/counters.h>
#include <ydb/core/base/tablet_pipe.h>
#include <ydb/core/cms/console/configs_dispatcher.h>
#include <ydb/core/cms/console/console.h>
@@ -63,6 +64,9 @@ public:
EnableLongLease = AppData()->FeatureFlags.GetEnableNodeBrokerLongLease();
+ auto counters = GetServiceCounters(AppData(ctx)->Counters, "utils")->GetSubgroup("component", "lease_holder");
+ PingErrorsCounter = counters->GetCounter("PingErrors", true);
+
const ui32 featureFlagsItem = NKikimrConsole::TConfigItem::FeatureFlagsItem;
Send(NConsole::MakeConfigsDispatcherID(SelfId().NodeId()),
new NConsole::TEvConfigsDispatcher::TEvSetConfigSubscriptionRequest(featureFlagsItem));
@@ -141,6 +145,7 @@ private:
{
NTabletPipe::CloseClient(ctx, NodeBrokerPipe);
NodeBrokerPipe = TActorId();
+ PingErrorsCounter->Inc();
Ping(ctx);
}
@@ -185,6 +190,7 @@ private:
if (rec.GetStatus().GetCode() != NKikimrNodeBroker::TStatus::OK) {
YDB_LOG_ERROR_CTX(ctx, "TLeaseHolder::Handle TEvNodeBroker::TEvExtendLeaseResponse: cannot extend lease",
{"reason", rec.GetStatus().GetReason().data()});
+ PingErrorsCounter->Inc();
return;
}
@@ -234,7 +240,7 @@ private:
<< "Epoch end: " << ToString(EpochEnd) << Endl
<< "Next epoch end: " << ToString(NextEpochEnd) << Endl
<< "Next ping at: " << ToString(NextPing) << Endl
- << "Enable long lease: " << EnableLongLease << Endl
+ << "EnableNodeBrokerLongLease: " << (EnableLongLease ? "true" : "false") << Endl
<< "Expire: " << ToString(Expire) << Endl
<< "ExpireV2: " << ToString(ExpireV2) << Endl;
}
@@ -289,6 +295,7 @@ private:
TInstant Expire;
TInstant ExpireV2;
NActors::TSchedulerCookieHolder ExpireCookieHolder;
+ ::NMonitoring::TDynamicCounters::TCounterPtr PingErrorsCounter;
};
IActor *CreateLeaseHolder(TInstant expire)
diff --git a/ydb/core/mind/node_broker.cpp b/ydb/core/mind/node_broker.cpp
index 44851258486..ac41e0902c6 100644
--- a/ydb/core/mind/node_broker.cpp
+++ b/ydb/core/mind/node_broker.cpp
@@ -151,6 +151,8 @@ bool TNodeBroker::OnRenderAppHtmlPage(NMon::TEvRemoteHttpInfo::TPtr ev,
<< " MaxStaticNodeId: " << MaxStaticId << Endl
<< " MaxDynamicNodeId: " << MaxDynamicId << Endl
<< " EpochDuration: " << Committed.EpochDuration << Endl
+ << " LeaseDuration: " << Committed.LeaseDuration << Endl
+ << " EnableNodeBrokerLongLease: " << (EnableLongLease ? "true" : "false") << Endl
<< " StableNodeNamePrefix: " << Committed.StableNodeNamePrefix << Endl
<< " BannedIds:";
for (auto &pr : Committed.BannedIds)
@@ -232,6 +234,7 @@ void TNodeBroker::TState::ClearState()
Nodes.clear();
ExpiredNodes.clear();
RemovedNodes.clear();
+ DeadNodesCount = 0;
Hosts.clear();
RecomputeFreeIds();
@@ -295,6 +298,9 @@ void TNodeBroker::TState::AddNode(const TNodeInfo &info)
}
Hosts.emplace(std::make_tuple(info.Host, info.Address, info.Port), info.NodeId);
Nodes.emplace(info.NodeId, info);
+ if (info.Liveness == ENodeLiveness::Dead) {
+ ++DeadNodesCount;
+ }
break;
case ENodeState::Expired:
YDB_LOG_DEBUG_CTX(TActorContext::AsActorContext(), "TNodeBroker::AddNode: added expired node",
@@ -329,6 +335,9 @@ void TNodeBroker::TState::ExtendLease(TNodeInfo &node)
node.ExpireV2 = Epoch.NextEnd + LeaseDuration;
node.AliveUntil = Epoch.NextEnd;
+ if (node.Liveness == ENodeLiveness::Dead) {
+ --DeadNodesCount;
+ }
node.Liveness = ENodeLiveness::Alive;
YDB_LOG_DEBUG_CTX(TActorContext::AsActorContext(), "TNodeBroker::ExtendLease: extended node lease",
@@ -348,6 +357,9 @@ void TNodeBroker::TState::FixNodeId(TNodeInfo &node)
node.ExpireV2 = TInstant::Max();
node.AliveUntil = TInstant::Max();
+ if (node.Liveness == ENodeLiveness::Dead) {
+ --DeadNodesCount;
+ }
node.Liveness = ENodeLiveness::Alive;
YDB_LOG_DEBUG_CTX(TActorContext::AsActorContext(), "TNodeBroker::FixNodeId: fixed node ID",
@@ -506,7 +518,7 @@ void TNodeBroker::FillNodeInfo(const TNodeInfo &node,
info.SetAddress(node.Address);
info.SetExpire(node.Expire.GetValue());
info.SetExpireV2(node.ExpireV2.GetValue());
- info.SetLiveness(static_cast<ui32>(node.Liveness));
+ info.SetLiveness(static_cast<NKikimrNodeBroker::ELiveness>(node.Liveness));
node.Location.Serialize(info.MutableLocation(), false);
FillNodeName(node.SlotIndex, info);
}
@@ -559,6 +571,9 @@ void TNodeBroker::TState::ApplyStateDiff(const TStateDiff &diff)
{"nodeId", it->second.IdString()});
Hosts.erase(std::make_tuple(it->second.Host, it->second.Address, it->second.Port));
+ if (it->second.Liveness == ENodeLiveness::Dead) {
+ --DeadNodesCount;
+ }
it->second.State = ENodeState::Expired;
it->second.Version = diff.NewEpoch.Version;
ExpiredNodes.emplace(id, std::move(it->second));
@@ -572,6 +587,9 @@ void TNodeBroker::TState::ApplyStateDiff(const TStateDiff &diff)
LOG_DEBUG_S(TActorContext::AsActorContext(), NKikimrServices::NODE_BROKER,
LogPrefix() << " Node " << it->second.IdString() << " is marked as dead");
+ if (it->second.Liveness != ENodeLiveness::Dead) {
+ ++DeadNodesCount;
+ }
it->second.Liveness = ENodeLiveness::Dead;
it->second.Version = diff.NewEpoch.Version;
}
@@ -843,6 +861,7 @@ void TNodeBroker::UpdateCommittedStateCounters() {
TabletCounters->Simple()[COUNTER_ACTIVE_NODES].Set(Committed.Nodes.size());
TabletCounters->Simple()[COUNTER_EXPIRED_NODES].Set(Committed.ExpiredNodes.size());
TabletCounters->Simple()[COUNTER_REMOVED_NODES].Set(Committed.RemovedNodes.size());
+ TabletCounters->Simple()[COUNTER_DEAD_NODES].Set(Committed.DeadNodesCount);
TabletCounters->Simple()[COUNTER_EPOCH_VERSION].Set(Committed.Epoch.Version);
}
@@ -1954,7 +1973,7 @@ TNodeInfoSchema TNodeBroker::TNodeInfo::SerializeToSchema() const {
serialized.SetExpire(Expire.MicroSeconds());
serialized.SetExpireV2(ExpireV2.MicroSeconds());
serialized.SetAliveUntil(AliveUntil.MicroSeconds());
- serialized.SetLiveness(static_cast<ui32>(Liveness));
+ serialized.SetLiveness(static_cast<NKikimrNodeBroker::ELiveness>(Liveness));
Location.Serialize(serialized.MutableLocation(), false);
serialized.MutableServicedSubDomain()->CopyFrom(ServicedSubDomain);
if (SlotIndex.has_value()) {
diff --git a/ydb/core/mind/node_broker_impl.h b/ydb/core/mind/node_broker_impl.h
index b932a7d0337..4d5fe31818c 100644
--- a/ydb/core/mind/node_broker_impl.h
+++ b/ydb/core/mind/node_broker_impl.h
@@ -404,6 +404,7 @@ private:
THashMap<ui32, TNodeInfo> Nodes;
THashMap<ui32, TNodeInfo> ExpiredNodes;
THashMap<ui32, TNodeInfo> RemovedNodes;
+ ui64 DeadNodesCount = 0;
// Maps <Host/Addr:Port> to NodeID.
THashMap<std::tuple<TString, TString, ui16>, ui32> Hosts;
// Bitmap with free Node IDs (with no lower 5 bits).
diff --git a/ydb/core/mind/node_broker_ut.cpp b/ydb/core/mind/node_broker_ut.cpp
index 41c7db5a91b..120bfc19d40 100644
--- a/ydb/core/mind/node_broker_ut.cpp
+++ b/ydb/core/mind/node_broker_ut.cpp
@@ -19,6 +19,7 @@
#include <ydb/core/blobstorage/pdisk/blobstorage_pdisk_tools.h>
#include <ydb/core/protos/schemeshard/operations.pb.h>
#include <ydb/core/protos/tx_proxy.pb.h>
+#include <ydb/core/protos/blobstorage_distributed_config.pb.h>
#include <ydb/core/tablet_flat/shared_cache_events.h>
#include <ydb/core/tablet_flat/shared_sausagecache.h>
#include <ydb/core/tx/schemeshard/schemeshard.h>
@@ -1053,6 +1054,50 @@ THashSet<ui32> GetNameserverDynamicNodeIds(TTestActorRuntime &runtime, TActorId
return ids;
}
+// Subscribe to static node table changes with the given onlyAliveDynamicNodes
+// preference. The nameservice remembers the preference per subscriber and
+// re-notifies with a fresh nodes list (respecting the filter) whenever the
+// static node table changes.
+void SubscribeToStaticNodeChanges(TTestActorRuntime &runtime, TActorId subscriber,
+ bool onlyAliveDynamicNodes)
+{
+ runtime.Send(new IEventHandle(GetNameserviceActorId(), subscriber,
+ new TEvInterconnect::TEvListNodes(/* subscribeToStaticNodeChanges */ true,
+ onlyAliveDynamicNodes)));
+}
+
+// Grab the next nodes-list event delivered to a specific subscriber and return
+// the set of dynamic (non-static) node ids in it. Used to observe the list the
+// nameservice pushes to static-node-change subscribers.
+THashSet<ui32> GrabNameserverDynamicNodeIds(TTestActorRuntime &runtime, TActorId subscriber)
+{
+ ui32 maxStaticNodeId = runtime.GetAppData().DynamicNameserviceConfig->MaxStaticNodeId;
+ auto ev = runtime.GrabEdgeEventRethrow<TEvInterconnect::TEvNodesInfo>(subscriber);
+ THashSet<ui32> ids;
+ for (auto &node : ev->Get()->Nodes)
+ if (node.NodeId > maxStaticNodeId)
+ ids.insert(node.NodeId);
+ return ids;
+}
+
+// Push a new static node table to the node-0 nameservice as if the node warden
+// delivered a self-managed storage config. When the table differs from the
+// current one the nameservice invalidates its caches and re-notifies every
+// static-node-change subscriber.
+void SetNameserverStaticNodes(TTestActorRuntime &runtime, TActorId sender,
+ const TVector<ui32> &staticNodeIds)
+{
+ auto config = std::make_shared<NKikimrBlobStorage::TStorageConfig>();
+ for (ui32 nodeId : staticNodeIds) {
+ auto *node = config->AddAllNodes();
+ node->SetNodeId(nodeId);
+ node->SetHost(Sprintf("static%u.local", nodeId));
+ node->SetPort(19000 + nodeId);
+ }
+ runtime.Send(new IEventHandle(GetNameserviceActorId(), sender,
+ new TEvNodeWardenStorageConfig(config, /* selfManagementEnabled */ true, nullptr)));
+}
+
void GetNameserverNodesList(TTestActorRuntime &runtime,
TActorId sender,
THashMap<ui32, TEvInterconnect::TNodeInfo> &nodes,
@@ -5395,6 +5440,147 @@ Y_UNIT_TEST_SUITE(TDynamicNameserverTest) {
UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
UNIT_ASSERT(!GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
}
+
+ Y_UNIT_TEST(OnlyAliveDynamicNodesFiltersDeadNodes)
+ {
+ TTestBasicRuntime runtime(8, false);
+ Setup(runtime, 4, {}, false, /* enableNodeBrokerLongLease */ true);
+ TActorId sender = runtime.AllocateEdgeActor();
+
+ // Lease duration equal to a single (default) epoch: a node that stops
+ // pinging becomes Dead two epochs later (see
+ // TNodeBrokerTest::LongLeaseNodeBecomesDeadThenExpires).
+ SetLeaseDuration(runtime, sender, TDuration::Minutes(5));
+
+ // Register two nodes; both start Alive.
+ CheckRegistration(runtime, sender, "host1", 1001, "host1.host1.host1", "1.2.3.4",
+ 1, 2, 3, 4, TStatus::OK, NODE1);
+ CheckRegistration(runtime, sender, "host2", 1001, "host2.host2.host2", "1.2.3.5",
+ 1, 2, 3, 5, TStatus::OK, NODE2);
+
+ // Keep NODE1 pinging every epoch so it stays Alive, while NODE2 goes
+ // silent and becomes Dead after two epochs.
+ auto epoch = WaitForEpochUpdate(runtime, sender);
+ CheckLeaseExtension(runtime, sender, NODE1, TStatus::OK, epoch);
+ epoch = WaitForEpochUpdate(runtime, sender);
+ CheckLeaseExtension(runtime, sender, NODE1, TStatus::OK, epoch);
+
+ CheckNodeLiveness(runtime, sender, NODE1, ENodeLiveness::Alive);
+ CheckNodeLiveness(runtime, sender, NODE2, ENodeLiveness::Dead);
+
+ // The full list contains both nodes; the alive-only list serves the
+ // Alive node and omits the Dead one (its own cache is built separately
+ // from the full-list cache).
+ auto all = GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false);
+ UNIT_ASSERT(all.contains(NODE1));
+ UNIT_ASSERT(all.contains(NODE2));
+
+ auto alive = GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true);
+ UNIT_ASSERT(alive.contains(NODE1));
+ UNIT_ASSERT(!alive.contains(NODE2));
+ UNIT_ASSERT_VALUES_EQUAL(alive.size(), 1);
+ }
+
+ Y_UNIT_TEST(OnlyAliveDynamicNodesNoopWhenLongLeaseDisabled)
+ {
+ TTestBasicRuntime runtime(8, false);
+ Setup(runtime, 4, {}, false, /* enableNodeBrokerLongLease */ false);
+ TActorId sender = runtime.AllocateEdgeActor();
+
+ SetLeaseDuration(runtime, sender, TDuration::Minutes(5));
+
+ CheckRegistration(runtime, sender, "host1", 1001, "host1.host1.host1", "1.2.3.4",
+ 1, 2, 3, 4, TStatus::OK, NODE1);
+
+ // With the long lease feature disabled there is no Dead state and the
+ // onlyAliveDynamicNodes filter is a no-op: a registered, not-yet-expired
+ // node is served in both the full and the alive-only lists.
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
+
+ // The node stops pinging but survives the current epoch, still in both lists.
+ WaitForEpochUpdate(runtime, sender);
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
+
+ // It then expires (by Expire, never becoming Dead) and drops out of both
+ // lists identically.
+ WaitForEpochUpdate(runtime, sender);
+ UNIT_ASSERT(!GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
+ UNIT_ASSERT(!GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
+ }
+
+ Y_UNIT_TEST(OnlyAliveDynamicNodesRevivedByRegistration)
+ {
+ TTestBasicRuntime runtime(8, false);
+ Setup(runtime, 4, {}, false, /* enableNodeBrokerLongLease */ true);
+ TActorId sender = runtime.AllocateEdgeActor();
+
+ SetLeaseDuration(runtime, sender, TDuration::Minutes(5));
+
+ CheckRegistration(runtime, sender, "host1", 1001, "host1.host1.host1", "1.2.3.4",
+ 1, 2, 3, 4, TStatus::OK, NODE1);
+
+ // Let the node become Dead (but not expired).
+ WaitForEpochUpdate(runtime, sender);
+ WaitForEpochUpdate(runtime, sender);
+ CheckNodeLiveness(runtime, sender, NODE1, ENodeLiveness::Dead);
+
+ // The dead node is hidden from the alive-only list but still in the full one.
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
+ UNIT_ASSERT(!GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
+
+ // Re-registration revives the node (back to Alive), so it must reappear
+ // in the alive-only list once the nameserver refreshes its cache.
+ CheckRegistration(runtime, sender, "host1", 1001, "host1.host1.host1", "1.2.3.4",
+ 1, 2, 3, 4, TStatus::OK, NODE1);
+ CheckNodeLiveness(runtime, sender, NODE1, ENodeLiveness::Alive);
+
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ false).contains(NODE1));
+ UNIT_ASSERT(GetNameserverDynamicNodeIds(runtime, sender, /* onlyAlive */ true).contains(NODE1));
+ }
+
+ Y_UNIT_TEST(OnlyAliveDynamicNodesStaticNodeChangeSubscribers)
+ {
+ TTestBasicRuntime runtime(8, false);
+ Setup(runtime, 4, {}, false, /* enableNodeBrokerLongLease */ true);
+ TActorId sender = runtime.AllocateEdgeActor();
+
+ SetLeaseDuration(runtime, sender, TDuration::Minutes(5));
+
+ // Register a node and drive it into the Dead-but-not-expired window, so
+ // the alive-only and full lists disagree on whether it is served.
+ CheckRegistration(runtime, sender, "host1", 1001, "host1.host1.host1", "1.2.3.4",
+ 1, 2, 3, 4, TStatus::OK, NODE1);
+ WaitForEpochUpdate(runtime, sender);
+ WaitForEpochUpdate(runtime, sender);
+ CheckNodeLiveness(runtime, sender, NODE1, ENodeLiveness::Dead);
+
+ // Establish a known static node table baseline.
+ SetNameserverStaticNodes(runtime, sender, {1, 2});
+
+ // Two subscribers to static node changes with opposite
+ // onlyAliveDynamicNodes preferences.
+ TActorId subAll = runtime.AllocateEdgeActor();
+ TActorId subAlive = runtime.AllocateEdgeActor();
+ SubscribeToStaticNodeChanges(runtime, subAll, /* onlyAlive */ false);
+ SubscribeToStaticNodeChanges(runtime, subAlive, /* onlyAlive */ true);
+
+ // The initial list responses already respect each subscriber's filter:
+ // the full-list subscriber sees the dead node, the alive-only one does not.
+ UNIT_ASSERT(GrabNameserverDynamicNodeIds(runtime, subAll).contains(NODE1));
+ UNIT_ASSERT(!GrabNameserverDynamicNodeIds(runtime, subAlive).contains(NODE1));
+
+ // Change the static node table; both subscribers must be re-notified.
+ SetNameserverStaticNodes(runtime, sender, {1, 2, 3});
+
+ // Each re-notification must respect the onlyAliveDynamicNodes preference
+ // the subscriber originally requested (stored in StaticNodeChangeSubscribers):
+ // the dead node stays in the full-list subscriber's view and is hidden
+ // from the alive-only subscriber's view.
+ UNIT_ASSERT(GrabNameserverDynamicNodeIds(runtime, subAll).contains(NODE1));
+ UNIT_ASSERT(!GrabNameserverDynamicNodeIds(runtime, subAlive).contains(NODE1));
+ }
}
Y_UNIT_TEST_SUITE(TSlotIndexesPoolTest) {
diff --git a/ydb/core/protos/counters_node_broker.proto b/ydb/core/protos/counters_node_broker.proto
index d3c774208c0..1b87c6be705 100644
--- a/ydb/core/protos/counters_node_broker.proto
+++ b/ydb/core/protos/counters_node_broker.proto
@@ -14,6 +14,7 @@ enum ESimpleCounters {
COUNTER_ACTIVE_NODES = 4 [(CounterOpts) = {Name: "ActiveNodes"}];
COUNTER_EXPIRED_NODES = 5 [(CounterOpts) = {Name: "ExpiredNodes"}];
COUNTER_REMOVED_NODES = 6 [(CounterOpts) = {Name: "RemovedNodes"}];
+ COUNTER_DEAD_NODES = 7 [(CounterOpts) = {Name: "DeadNodes"}];
}
enum ECumulativeCounters {
diff --git a/ydb/core/protos/node_broker.proto b/ydb/core/protos/node_broker.proto
index 4240a396d9b..6c82e332e15 100644
--- a/ydb/core/protos/node_broker.proto
+++ b/ydb/core/protos/node_broker.proto
@@ -13,6 +13,11 @@ message TListNodes {
}
}
+enum ELiveness {
+ ALIVE = 0;
+ DEAD = 1;
+}
+
message TNodeInfo {
optional uint32 NodeId = 1;
optional string Host = 2;
@@ -23,7 +28,7 @@ message TNodeInfo {
optional uint64 Expire = 7;
optional uint64 ExpireV2 = 9;
optional string Name = 8;
- optional uint32 Liveness = 10;
+ optional ELiveness Liveness = 10;
}
message TNodeInfoSchema {
@@ -39,7 +44,7 @@ message TNodeInfoSchema {
optional bool AuthorizedByCertificate = 10;
optional uint64 ExpireV2 = 11;
optional uint64 AliveUntil = 12;
- optional uint32 Liveness = 13;
+ optional ELiveness Liveness = 13;
}
message TVersionInfo {
diff --git a/ydb/library/actors/core/interconnect.h b/ydb/library/actors/core/interconnect.h
index 33a99a62669..9291fe6d439 100644
--- a/ydb/library/actors/core/interconnect.h
+++ b/ydb/library/actors/core/interconnect.h
@@ -211,13 +211,13 @@ namespace NActors {
struct TEvListNodes: public TEventLocal<TEvListNodes, EvListNodes> {
const bool SubscribeToStaticNodeChanges = false;
- const bool OnlyAliveNodes = true;
+ const bool OnlyAliveDynamicNodes = true;
TEvListNodes() = default;
- TEvListNodes(bool subscribeToStaticNodeChanges, bool onlyAliveNodes = true)
+ TEvListNodes(bool subscribeToStaticNodeChanges, bool onlyAliveDynamicNodes = true)
: SubscribeToStaticNodeChanges(subscribeToStaticNodeChanges)
- , OnlyAliveNodes(onlyAliveNodes)
+ , OnlyAliveDynamicNodes(onlyAliveDynamicNodes)
{}
};