mito2/schedule/
remote_job_scheduler.rs1use std::fmt;
16use std::sync::{Arc, Mutex};
17use std::time::Instant;
18
19use common_telemetry::error;
20use common_time::TimeToLive;
21use serde::{Deserialize, Serialize};
22use snafu::{Location, ResultExt, Snafu};
23use store_api::storage::RegionId;
24use tokio::sync::mpsc::Sender;
25use uuid::Uuid;
26
27use crate::compaction::CompactionExecution;
28use crate::compaction::compactor::CompactionRegion;
29use crate::compaction::picker::PickerOutput;
30use crate::error::{CompactRegionSnafu, Error, ParseJobIdSnafu, Result};
31use crate::manifest::action::RegionEdit;
32use crate::metrics::{COMPACTION_FAILURE_COUNT, INFLIGHT_COMPACTION_COUNT};
33use crate::request::{
34 BackgroundNotify, CompactionFailed, CompactionFinished, OutputTx, WorkerRequest,
35 WorkerRequestWithTime,
36};
37
38pub type RemoteJobSchedulerRef = Arc<dyn RemoteJobScheduler>;
39
40#[cfg_attr(doc, aquamarine::aquamarine)]
41#[async_trait::async_trait]
59pub trait RemoteJobScheduler: Send + Sync + 'static {
60 async fn schedule(
62 &self,
63 job: RemoteJob,
64 notifier: Box<dyn Notifier>,
65 ) -> Result<JobId, RemoteJobSchedulerError>;
66}
67
68#[derive(Snafu, Debug)]
69#[snafu(display("Internal error occurred in remote job scheduler: {}", reason))]
70pub struct RemoteJobSchedulerError {
71 #[snafu(implicit)]
72 pub location: Location,
73 pub reason: String,
74 pub waiters: Vec<OutputTx>,
76}
77
78#[async_trait::async_trait]
80pub trait Notifier: Send + Sync + 'static {
81 async fn notify(&self, result: RemoteJobResult, waiters: Vec<OutputTx>);
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
87pub struct JobId(Uuid);
88
89impl JobId {
90 pub fn parse_str(input: &str) -> Result<JobId> {
92 Uuid::parse_str(input).map(JobId).context(ParseJobIdSnafu)
93 }
94}
95
96impl fmt::Display for JobId {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(f, "{}", self.0)
99 }
100}
101
102#[allow(dead_code)]
104pub enum RemoteJob {
105 CompactionJob(CompactionJob),
106}
107
108#[allow(dead_code)]
110pub struct CompactionJob {
111 pub compaction_region: CompactionRegion,
112 pub picker_output: PickerOutput,
113 pub start_time: Instant,
114 pub ttl: TimeToLive,
115 pub waiters: Vec<OutputTx>,
117}
118
119#[allow(dead_code)]
121pub enum RemoteJobResult {
122 CompactionJobResult(CompactionJobResult),
123}
124
125#[allow(dead_code)]
127pub struct CompactionJobResult {
128 pub job_id: JobId,
129 pub region_id: RegionId,
130 pub start_time: Instant,
131 pub region_edit: Result<RegionEdit>,
132}
133
134pub(crate) struct DefaultNotifier {
136 pub(crate) request_sender: Sender<WorkerRequestWithTime>,
138 execution: Mutex<Option<CompactionExecution>>,
139}
140
141impl DefaultNotifier {
142 pub(crate) fn new(
143 request_sender: Sender<WorkerRequestWithTime>,
144 execution: CompactionExecution,
145 ) -> Self {
146 Self {
147 request_sender,
148 execution: Mutex::new(Some(execution)),
149 }
150 }
151
152 fn on_failure(&self, err: Arc<Error>, region_id: RegionId, mut waiters: Vec<OutputTx>) {
153 COMPACTION_FAILURE_COUNT.inc();
154 for waiter in waiters.drain(..) {
155 waiter.send(Err(err.clone()).context(CompactRegionSnafu { region_id }));
156 }
157 }
158}
159
160#[async_trait::async_trait]
161impl Notifier for DefaultNotifier {
162 async fn notify(&self, result: RemoteJobResult, waiters: Vec<OutputTx>) {
163 let Some(execution) = self
164 .execution
165 .lock()
166 .unwrap_or_else(|poisoned| poisoned.into_inner())
167 .take()
168 else {
169 error!("Remote compaction notifier invoked more than once");
170 return;
171 };
172 INFLIGHT_COMPACTION_COUNT.dec();
173 match result {
174 RemoteJobResult::CompactionJobResult(result) => {
175 let notify = {
176 match result.region_edit {
177 Ok(edit) => BackgroundNotify::CompactionFinished(CompactionFinished {
178 region_id: result.region_id,
179 execution,
180 senders: waiters,
181 start_time: result.start_time,
182 edit,
183 }),
184 Err(err) => {
185 error!(
186 "Compaction failed for region {}: {:?}",
187 result.region_id, err
188 );
189 let err = Arc::new(err);
190 self.on_failure(err.clone(), result.region_id, waiters);
191 BackgroundNotify::CompactionFailed(CompactionFailed {
192 region_id: result.region_id,
193 execution,
194 err,
195 })
196 }
197 }
198 };
199
200 if let Err(e) = self
201 .request_sender
202 .send(WorkerRequestWithTime::new(WorkerRequest::Background {
203 region_id: result.region_id,
204 notify,
205 }))
206 .await
207 {
208 error!(
209 "Failed to notify compaction job status for region {}, error: {:?}",
210 result.region_id, e
211 );
212 }
213 }
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_job_id() {
224 let id = Uuid::new_v4().to_string();
225 let job_id = JobId::parse_str(&id).unwrap();
226 assert_eq!(job_id.to_string(), id);
227 }
228}