datanode/heartbeat/handler/
flush_region.rs1use std::time::Instant;
16
17use common_meta::instruction::{
18 FlushErrorStrategy, FlushRegionReply, FlushRegions, FlushStrategy, InstructionError,
19 InstructionReply,
20};
21use common_telemetry::{debug, warn};
22use store_api::region_request::{RegionFlushReason, RegionFlushRequest, RegionRequest};
23use store_api::storage::RegionId;
24
25use crate::error::{self, RegionNotFoundSnafu, RegionNotReadySnafu, Result, UnexpectedSnafu};
26use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
27
28pub struct FlushRegionsHandler;
29
30#[async_trait::async_trait]
31impl InstructionHandler for FlushRegionsHandler {
32 type Instruction = FlushRegions;
33
34 async fn handle(
35 &self,
36 ctx: &HandlerContext,
37 flush_regions: FlushRegions,
38 ) -> Option<InstructionReply> {
39 let start_time = Instant::now();
40 let strategy = flush_regions.strategy;
41 let region_ids = flush_regions.region_ids;
42 let error_strategy = flush_regions.error_strategy;
43 let reason = flush_regions.reason;
44
45 let reply = if matches!(strategy, FlushStrategy::Async) {
46 ctx.handle_flush_hint(region_ids, reason).await;
48 None
49 } else {
50 let reply = ctx
52 .handle_flush_sync(region_ids, error_strategy, reason)
53 .await;
54 Some(InstructionReply::FlushRegions(reply))
55 };
56
57 let elapsed = start_time.elapsed();
58 debug!(
59 "FlushRegions strategy: {:?}, elapsed: {:?}, reply: {:?}",
60 strategy, elapsed, reply
61 );
62
63 reply
64 }
65}
66
67impl HandlerContext {
68 async fn perform_region_flush(
70 &self,
71 region_id: RegionId,
72 reason: Option<RegionFlushReason>,
73 ) -> Result<()> {
74 let request = RegionRequest::Flush(RegionFlushRequest {
75 reason,
76 ..Default::default()
77 });
78 self.region_server
79 .handle_request(region_id, request)
80 .await?;
81 Ok(())
82 }
83
84 async fn handle_flush_hint(
86 &self,
87 region_ids: Vec<RegionId>,
88 reason: Option<RegionFlushReason>,
89 ) {
90 let start_time = Instant::now();
91 for region_id in ®ion_ids {
92 let result = self.perform_region_flush(*region_id, reason).await;
93 match result {
94 Ok(_) => {}
95 Err(error::Error::RegionNotFound { .. }) => {
96 warn!(
97 "Received a flush region hint from meta, but target region: {} is not found.",
98 region_id
99 );
100 }
101 Err(err) => {
102 warn!("Failed to flush region: {}, error: {}", region_id, err);
103 }
104 }
105 }
106 let elapsed = start_time.elapsed();
107 debug!(
108 "Flush regions hint: {:?}, elapsed: {:?}",
109 region_ids, elapsed
110 );
111 }
112
113 async fn handle_flush_sync(
115 &self,
116 region_ids: Vec<RegionId>,
117 error_strategy: FlushErrorStrategy,
118 reason: Option<RegionFlushReason>,
119 ) -> FlushRegionReply {
120 let mut results = Vec::with_capacity(region_ids.len());
121
122 for region_id in region_ids {
123 let result = self.flush_single_region_sync(region_id, reason).await;
124
125 match &result {
126 Ok(_) => results.push((region_id, Ok(()))),
127 Err(err) => {
128 results.push((region_id, Err(InstructionError::from_error(err))));
129
130 if matches!(error_strategy, FlushErrorStrategy::FailFast) {
132 break;
133 }
134 }
135 }
136 }
137
138 FlushRegionReply::from_results(results)
139 }
140
141 async fn flush_single_region_sync(
143 &self,
144 region_id: RegionId,
145 reason: Option<RegionFlushReason>,
146 ) -> Result<()> {
147 let Some(writable) = self.region_server.is_region_leader(region_id) else {
149 return Err(RegionNotFoundSnafu { region_id }.build());
150 };
151
152 if !writable {
153 return Err(RegionNotReadySnafu { region_id }.build());
154 }
155
156 let region_server_moved = self.region_server.clone();
158 let register_result = self
159 .flush_tasks
160 .try_register(
161 region_id,
162 Box::pin(async move {
163 region_server_moved
164 .handle_request(
165 region_id,
166 RegionRequest::Flush(RegionFlushRequest {
167 reason,
168 ..Default::default()
169 }),
170 )
171 .await?;
172 Ok(())
173 }),
174 )
175 .await;
176
177 if register_result.is_busy() {
178 warn!("Another flush task is running for the region: {region_id}");
179 }
180
181 let mut watcher = register_result.into_watcher();
182 match self.flush_tasks.wait_until_finish(&mut watcher).await {
183 Ok(()) => Ok(()),
184 Err(err) => Err(UnexpectedSnafu {
185 violated: format!("Flush task failed: {err:?}"),
186 }
187 .build()),
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use std::sync::{Arc, RwLock};
195
196 use common_meta::instruction::{FlushErrorStrategy, FlushRegions};
197 use common_meta::kv_backend::memory::MemoryKvBackend;
198 use mito2::engine::MITO_ENGINE_NAME;
199 use store_api::storage::RegionId;
200
201 use super::*;
202 use crate::tests::{MockRegionEngine, mock_region_server};
203
204 type FlushedRequests = Arc<RwLock<Vec<(RegionId, Option<RegionFlushReason>)>>>;
205
206 #[tokio::test]
207 async fn test_handle_flush_region_hint() {
208 let flushed_requests: FlushedRequests = Arc::new(RwLock::new(Vec::new()));
209
210 let mock_region_server = mock_region_server();
211 let region_ids = (0..16).map(|i| RegionId::new(1024, i)).collect::<Vec<_>>();
212 for region_id in ®ion_ids {
213 let flushed_requests_ref = flushed_requests.clone();
214 let (mock_engine, _) =
215 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, move |region_engine| {
216 region_engine.handle_request_mock_fn =
217 Some(Box::new(move |region_id, request| {
218 let RegionRequest::Flush(request) = request else {
219 panic!("Expected flush request");
220 };
221 flushed_requests_ref
222 .write()
223 .unwrap()
224 .push((region_id, request.reason));
225 Ok(0)
226 }))
227 });
228 mock_region_server.register_test_region(*region_id, mock_engine);
229 }
230 let kv_backend = Arc::new(MemoryKvBackend::new());
231 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
232
233 let flush_instruction = FlushRegions::async_batch(region_ids.clone())
235 .with_reason(RegionFlushReason::RemoteWalPrune);
236 let reply = FlushRegionsHandler
237 .handle(&handler_context, flush_instruction)
238 .await;
239 assert!(reply.is_none()); let expected = region_ids
241 .iter()
242 .map(|region_id| (*region_id, Some(RegionFlushReason::RemoteWalPrune)))
243 .collect::<Vec<_>>();
244 assert_eq!(*flushed_requests.read().unwrap(), expected);
245
246 flushed_requests.write().unwrap().clear();
248 let not_found_region_ids = (0..2).map(|i| RegionId::new(2048, i)).collect::<Vec<_>>();
249 let flush_instruction = FlushRegions::async_batch(not_found_region_ids);
250 let reply = FlushRegionsHandler
251 .handle(&handler_context, flush_instruction)
252 .await;
253 assert!(reply.is_none());
254 assert!(flushed_requests.read().unwrap().is_empty());
255 }
256
257 #[tokio::test]
258 async fn test_handle_flush_region_sync_single() {
259 let flushed_requests: FlushedRequests = Arc::new(RwLock::new(Vec::new()));
260
261 let mock_region_server = mock_region_server();
262 let region_id = RegionId::new(1024, 1);
263
264 let flushed_requests_ref = flushed_requests.clone();
265 let (mock_engine, _) =
266 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, move |region_engine| {
267 region_engine.handle_request_mock_fn = Some(Box::new(move |region_id, request| {
268 let RegionRequest::Flush(request) = request else {
269 panic!("Expected flush request");
270 };
271 flushed_requests_ref
272 .write()
273 .unwrap()
274 .push((region_id, request.reason));
275 Ok(0)
276 }))
277 });
278 mock_region_server.register_test_region(region_id, mock_engine);
279 let kv_backend = Arc::new(MemoryKvBackend::new());
280 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
281
282 let flush_instruction =
283 FlushRegions::sync_single(region_id).with_reason(RegionFlushReason::Repartition);
284 let reply = FlushRegionsHandler
285 .handle(&handler_context, flush_instruction)
286 .await;
287 let flush_reply = reply.unwrap().expect_flush_regions_reply();
288 assert!(flush_reply.overall_success);
289 assert_eq!(flush_reply.results.len(), 1);
290 assert_eq!(flush_reply.results[0].0, region_id);
291 assert!(flush_reply.results[0].1.is_ok());
292 assert_eq!(
293 *flushed_requests.read().unwrap(),
294 vec![(region_id, Some(RegionFlushReason::Repartition))]
295 );
296 }
297
298 #[tokio::test]
299 async fn test_handle_flush_region_sync_batch_fail_fast() {
300 let flushed_region_ids: Arc<RwLock<Vec<RegionId>>> = Arc::new(RwLock::new(Vec::new()));
301
302 let mock_region_server = mock_region_server();
303 let region_ids = vec![
304 RegionId::new(1024, 1),
305 RegionId::new(1024, 2),
306 RegionId::new(1024, 3),
307 ];
308
309 let flushed_region_ids_ref = flushed_region_ids.clone();
311 let (mock_engine, _) =
312 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, move |region_engine| {
313 region_engine.handle_request_mock_fn = Some(Box::new(move |region_id, _request| {
314 flushed_region_ids_ref.write().unwrap().push(region_id);
315 Ok(0)
316 }))
317 });
318 mock_region_server.register_test_region(region_ids[0], mock_engine);
319 let kv_backend = Arc::new(MemoryKvBackend::new());
320 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
321
322 let flush_instruction =
324 FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::FailFast);
325 let reply = FlushRegionsHandler
326 .handle(&handler_context, flush_instruction)
327 .await;
328 let flush_reply = reply.unwrap().expect_flush_regions_reply();
329 assert!(!flush_reply.overall_success); assert!(flush_reply.results.len() <= region_ids.len());
332 }
333
334 #[tokio::test]
335 async fn test_handle_flush_region_sync_batch_try_all() {
336 let flushed_region_ids: Arc<RwLock<Vec<RegionId>>> = Arc::new(RwLock::new(Vec::new()));
337
338 let mock_region_server = mock_region_server();
339 let region_ids = vec![RegionId::new(1024, 1), RegionId::new(1024, 2)];
340
341 let flushed_region_ids_ref = flushed_region_ids.clone();
343 let (mock_engine, _) =
344 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, move |region_engine| {
345 region_engine.handle_request_mock_fn = Some(Box::new(move |region_id, _request| {
346 flushed_region_ids_ref.write().unwrap().push(region_id);
347 Ok(0)
348 }))
349 });
350 mock_region_server.register_test_region(region_ids[0], mock_engine);
351 let kv_backend = Arc::new(MemoryKvBackend::new());
352 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
353
354 let flush_instruction =
356 FlushRegions::sync_batch(region_ids.clone(), FlushErrorStrategy::TryAll);
357 let reply = FlushRegionsHandler
358 .handle(&handler_context, flush_instruction)
359 .await;
360 let flush_reply = reply.unwrap().expect_flush_regions_reply();
361 assert!(!flush_reply.overall_success); assert_eq!(flush_reply.results.len(), region_ids.len());
364 assert!(flush_reply.results[0].1.is_ok());
366 assert!(flush_reply.results[1].1.is_err());
367 }
368
369 #[test]
370 fn test_flush_regions_display() {
371 let region_id = RegionId::new(1024, 1);
372 let flush_regions = FlushRegions::sync_single(region_id);
373 let display = format!("{}", flush_regions);
374 assert_eq!(
375 display,
376 "FlushRegions(region_ids=[4398046511105(1024, 1)], strategy=Sync, error_strategy=FailFast, reason=None)"
377 );
378 }
379}