@@ -998,7 +998,7 @@ func (c *ClusterClient) Process(ctx context.Context, cmd Cmder) error {
998998}
999999
10001000func (c * ClusterClient ) process (ctx context.Context , cmd Cmder ) error {
1001- slot := c .cmdSlot (cmd )
1001+ slot := c .cmdSlot (cmd , - 1 )
10021002 var node * clusterNode
10031003 var moved bool
10041004 var ask bool
@@ -1344,9 +1344,13 @@ func (c *ClusterClient) mapCmdsByNode(ctx context.Context, cmdsMap *cmdsMap, cmd
13441344 return err
13451345 }
13461346
1347+ preferredRandomSlot := - 1
13471348 if c .opt .ReadOnly && c .cmdsAreReadOnly (ctx , cmds ) {
13481349 for _ , cmd := range cmds {
1349- slot := c .cmdSlot (cmd )
1350+ slot := c .cmdSlot (cmd , preferredRandomSlot )
1351+ if preferredRandomSlot == - 1 {
1352+ preferredRandomSlot = slot
1353+ }
13501354 node , err := c .slotReadOnlyNode (state , slot )
13511355 if err != nil {
13521356 return err
@@ -1357,7 +1361,10 @@ func (c *ClusterClient) mapCmdsByNode(ctx context.Context, cmdsMap *cmdsMap, cmd
13571361 }
13581362
13591363 for _ , cmd := range cmds {
1360- slot := c .cmdSlot (cmd )
1364+ slot := c .cmdSlot (cmd , preferredRandomSlot )
1365+ if preferredRandomSlot == - 1 {
1366+ preferredRandomSlot = slot
1367+ }
13611368 node , err := state .slotMasterNode (slot )
13621369 if err != nil {
13631370 return err
@@ -1519,58 +1526,78 @@ func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) err
15191526 return err
15201527 }
15211528
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.
15251541 setCmdsErr (cmds , ErrCrossSlot )
15261542 return ErrCrossSlot
15271543 }
15281544
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+ }
15351550
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
15431557 }
1558+ }
15441559
1545- failedCmds := newCmdsMap ()
1546- var wg sync.WaitGroup
1560+ failedCmds := newCmdsMap ()
1561+ var wg sync.WaitGroup
15471562
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+ }
15551570
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
15611574 }
1575+ cmdsMap = failedCmds .m
15621576 }
15631577
15641578 return cmdsFirstErr (cmds )
15651579}
15661580
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
15691587 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 )
15721598 }
1573- return cmdsMap
1599+
1600+ return cmdsSlots
15741601}
15751602
15761603func (c * ClusterClient ) processTxPipelineNode (
@@ -1885,17 +1912,20 @@ func (c *ClusterClient) cmdInfo(ctx context.Context, name string) *CommandInfo {
18851912 return info
18861913}
18871914
1888- func (c * ClusterClient ) cmdSlot (cmd Cmder ) int {
1915+ func (c * ClusterClient ) cmdSlot (cmd Cmder , preferredRandomSlot int ) int {
18891916 args := cmd .Args ()
18901917 if args [0 ] == "cluster" && (args [1 ] == "getkeysinslot" || args [1 ] == "countkeysinslot" ) {
18911918 return args [2 ].(int )
18921919 }
18931920
1894- return cmdSlot (cmd , cmdFirstKeyPos (cmd ))
1921+ return cmdSlot (cmd , cmdFirstKeyPos (cmd ), preferredRandomSlot )
18951922}
18961923
1897- func cmdSlot (cmd Cmder , pos int ) int {
1924+ func cmdSlot (cmd Cmder , pos int , preferredRandomSlot int ) int {
18981925 if pos == 0 {
1926+ if preferredRandomSlot != - 1 {
1927+ return preferredRandomSlot
1928+ }
18991929 return hashtag .RandomSlot ()
19001930 }
19011931 firstKey := cmd .stringArg (pos )
0 commit comments