Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
74d5753
Add MR_ClusterRefreshTopology for event-driven OSS topology refresh
gabsow Jun 18, 2026
f481460
MOD-16382 Consume cluster topology-change reason flags; map-only refr…
gabsow Jun 24, 2026
08698d7
MOD-16382 Decide rebuild-vs-in-place by diffing the primary set, not …
gabsow Jul 5, 2026
d7af940
MOD-16399 Skip the rebuild when CLUSTERSET carries an unchanged topology
gabsow Jul 5, 2026
6f7b604
Review round: positive naming, length-aware compare, short-form no-op…
gabsow Jul 7, 2026
b35bab1
MOD-16382 Drop the topology-refresh debounce; reconcile per event
gabsow Jul 13, 2026
d3830c1
MOD-16382 Rebuild when a known primary changes its address
gabsow Jul 13, 2026
6118f92
MOD-16382 Review: build the reconcile view from the cluster module API
gabsow Jul 13, 2026
57a2dd1
MOD-16399 Skip the rebuild when CLUSTERSET carries an unchanged topology
gabsow Jul 5, 2026
e8a3578
Review round: positive naming, length-aware compare, short-form no-op…
gabsow Jul 7, 2026
fafda41
MOD-16399 Reconcile CLUSTERSET through the shared topology mechanism
gabsow Jul 13, 2026
1b5d6c3
MOD-16399 Fix testMassiveClusterSet alternation phase (CI hang)
gabsow Jul 13, 2026
772a7f7
Merge current master into MOD-16399 branch
gabsow Jul 27, 2026
20bc188
MOD-16399 Reuse shared topology comparison for CLUSTERSET
gabsow Jul 27, 2026
5d6182b
MOD-16399 Use long-form argument guard
gabsow Jul 27, 2026
4243940
MOD-16399 Reuse shared topology comparison for CLUSTERSET
gabsow Jul 27, 2026
83a5dea
MOD-16399 Use long-form argument guard
gabsow Jul 27, 2026
c23cc6a
MOD-16399 Skip identical long-form CLUSTERSET
gabsow Jul 27, 2026
c9b018f
MOD-16399 Restore published PR history
gabsow Jul 27, 2026
60a4f32
MOD-16399 Compare long-form arguments by length
gabsow Jul 27, 2026
4d1759e
MOD-16399 Place MYID comment by comparison guard
gabsow Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/cluster.c
Original file line number Diff line number Diff line change
Expand Up @@ -1406,8 +1406,33 @@ static void SetClusterDataLongForm(RedisModuleString** argv, int argc){
mr_dictEmpty(clusterCtx.nodesMsgIds, NULL);
}

static bool IsSameLongFormClusterSet(RedisModuleString** argv, int argc){
Cluster* current = clusterCtx.CurrCluster;
if (!current ||
!current->clusterSetCommand ||
current->clusterSetCommandSize != argc)
return false;

for (int i = 1; i < argc; ++i) {
/* MYID identifies the receiving shard and is not retained in clusterSetCommand. */
if (i == CLUSTERSET_MYID_LONG_FORM_INDEX)
continue;
size_t argLen;
const char* arg = RedisModule_StringPtrLen(argv[i], &argLen);
if (argLen != strlen(current->clusterSetCommand[i]) ||
memcmp(arg, current->clusterSetCommand[i], argLen) != 0)
return false;
}
return true;
}

static int MR_SetClusterData(RedisModuleString** argv, int argc){
if (IsLongFormClusterSet(argc)) {
if (IsSameLongFormClusterSet(argv, argc)) {
RedisModule_Log(mr_staticCtx, "notice",
"Skipping identical long-form cluster set");
return REDISMODULE_OK;
}
SetClusterDataLongForm(argv, argc);
return REDISMODULE_OK;
} else if (IsShortFormClusterSet(argc)) {
Expand Down
31 changes: 27 additions & 4 deletions tests/mr_test_module/pytests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def _handle_conn(self, sock, client_addr):
conn = Connection(sock)
self.new_conns.put(conn)

def _send_cluster_set(self):
def _send_cluster_set(self, mock_shard_id='2', first_arg='NO-USED'):
# try to promote to internal connection
promote_internal_client_if_supported(env=self.env)
# IPv6 endpoints must be bracketed in host:port strings
Expand All @@ -220,7 +220,7 @@ def _send_cluster_set(self):
# argv[6] => myId, argv[7] => "RANGES", argv[8] => numOfRanges, then repeating:
# "SHARD" <id> "SLOTRANGE" <min> <max> "ADDR" <password@host:port> ["MASTER"]
args = [
'NO-USED', # [1]
first_arg, # [1]
'NO-USED', # [2]
'NO-USED', # [3]
'NO-USED', # [4]
Expand All @@ -234,7 +234,7 @@ def _send_cluster_set(self):
'ADDR', 'password@%s:6379' % endpoint_host,
'MASTER',
# Shard 2 (mock shard)
'SHARD', '2',
'SHARD', mock_shard_id,
'SLOTRANGE', '8193', '16383',
'ADDR', 'password@%s:%d' % (endpoint_host, self.port),
'MASTER'
Expand Down Expand Up @@ -793,7 +793,30 @@ def testMassiveClusterSet(env, conn):
with ShardMock(env, host) as shardMock:
for i in range(1000):
conn = shardMock.GetConnection(sendHelloResponse=False)
shardMock._send_cluster_set()
# Keep exercising rebuilds now that identical updates are skipped.
shardMock._send_cluster_set(mock_shard_id=str(3 - (i % 2)))


@MRTestDecorator(skipOnCluster=True)
def testIdenticalLongFormClusterSetIsNoOp(env, conn):
for host in _get_hosts():
with ShardMock(env, host) as shardMock:
shardMock.GetConnection()
run_id = env.cmd('MRTESTS.INFOCLUSTER')[3]

shardMock._send_cluster_set()
env.assertEqual(env.cmd('MRTESTS.INFOCLUSTER')[3], run_id)

# RedisModuleString can carry embedded NUL bytes. Its explicit length
# must participate in the comparison so this safely rebuilds.
shardMock._send_cluster_set(first_arg=b'NO-USED\0changed')
env.assertNotEqual(env.cmd('MRTESTS.INFOCLUSTER')[3], run_id)
shardMock.GetConnection()

run_id = env.cmd('MRTESTS.INFOCLUSTER')[3]
shardMock._send_cluster_set(mock_shard_id='3')
env.assertNotEqual(env.cmd('MRTESTS.INFOCLUSTER')[3], run_id)
shardMock.GetConnection()

@MRTestDecorator(skipOnCluster=True)
def testMassiveClusterSetFromShard(env, conn):
Expand Down
Loading