1
+ use rand:: { thread_rng, Rng } ;
1
2
use tracing:: error;
2
3
3
4
use super :: { FallbackPlan , LoadBalancingPolicy , NodeRef , RoutingInfo } ;
@@ -6,20 +7,65 @@ use crate::{routing::Shard, transport::ClusterData};
6
7
enum PlanState < ' a > {
7
8
Created ,
8
9
PickedNone , // This always means an abnormal situation: it means that no nodes satisfied locality/node filter requirements.
9
- Picked ( ( NodeRef < ' a > , Shard ) ) ,
10
+ Picked ( ( NodeRef < ' a > , Option < Shard > ) ) ,
10
11
Fallback {
11
12
iter : FallbackPlan < ' a > ,
12
- node_to_filter_out : ( NodeRef < ' a > , Shard ) ,
13
+ target_to_filter_out : ( NodeRef < ' a > , Option < Shard > ) ,
13
14
} ,
14
15
}
15
16
16
- /// The list of nodes constituting the query plan.
17
+ /// The list of targets constituting the query plan. Target here is a pair `(NodeRef<'a>, Shard)` .
17
18
///
18
- /// The plan is partly lazily computed, with the first node computed
19
- /// eagerly in the first place and the remaining nodes computed on-demand
19
+ /// The plan is partly lazily computed, with the first target computed
20
+ /// eagerly in the first place and the remaining targets computed on-demand
20
21
/// (all at once).
21
22
/// This significantly reduces the allocation overhead on "the happy path"
22
- /// (when the first node successfully handles the request),
23
+ /// (when the first target successfully handles the request).
24
+ ///
25
+ /// `Plan` implements `Iterator<Item=(NodeRef<'a>, Shard)>` but LoadBalancingPolicy
26
+ /// returns `Option<Shard>` instead of `Shard` both in `pick` and in `fallback`.
27
+ /// `Plan` handles the `None` case by using random shard for a given node.
28
+ /// There is currently no way to configure RNG used by `Plan`.
29
+ /// If you don't want `Plan` to do randomize shards or you want to control the RNG,
30
+ /// use custom LBP that will always return non-`None` shards.
31
+ /// Example of LBP that always uses shard 0, preventing `Plan` from using random numbers:
32
+ ///
33
+ /// ```
34
+ /// # use std::sync::Arc;
35
+ /// # use scylla::load_balancing::LoadBalancingPolicy;
36
+ /// # use scylla::load_balancing::RoutingInfo;
37
+ /// # use scylla::transport::ClusterData;
38
+ /// # use scylla::transport::NodeRef;
39
+ /// # use scylla::routing::Shard;
40
+ /// # use scylla::load_balancing::FallbackPlan;
41
+ ///
42
+ /// #[derive(Debug)]
43
+ /// struct NonRandomLBP {
44
+ /// inner: Arc<dyn LoadBalancingPolicy>,
45
+ /// }
46
+ /// impl LoadBalancingPolicy for NonRandomLBP {
47
+ /// fn pick<'a>(
48
+ /// &'a self,
49
+ /// info: &'a RoutingInfo,
50
+ /// cluster: &'a ClusterData,
51
+ /// ) -> Option<(NodeRef<'a>, Option<Shard>)> {
52
+ /// self.inner
53
+ /// .pick(info, cluster)
54
+ /// .map(|(node, shard)| (node, shard.or(Some(0))))
55
+ /// }
56
+ ///
57
+ /// fn fallback<'a>(&'a self, info: &'a RoutingInfo, cluster: &'a ClusterData) -> FallbackPlan<'a> {
58
+ /// Box::new(self.inner
59
+ /// .fallback(info, cluster)
60
+ /// .map(|(node, shard)| (node, shard.or(Some(0)))))
61
+ /// }
62
+ ///
63
+ /// fn name(&self) -> String {
64
+ /// "NonRandomLBP".to_string()
65
+ /// }
66
+ /// }
67
+ /// ```
68
+
23
69
pub struct Plan < ' a > {
24
70
policy : & ' a dyn LoadBalancingPolicy ,
25
71
routing_info : & ' a RoutingInfo < ' a > ,
@@ -41,6 +87,21 @@ impl<'a> Plan<'a> {
41
87
state : PlanState :: Created ,
42
88
}
43
89
}
90
+
91
+ fn with_random_shard_if_unknown (
92
+ ( node, shard) : ( NodeRef < ' _ > , Option < Shard > ) ,
93
+ ) -> ( NodeRef < ' _ > , Shard ) {
94
+ (
95
+ node,
96
+ shard. unwrap_or_else ( || {
97
+ let nr_shards = node
98
+ . sharder ( )
99
+ . map ( |sharder| sharder. nr_shards . get ( ) )
100
+ . unwrap_or ( 1 ) ;
101
+ thread_rng ( ) . gen_range ( 0 ..nr_shards) . into ( )
102
+ } ) ,
103
+ )
104
+ }
44
105
}
45
106
46
107
impl < ' a > Iterator for Plan < ' a > {
@@ -52,7 +113,7 @@ impl<'a> Iterator for Plan<'a> {
52
113
let picked = self . policy . pick ( self . routing_info , self . cluster ) ;
53
114
if let Some ( picked) = picked {
54
115
self . state = PlanState :: Picked ( picked) ;
55
- Some ( picked)
116
+ Some ( Self :: with_random_shard_if_unknown ( picked) )
56
117
} else {
57
118
// `pick()` returned None, which semantically means that a first node cannot be computed _cheaply_.
58
119
// This, however, does not imply that fallback would return an empty plan, too.
@@ -64,9 +125,9 @@ impl<'a> Iterator for Plan<'a> {
64
125
if let Some ( node) = first_fallback_node {
65
126
self . state = PlanState :: Fallback {
66
127
iter,
67
- node_to_filter_out : node,
128
+ target_to_filter_out : node,
68
129
} ;
69
- Some ( node)
130
+ Some ( Self :: with_random_shard_if_unknown ( node) )
70
131
} else {
71
132
error ! ( "Load balancing policy returned an empty plan! The query cannot be executed. Routing info: {:?}" , self . routing_info) ;
72
133
self . state = PlanState :: PickedNone ;
@@ -77,20 +138,20 @@ impl<'a> Iterator for Plan<'a> {
77
138
PlanState :: Picked ( node) => {
78
139
self . state = PlanState :: Fallback {
79
140
iter : self . policy . fallback ( self . routing_info , self . cluster ) ,
80
- node_to_filter_out : * node,
141
+ target_to_filter_out : * node,
81
142
} ;
82
143
83
144
self . next ( )
84
145
}
85
146
PlanState :: Fallback {
86
147
iter,
87
- node_to_filter_out,
148
+ target_to_filter_out : node_to_filter_out,
88
149
} => {
89
150
for node in iter {
90
151
if node == * node_to_filter_out {
91
152
continue ;
92
153
} else {
93
- return Some ( node) ;
154
+ return Some ( Self :: with_random_shard_if_unknown ( node) ) ;
94
155
}
95
156
}
96
157
@@ -135,7 +196,7 @@ mod tests {
135
196
& ' a self ,
136
197
_query : & ' a RoutingInfo ,
137
198
_cluster : & ' a ClusterData ,
138
- ) -> Option < ( NodeRef < ' a > , Shard ) > {
199
+ ) -> Option < ( NodeRef < ' a > , Option < Shard > ) > {
139
200
None
140
201
}
141
202
@@ -147,7 +208,7 @@ mod tests {
147
208
Box :: new (
148
209
self . expected_nodes
149
210
. iter ( )
150
- . map ( |( node_ref, shard) | ( node_ref, * shard) ) ,
211
+ . map ( |( node_ref, shard) | ( node_ref, Some ( * shard) ) ) ,
151
212
)
152
213
}
153
214
0 commit comments