Skip to main content

meta_srv/
node_scheduling.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashSet;
16use std::sync::Arc;
17
18use common_meta::kv_backend::KvBackendRef;
19use common_meta::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
20use common_meta::rpc::store::RangeRequest;
21use serde::{Deserialize, Serialize};
22use snafu::ResultExt;
23
24use crate::error::{self, Result};
25use crate::key::{NodeSchedulingKey, NodeSchedulingRole};
26
27pub type NodeSchedulingManagerRef = Arc<NodeSchedulingManager>;
28
29/// The scheduling state persisted for a node.
30///
31/// Only `schedulable: false` is written today; a missing key already means schedulable.
32/// New metadata fields must use `#[serde(default)]`.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct NodeSchedulingValue {
35    /// Whether the node may receive new regions.
36    pub schedulable: bool,
37}
38
39/// Persists which nodes may receive new regions.
40///
41/// A missing key means the node is schedulable; a present key stores a
42/// [`NodeSchedulingValue`] whose `schedulable` field decides placement.
43pub struct NodeSchedulingManager {
44    kv_backend: KvBackendRef,
45}
46
47impl NodeSchedulingManager {
48    pub fn new(kv_backend: KvBackendRef) -> Self {
49        Self { kv_backend }
50    }
51
52    /// Marks the node as unschedulable. Idempotent.
53    ///
54    /// Returns whether the stored state changed.
55    pub async fn mark_unschedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
56        let key: Vec<u8> = NodeSchedulingKey::new(role, node_id).into();
57        let value = serde_json::to_vec(&NodeSchedulingValue { schedulable: false }).context(
58            error::SerializeToJsonSnafu {
59                input: String::from_utf8_lossy(&key).into_owned(),
60            },
61        )?;
62
63        let txn = match self
64            .kv_backend
65            .get(&key)
66            .await
67            .context(error::KvBackendSnafu)?
68        {
69            None => Txn::put_if_not_exists(key, value),
70            Some(kv) if kv.value == value => return Ok(false),
71            Some(kv) => Txn::compare_and_put(key, kv.value, value),
72        };
73        let response = self
74            .kv_backend
75            .txn(txn)
76            .await
77            .context(error::KvBackendSnafu)?;
78        Ok(response.succeeded)
79    }
80
81    /// Marks the node as schedulable by deleting its state. Idempotent.
82    ///
83    /// Returns whether a state was removed; a node without state is already schedulable,
84    /// so this returns `false` and writes nothing.
85    pub async fn mark_schedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
86        let key: Vec<u8> = NodeSchedulingKey::new(role, node_id).into();
87        // `NotEqual` against a missing target means "the key exists".
88        let txn = Txn::new()
89            .when(vec![Compare::new(key.clone(), CompareOp::NotEqual, None)])
90            .and_then(vec![TxnOp::Delete(key)]);
91        let response = self
92            .kv_backend
93            .txn(txn)
94            .await
95            .context(error::KvBackendSnafu)?;
96        Ok(response.succeeded)
97    }
98
99    /// Returns `false` iff the node is marked unschedulable.
100    pub async fn is_schedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
101        let key = NodeSchedulingKey::new(role, node_id);
102        Ok(self
103            .get_value(&key)
104            .await?
105            .is_none_or(|value| value.schedulable))
106    }
107
108    /// Returns the ids of nodes of `role` that must not receive new regions.
109    ///
110    /// Returns a set because callers test membership per node.
111    pub async fn list_unschedulable_node_ids(
112        &self,
113        role: NodeSchedulingRole,
114    ) -> Result<HashSet<u64>> {
115        let request = RangeRequest::new().with_prefix(NodeSchedulingKey::prefix_key(role));
116        let response = self
117            .kv_backend
118            .range(request)
119            .await
120            .context(error::KvBackendSnafu)?;
121
122        let mut node_ids = HashSet::new();
123        for kv in response.kvs {
124            let key = NodeSchedulingKey::try_from(kv.key)?;
125            if !parse_value(&kv.value)?.schedulable {
126                node_ids.insert(key.node_id);
127            }
128        }
129        Ok(node_ids)
130    }
131
132    async fn get_value(&self, key: &NodeSchedulingKey) -> Result<Option<NodeSchedulingValue>> {
133        let key: Vec<u8> = (*key).into();
134        let Some(kv) = self
135            .kv_backend
136            .get(&key)
137            .await
138            .context(error::KvBackendSnafu)?
139        else {
140            return Ok(None);
141        };
142
143        parse_value(&kv.value).map(Some)
144    }
145}
146
147fn parse_value(value: &[u8]) -> Result<NodeSchedulingValue> {
148    serde_json::from_slice(value).context(error::DeserializeFromJsonSnafu {
149        input_len: value.len(),
150    })
151}
152
153#[cfg(test)]
154mod tests {
155    use std::sync::Arc;
156
157    use common_meta::kv_backend::KvBackend;
158    use common_meta::kv_backend::memory::MemoryKvBackend;
159
160    use super::*;
161    use crate::metasrv::SelectorContext;
162
163    fn new_manager() -> (
164        Arc<MemoryKvBackend<common_meta::error::Error>>,
165        NodeSchedulingManager,
166    ) {
167        let kv_backend = Arc::new(MemoryKvBackend::new());
168        let manager = NodeSchedulingManager::new(kv_backend.clone());
169        (kv_backend, manager)
170    }
171
172    #[tokio::test]
173    async fn test_mark_unschedulable_is_idempotent() {
174        let (kv_backend, manager) = new_manager();
175
176        assert!(
177            manager
178                .is_schedulable(NodeSchedulingRole::Datanode, 1)
179                .await
180                .unwrap()
181        );
182        assert!(
183            manager
184                .mark_unschedulable(NodeSchedulingRole::Datanode, 1)
185                .await
186                .unwrap()
187        );
188        assert!(
189            !manager
190                .mark_unschedulable(NodeSchedulingRole::Datanode, 1)
191                .await
192                .unwrap()
193        );
194
195        assert!(
196            !manager
197                .is_schedulable(NodeSchedulingRole::Datanode, 1)
198                .await
199                .unwrap()
200        );
201        assert_eq!(
202            HashSet::from([1]),
203            manager
204                .list_unschedulable_node_ids(NodeSchedulingRole::Datanode)
205                .await
206                .unwrap()
207        );
208
209        let key: Vec<u8> = NodeSchedulingKey::new(NodeSchedulingRole::Datanode, 1).into();
210        let kv = kv_backend.get(&key).await.unwrap().unwrap();
211        assert_eq!(br#"{"schedulable":false}"#, kv.value.as_slice());
212
213        assert!(
214            manager
215                .mark_schedulable(NodeSchedulingRole::Datanode, 1)
216                .await
217                .unwrap()
218        );
219        assert!(
220            !manager
221                .mark_schedulable(NodeSchedulingRole::Datanode, 1)
222                .await
223                .unwrap()
224        );
225
226        assert!(
227            manager
228                .is_schedulable(NodeSchedulingRole::Datanode, 1)
229                .await
230                .unwrap()
231        );
232        assert!(
233            manager
234                .list_unschedulable_node_ids(NodeSchedulingRole::Datanode)
235                .await
236                .unwrap()
237                .is_empty()
238        );
239    }
240
241    #[tokio::test]
242    async fn test_mark_schedulable_without_state_writes_nothing() {
243        let (kv_backend, manager) = new_manager();
244
245        assert!(
246            !manager
247                .mark_schedulable(NodeSchedulingRole::Datanode, 2)
248                .await
249                .unwrap()
250        );
251
252        let key: Vec<u8> = NodeSchedulingKey::new(NodeSchedulingRole::Datanode, 2).into();
253        assert!(kv_backend.get(&key).await.unwrap().is_none());
254    }
255
256    #[tokio::test]
257    async fn test_scheduling_state_is_role_scoped() {
258        let (_, manager) = new_manager();
259
260        manager
261            .mark_unschedulable(NodeSchedulingRole::Datanode, 7)
262            .await
263            .unwrap();
264
265        assert!(
266            manager
267                .is_schedulable(NodeSchedulingRole::Flownode, 7)
268                .await
269                .unwrap()
270        );
271        assert!(
272            manager
273                .list_unschedulable_node_ids(NodeSchedulingRole::Flownode)
274                .await
275                .unwrap()
276                .is_empty()
277        );
278    }
279
280    #[tokio::test]
281    async fn test_scheduling_state_is_visible_to_other_manager() {
282        let (kv_backend, manager_a) = new_manager();
283        let manager_b = NodeSchedulingManager::new(kv_backend);
284
285        manager_a
286            .mark_unschedulable(NodeSchedulingRole::Datanode, 3)
287            .await
288            .unwrap();
289
290        assert!(
291            !manager_b
292                .is_schedulable(NodeSchedulingRole::Datanode, 3)
293                .await
294                .unwrap()
295        );
296    }
297
298    #[tokio::test]
299    async fn test_concurrent_mark_unschedulable_changes_once() {
300        let (_, manager) = new_manager();
301
302        let (a, b) = tokio::join!(
303            manager.mark_unschedulable(NodeSchedulingRole::Datanode, 2),
304            manager.mark_unschedulable(NodeSchedulingRole::Datanode, 2)
305        );
306        let changed = [a.unwrap(), b.unwrap()];
307        assert_eq!(1, changed.iter().filter(|changed| **changed).count());
308    }
309
310    /// Returns a selector context that stamps scheduling marks of datanodes 1, 2, 3
311    /// where datanode 2 is unschedulable.
312    async fn new_selector_context() -> SelectorContext {
313        use common_meta::peer::Peer;
314
315        use crate::test_util::{
316            create_meta_peer_client, new_node_scheduling_discovery, put_datanodes,
317        };
318
319        let client = create_meta_peer_client();
320        put_datanodes(
321            &client,
322            vec![Peer::empty(1), Peer::empty(2), Peer::empty(3)],
323        )
324        .await;
325
326        let (manager, discovery) = new_node_scheduling_discovery(&client);
327        manager
328            .mark_unschedulable(NodeSchedulingRole::Datanode, 2)
329            .await
330            .unwrap();
331
332        SelectorContext {
333            affinity_fallback: Default::default(),
334            affinity: Arc::new(common_meta::affinity::NoopAffinity),
335            peer_discovery: Arc::new(discovery),
336        }
337    }
338
339    #[tokio::test]
340    async fn test_lease_based_selector_skips_unschedulable() {
341        use crate::error;
342        use crate::selector::lease_based::LeaseBasedSelector;
343        use crate::selector::{Selector, SelectorOptions};
344
345        let ctx = new_selector_context().await;
346        let selector = LeaseBasedSelector;
347
348        let peers = selector
349            .select(
350                &ctx,
351                SelectorOptions {
352                    min_required_items: 2,
353                    ..Default::default()
354                },
355            )
356            .await
357            .unwrap();
358        assert_eq!(2, peers.len());
359        for peer in peers {
360            assert!([1, 3].contains(&peer.id), "unexpected peer: {peer:?}");
361        }
362
363        let err = selector
364            .select(
365                &ctx,
366                SelectorOptions {
367                    min_required_items: 3,
368                    ..Default::default()
369                },
370            )
371            .await
372            .unwrap_err();
373        assert!(
374            matches!(err, error::Error::NoEnoughAvailableNode { .. }),
375            "unexpected error: {err:?}"
376        );
377    }
378
379    #[tokio::test]
380    async fn test_round_robin_selector_skips_unschedulable() {
381        use crate::metasrv::SelectTarget;
382        use crate::selector::round_robin::RoundRobinSelector;
383        use crate::selector::{Selector, SelectorOptions};
384
385        let ctx = new_selector_context().await;
386        let selector = RoundRobinSelector::new(SelectTarget::Datanode);
387
388        let peers = selector
389            .select(
390                &ctx,
391                SelectorOptions {
392                    min_required_items: 2,
393                    ..Default::default()
394                },
395            )
396            .await
397            .unwrap();
398        let ids = peers.iter().map(|peer| peer.id).collect::<Vec<_>>();
399        assert_eq!(vec![1, 3], ids);
400    }
401}