Skip to content

Commit 5fc490e

Browse files
committed
feat(sharding): add shard creation for new nodes during rebalance
- Log distinct messages for rebalancing due to inactive vs new nodes - Detect new nodes joining cluster and create missing shard tables - Set up replication from existing nodes to new nodes for new shards - Improve shard map initialization to avoid undefined keys
1 parent 8689848 commit 5fc490e

File tree

3 files changed

+98
-4
lines changed

3 files changed

+98
-4
lines changed

src/Plugin/Sharding/Operator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ public function checkBalance(): static {
113113
if ($clusterHash === $currentHash) {
114114
return $this;
115115
}
116-
Buddy::info("Rebalancing due to inactive nodes: {$inactiveNodes->join(', ')}");
116+
117+
if ($inactiveNodes->count() > 0) {
118+
Buddy::info("Rebalancing due to inactive nodes: {$inactiveNodes->join(', ')}");
119+
} else {
120+
Buddy::info('Rebalancing due to new nodes joined');
121+
}
117122

118123
// Get all tables from the state we have
119124
$list = $this->state->listRegex('table:.+');

src/Plugin/Sharding/Table.php

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,16 @@ public function rebalance(Queue $queue): void {
443443
$cluster->processPendingTables($queue);
444444
}
445445

446+
// Handle new nodes that need shard creation
447+
$originalNodes = new Set($schema->map(fn($row) => $row['node']));
448+
$newNodes = $activeNodes->diff($originalNodes);
449+
450+
451+
452+
if ($newNodes->count() > 0) {
453+
$this->handleShardCreationForRebalancing($queue, $schema, $newSchema, $clusterMap);
454+
}
455+
446456
// At this case we update schema
447457
// before creating distributed table
448458
$this->updateScheme($newSchema);
@@ -664,10 +674,12 @@ protected function getCreateShardedTableSQL(Set $shards): string {
664674
$map = new Map;
665675
foreach ($nodes as $row) {
666676
foreach ($row['shards'] as $shard) {
667-
$map[$shard] ??= new Set;
677+
if (!$map->hasKey($shard)) {
678+
$map[$shard] = new Set();
679+
}
668680
$shardName = $this->getShardName($shard);
669-
// @phpstan-ignore-next-line
670-
$map[$shard]->add("{$row['node']}:{$shardName}");
681+
682+
$map[$shard]?->add("{$row['node']}:{$shardName}");
671683
}
672684
}
673685

@@ -748,4 +760,61 @@ protected static function parseShards(string $shards): Set {
748760
: new Set
749761
;
750762
}
763+
764+
/**
765+
* Handle shard creation for rebalancing (all nodes that need new shards)
766+
* @param Queue $queue
767+
* @param Vector<array{node:string,shards:Set<int>,connections:Set<string>}> $oldSchema
768+
* @param Vector<array{node:string,shards:Set<int>,connections:Set<string>}> $newSchema
769+
* @param Map<string,Cluster> $clusterMap
770+
* @return void
771+
*/
772+
protected function handleShardCreationForRebalancing(Queue $queue, Vector $oldSchema, Vector $newSchema, Map $clusterMap): void {
773+
// Create map of old schema for comparison
774+
/** @var Map<string,Set<int>> */
775+
$oldShardMap = new Map();
776+
foreach ($oldSchema as $row) {
777+
$oldShardMap[$row['node']] = $row['shards'];
778+
}
779+
780+
foreach ($newSchema as $row) {
781+
$oldShards = $oldShardMap->get($row['node'], new Set());
782+
$newShards = $row['shards'];
783+
$shardsToCreate = $newShards->diff($oldShards);
784+
785+
if ($shardsToCreate->isEmpty()) {
786+
continue;
787+
}
788+
789+
// Create missing shard tables on this node
790+
foreach ($shardsToCreate as $shard) {
791+
$sql = $this->getCreateTableShardSQL($shard);
792+
$queue->add($row['node'], $sql);
793+
794+
// Find nodes that already have this shard in old schema for replication
795+
$existingNodesWithShard = $oldSchema->filter(
796+
fn($existingRow) => $existingRow['shards']->contains($shard)
797+
);
798+
799+
// If there are existing nodes with this shard, set up replication
800+
if ($existingNodesWithShard->count() <= 0) {
801+
continue;
802+
}
803+
804+
$sourceNode = $existingNodesWithShard->first()['node'];
805+
$connectedNodes = new Set([$row['node'], $sourceNode]);
806+
807+
808+
809+
// Set up cluster replication for this shard
810+
$this->handleReplication(
811+
$sourceNode,
812+
$queue,
813+
$connectedNodes,
814+
$clusterMap,
815+
$shard
816+
);
817+
}
818+
}
819+
}
751820
}

src/Plugin/Sharding/Util.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Ds\Vector;
88
use RuntimeException;
99

10+
/** @package Manticoresearch\Buddy\Base\Plugin\Sharding */
1011
final class Util {
1112
/**
1213
* Generate sharding schema by using input nodes, shards and replication
@@ -137,6 +138,25 @@ public static function rebalanceShardingScheme(Vector $schema, Set $nodes): Vect
137138
$newSchema = self::addNodesToSchema($newSchema, $nodes);
138139
$inactiveShards = self::findInactiveShards($schema, $nodes);
139140

141+
// Check if we have new nodes (nodes with no shards assigned)
142+
$hasNewNodes = $newSchema->filter(fn($row) => $row['shards']->isEmpty())->count() > 0;
143+
144+
if ($hasNewNodes) {
145+
// For new nodes, use the same balanced logic as createShardingSchema
146+
$totalShards = 0;
147+
foreach ($schema as $row) {
148+
$totalShards += $row['shards']->count();
149+
}
150+
151+
if ($totalShards > 0) {
152+
// Reuse the balanced assignment logic from createShardingSchema
153+
$balancedSchema = self::initializeSchema($nodes);
154+
$nodeMap = self::initializeNodeMap($nodes->count());
155+
return self::assignNodesToSchema($balancedSchema, $nodeMap, $nodes, $totalShards, 2);
156+
}
157+
}
158+
159+
// For node failures only (no new nodes), use the original logic
140160
return self::assignShardsToNodes($newSchema, $inactiveShards);
141161
}
142162

0 commit comments

Comments
 (0)