@@ -998,7 +998,7 @@ func (c *ClusterClient) Process(ctx context.Context, cmd Cmder) error {
998
998
}
999
999
1000
1000
func (c * ClusterClient ) process (ctx context.Context , cmd Cmder ) error {
1001
- slot := c .cmdSlot (cmd )
1001
+ slot := c .cmdSlot (cmd , - 1 )
1002
1002
var node * clusterNode
1003
1003
var moved bool
1004
1004
var ask bool
@@ -1344,9 +1344,13 @@ func (c *ClusterClient) mapCmdsByNode(ctx context.Context, cmdsMap *cmdsMap, cmd
1344
1344
return err
1345
1345
}
1346
1346
1347
+ preferredRandomSlot := - 1
1347
1348
if c .opt .ReadOnly && c .cmdsAreReadOnly (ctx , cmds ) {
1348
1349
for _ , cmd := range cmds {
1349
- slot := c .cmdSlot (cmd )
1350
+ slot := c .cmdSlot (cmd , preferredRandomSlot )
1351
+ if preferredRandomSlot == - 1 {
1352
+ preferredRandomSlot = slot
1353
+ }
1350
1354
node , err := c .slotReadOnlyNode (state , slot )
1351
1355
if err != nil {
1352
1356
return err
@@ -1357,7 +1361,10 @@ func (c *ClusterClient) mapCmdsByNode(ctx context.Context, cmdsMap *cmdsMap, cmd
1357
1361
}
1358
1362
1359
1363
for _ , cmd := range cmds {
1360
- slot := c .cmdSlot (cmd )
1364
+ slot := c .cmdSlot (cmd , preferredRandomSlot )
1365
+ if preferredRandomSlot == - 1 {
1366
+ preferredRandomSlot = slot
1367
+ }
1361
1368
node , err := state .slotMasterNode (slot )
1362
1369
if err != nil {
1363
1370
return err
@@ -1519,58 +1526,78 @@ func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) err
1519
1526
return err
1520
1527
}
1521
1528
1522
- cmdsMap := c .mapCmdsBySlot (cmds )
1523
- // TxPipeline does not support cross slot transaction.
1524
- if len (cmdsMap ) > 1 {
1529
+ keyedCmdsBySlot := c .slottedKeyedCommands (cmds )
1530
+ slot := - 1
1531
+ switch len (keyedCmdsBySlot ) {
1532
+ case 0 :
1533
+ slot = hashtag .RandomSlot ()
1534
+ case 1 :
1535
+ for sl := range keyedCmdsBySlot {
1536
+ slot = sl
1537
+ break
1538
+ }
1539
+ default :
1540
+ // TxPipeline does not support cross slot transaction.
1525
1541
setCmdsErr (cmds , ErrCrossSlot )
1526
1542
return ErrCrossSlot
1527
1543
}
1528
1544
1529
- for slot , cmds := range cmdsMap {
1530
- node , err := state .slotMasterNode (slot )
1531
- if err != nil {
1532
- setCmdsErr (cmds , err )
1533
- continue
1534
- }
1545
+ node , err := state .slotMasterNode (slot )
1546
+ if err != nil {
1547
+ setCmdsErr (cmds , err )
1548
+ return err
1549
+ }
1535
1550
1536
- cmdsMap := map [* clusterNode ][]Cmder {node : cmds }
1537
- for attempt := 0 ; attempt <= c .opt .MaxRedirects ; attempt ++ {
1538
- if attempt > 0 {
1539
- if err := internal .Sleep (ctx , c .retryBackoff (attempt )); err != nil {
1540
- setCmdsErr (cmds , err )
1541
- return err
1542
- }
1551
+ cmdsMap := map [* clusterNode ][]Cmder {node : cmds }
1552
+ for attempt := 0 ; attempt <= c .opt .MaxRedirects ; attempt ++ {
1553
+ if attempt > 0 {
1554
+ if err := internal .Sleep (ctx , c .retryBackoff (attempt )); err != nil {
1555
+ setCmdsErr (cmds , err )
1556
+ return err
1543
1557
}
1558
+ }
1544
1559
1545
- failedCmds := newCmdsMap ()
1546
- var wg sync.WaitGroup
1560
+ failedCmds := newCmdsMap ()
1561
+ var wg sync.WaitGroup
1547
1562
1548
- for node , cmds := range cmdsMap {
1549
- wg .Add (1 )
1550
- go func (node * clusterNode , cmds []Cmder ) {
1551
- defer wg .Done ()
1552
- c .processTxPipelineNode (ctx , node , cmds , failedCmds )
1553
- }(node , cmds )
1554
- }
1563
+ for node , cmds := range cmdsMap {
1564
+ wg .Add (1 )
1565
+ go func (node * clusterNode , cmds []Cmder ) {
1566
+ defer wg .Done ()
1567
+ c .processTxPipelineNode (ctx , node , cmds , failedCmds )
1568
+ }(node , cmds )
1569
+ }
1555
1570
1556
- wg .Wait ()
1557
- if len (failedCmds .m ) == 0 {
1558
- break
1559
- }
1560
- cmdsMap = failedCmds .m
1571
+ wg .Wait ()
1572
+ if len (failedCmds .m ) == 0 {
1573
+ break
1561
1574
}
1575
+ cmdsMap = failedCmds .m
1562
1576
}
1563
1577
1564
1578
return cmdsFirstErr (cmds )
1565
1579
}
1566
1580
1567
- func (c * ClusterClient ) mapCmdsBySlot (cmds []Cmder ) map [int ][]Cmder {
1568
- cmdsMap := make (map [int ][]Cmder )
1581
+ // slottedKeyedCommands returns a map of slot to commands taking into account
1582
+ // only commands that have keys.
1583
+ func (c * ClusterClient ) slottedKeyedCommands (cmds []Cmder ) map [int ][]Cmder {
1584
+ cmdsSlots := map [int ][]Cmder {}
1585
+
1586
+ preferredRandomSlot := - 1
1569
1587
for _ , cmd := range cmds {
1570
- slot := c .cmdSlot (cmd )
1571
- cmdsMap [slot ] = append (cmdsMap [slot ], cmd )
1588
+ if cmdFirstKeyPos (cmd ) == 0 {
1589
+ continue
1590
+ }
1591
+
1592
+ slot := c .cmdSlot (cmd , preferredRandomSlot )
1593
+ if preferredRandomSlot == - 1 {
1594
+ preferredRandomSlot = slot
1595
+ }
1596
+
1597
+ cmdsSlots [slot ] = append (cmdsSlots [slot ], cmd )
1572
1598
}
1573
- return cmdsMap
1599
+
1600
+ return cmdsSlots
1574
1601
}
1575
1602
1576
1603
func (c * ClusterClient ) processTxPipelineNode (
@@ -1885,17 +1912,20 @@ func (c *ClusterClient) cmdInfo(ctx context.Context, name string) *CommandInfo {
1885
1912
return info
1886
1913
}
1887
1914
1888
- func (c * ClusterClient ) cmdSlot (cmd Cmder ) int {
1915
+ func (c * ClusterClient ) cmdSlot (cmd Cmder , preferredRandomSlot int ) int {
1889
1916
args := cmd .Args ()
1890
1917
if args [0 ] == "cluster" && (args [1 ] == "getkeysinslot" || args [1 ] == "countkeysinslot" ) {
1891
1918
return args [2 ].(int )
1892
1919
}
1893
1920
1894
- return cmdSlot (cmd , cmdFirstKeyPos (cmd ))
1921
+ return cmdSlot (cmd , cmdFirstKeyPos (cmd ), preferredRandomSlot )
1895
1922
}
1896
1923
1897
- func cmdSlot (cmd Cmder , pos int ) int {
1924
+ func cmdSlot (cmd Cmder , pos int , preferredRandomSlot int ) int {
1898
1925
if pos == 0 {
1926
+ if preferredRandomSlot != - 1 {
1927
+ return preferredRandomSlot
1928
+ }
1899
1929
return hashtag .RandomSlot ()
1900
1930
}
1901
1931
firstKey := cmd .stringArg (pos )
0 commit comments