meta_srv/selector/
load_based.rs1use std::collections::HashMap;
16
17use common_meta::affinity::AffinityFallback;
18use common_meta::datanode::{DatanodeStatKey, DatanodeStatValue};
19use common_meta::peer::Peer;
20use common_telemetry::debug;
21
22use crate::cluster::MetaPeerClientRef;
23use crate::error::{self, Result};
24use crate::metasrv::SelectorContext;
25use crate::selector::common::{self, choose_items, filter_out_excluded_peers};
26use crate::selector::weight_compute::WeightCompute;
27use crate::selector::weighted_choose::RandomWeightedChoose;
28use crate::selector::{Selector, SelectorOptions};
29
30pub struct LoadBasedSelector<C> {
31 weight_compute: C,
32 meta_peer_client: MetaPeerClientRef,
33}
34
35impl<C> LoadBasedSelector<C> {
36 pub fn new(weight_compute: C, meta_peer_client: MetaPeerClientRef) -> Self {
37 Self {
38 weight_compute,
39 meta_peer_client,
40 }
41 }
42}
43
44impl<C> LoadBasedSelector<C>
45where
46 C: WeightCompute<Source = HashMap<DatanodeStatKey, DatanodeStatValue>>,
47{
48 async fn select_once(
49 &self,
50 ctx: &SelectorContext,
51 opts: &SelectorOptions<'_>,
52 ) -> Result<Vec<Peer>> {
53 let alive_datanodes = common::active_schedulable_datanodes(ctx, opts.workload_filter)
55 .await?
56 .into_iter()
57 .filter(|node| {
58 opts.affinity
59 .is_none_or(|requirements| ctx.affinity.matches(requirements, node))
60 })
61 .map(|node| node.peer)
62 .collect::<Vec<_>>();
63
64 let stat_keys = alive_datanodes
66 .iter()
67 .map(|k| DatanodeStatKey { node_id: k.id })
68 .collect();
69 let stat_kvs = filter_out_expired_datanode(
70 self.meta_peer_client.get_dn_stat_kvs(stat_keys).await?,
71 &alive_datanodes,
72 );
73
74 let mut weight_array = self.weight_compute.compute(&stat_kvs);
76
77 filter_out_excluded_peers(&mut weight_array, &opts.exclude_peer_ids);
79 let mut weighted_choose = RandomWeightedChoose::new(weight_array);
81 let selected = choose_items(opts, &mut weighted_choose)?;
82
83 debug!(
84 "LoadBasedSelector select peers: {:?}, opts: {:?}.",
85 selected, opts,
86 );
87
88 Ok(selected)
89 }
90}
91
92#[async_trait::async_trait]
93impl<C> Selector for LoadBasedSelector<C>
94where
95 C: WeightCompute<Source = HashMap<DatanodeStatKey, DatanodeStatValue>>,
96{
97 type Context = SelectorContext;
98 type Output = Vec<Peer>;
99
100 async fn select(
101 &self,
102 ctx: &Self::Context,
103 mut opts: SelectorOptions<'_>,
104 ) -> Result<Self::Output> {
105 match self.select_once(ctx, &opts).await {
106 Err(error::Error::NoEnoughAvailableNode {
107 required,
108 available,
109 ..
110 }) if opts.affinity.is_some()
111 && ctx.affinity_fallback == AffinityFallback::Unconstrained
112 && available < required =>
113 {
114 opts.affinity = None;
115 self.select_once(ctx, &opts).await
116 }
117 result => result,
118 }
119 }
120}
121
122fn filter_out_expired_datanode(
123 mut stat_kvs: HashMap<DatanodeStatKey, DatanodeStatValue>,
124 datanodes: &[Peer],
125) -> HashMap<DatanodeStatKey, DatanodeStatValue> {
126 datanodes
127 .iter()
128 .filter_map(|p| stat_kvs.remove_entry(&DatanodeStatKey { node_id: p.id }))
129 .collect()
130}
131
132#[cfg(test)]
133mod tests {
134 use std::collections::HashMap;
135
136 use common_meta::datanode::{DatanodeStatKey, DatanodeStatValue};
137 use common_meta::peer::Peer;
138
139 use crate::selector::load_based::filter_out_expired_datanode;
140
141 #[test]
142 fn test_filter_out_expired_datanode() {
143 let mut stat_kvs = HashMap::new();
144 stat_kvs.insert(
145 DatanodeStatKey { node_id: 0 },
146 DatanodeStatValue { stats: vec![] },
147 );
148 stat_kvs.insert(
149 DatanodeStatKey { node_id: 1 },
150 DatanodeStatValue { stats: vec![] },
151 );
152 stat_kvs.insert(
153 DatanodeStatKey { node_id: 2 },
154 DatanodeStatValue { stats: vec![] },
155 );
156
157 let lease_kvs = vec![Peer::new(1, "127.0.0.1:3002".to_string())];
158 let alive_stat_kvs = filter_out_expired_datanode(stat_kvs, &lease_kvs);
159
160 assert_eq!(1, alive_stat_kvs.len());
161 assert!(alive_stat_kvs.contains_key(&DatanodeStatKey { node_id: 1 }));
162 }
163}