1use common_telemetry::{debug, info, warn};
18use store_api::logstore::LogStore;
19use store_api::region_request::RegionTruncateRequest;
20use store_api::storage::RegionId;
21
22use crate::error::RegionNotFoundSnafu;
23use crate::manifest::action::{RegionTruncate, TruncateKind};
24use crate::region::RegionLeaderState;
25use crate::request::{DdlRequest, DiscardUnflushedResult, OptionOutputTx, TruncateResult};
26use crate::worker::RegionWorkerLoop;
27
28impl<S: LogStore> RegionWorkerLoop<S> {
29 pub(crate) async fn handle_truncate_request(
30 &mut self,
31 region_id: RegionId,
32 req: RegionTruncateRequest,
33 sender: OptionOutputTx,
34 ) {
35 let region = match self.regions.writable_non_staging_region(region_id) {
36 Ok(region) => region,
37 Err(e) => {
38 sender.send(Err(e));
39 return;
40 }
41 };
42
43 let (sender, req) = match self.compaction_scheduler.try_cancel_and_add_ddl(
45 region_id,
46 sender,
47 req,
48 DdlRequest::Truncate,
49 ) {
50 Ok(()) => {
51 self.listener.on_compaction_cancel_requested(region_id);
52 return;
53 }
54 Err(request) => request,
55 };
56
57 let (sender, req) = match self.flush_scheduler.try_cancel_and_add_ddl(
58 region_id,
59 sender,
60 req,
61 DdlRequest::Truncate,
62 ) {
63 Ok(()) => {
64 self.listener.on_flush_cancel_requested(region_id);
65 return;
66 }
67 Err(request) => request,
68 };
69
70 let version_data = region.version_control.current();
71
72 match req {
73 RegionTruncateRequest::All => {
74 info!("Try to fully truncate region {}", region_id);
75
76 let truncated_entry_id = version_data.last_entry_id;
77 let truncated_sequence = version_data.committed_sequence;
78
79 let truncate = RegionTruncate {
81 region_id,
82 kind: TruncateKind::All {
83 truncated_entry_id,
84 truncated_sequence,
85 },
86 timestamp_ms: None,
87 };
88
89 self.handle_manifest_truncate_action(region, truncate, sender);
90 }
91 RegionTruncateRequest::Unflushed => {
92 let memtables = &version_data.version.memtables;
93 if memtables.is_empty()
94 && version_data.version.flushed_entry_id == version_data.last_entry_id
95 && version_data.version.flushed_sequence == version_data.committed_sequence
96 {
97 self.update_topic_latest_entry_id(®ion);
98 let result = self
99 .wal
100 .obsolete(
101 region_id,
102 version_data.version.flushed_entry_id,
103 ®ion.provider,
104 )
105 .await
106 .map(|_| 0);
107 sender.send(result);
108 return;
109 }
110
111 let discarded_rows = memtables.num_rows();
112 let discarded_bytes =
113 (memtables.mutable_usage() + memtables.immutables_usage()) as u64;
114 warn!(
115 "Discarding unflushed data from region {}, entry_id: {}, sequence: {}, estimated rows: {}, estimated bytes: {}",
116 region_id,
117 version_data.last_entry_id,
118 version_data.committed_sequence,
119 discarded_rows,
120 discarded_bytes,
121 );
122
123 self.handle_manifest_discard_unflushed_action(
124 region,
125 version_data.last_entry_id,
126 version_data.committed_sequence,
127 discarded_rows,
128 discarded_bytes,
129 sender,
130 );
131 }
132 RegionTruncateRequest::ByTimeRanges { time_ranges } => {
133 info!(
134 "Try to partially truncate region {} by time ranges: {:?}",
135 region_id, time_ranges
136 );
137 let mut files_to_truncate = Vec::new();
139 for level in version_data.version.ssts.levels() {
140 for file in level.files() {
141 let file_time_range = file.time_range();
142 let is_subset = time_ranges.iter().any(|(start, end)| {
145 file_time_range.0 >= *start && file_time_range.1 <= *end
146 });
147
148 if is_subset {
149 files_to_truncate.push(file.meta_ref().clone());
150 }
151 }
152 }
153 debug!(
154 "Found {} files to partially truncate in region {}",
155 files_to_truncate.len(),
156 region_id
157 );
158
159 if files_to_truncate.is_empty() {
161 info!("No files to truncate in region {}", region_id);
162 sender.send(Ok(0));
165 return;
166 }
167 let truncate = RegionTruncate {
168 region_id,
169 kind: TruncateKind::Partial {
170 files_to_remove: files_to_truncate,
171 },
172 timestamp_ms: None,
173 };
174 self.handle_manifest_truncate_action(region, truncate, sender);
175 }
176 };
177 }
178
179 pub(crate) async fn handle_truncate_result(&mut self, truncate_result: TruncateResult) {
181 let region_id = truncate_result.region_id;
182 let Some(region) = self.regions.get_region(region_id) else {
183 truncate_result.sender.send(
184 RegionNotFoundSnafu {
185 region_id: truncate_result.region_id,
186 }
187 .fail(),
188 );
189 return;
190 };
191
192 region.switch_state_to_writable(RegionLeaderState::Truncating);
194
195 match truncate_result.result {
196 Ok(()) => {
197 region
199 .version_control
200 .truncate(truncate_result.kind.clone());
201 }
202 Err(e) => {
203 truncate_result.sender.send(Err(e));
205 return;
206 }
207 }
208
209 self.flush_scheduler.on_region_truncated(region_id);
211 self.compaction_scheduler.on_region_truncated(region_id);
213 self.index_build_scheduler
215 .on_region_truncated(region_id)
216 .await;
217
218 if let TruncateKind::All {
219 truncated_entry_id,
220 truncated_sequence: _,
221 } = &truncate_result.kind
222 {
223 if let Err(e) = self
225 .wal
226 .obsolete(region_id, *truncated_entry_id, ®ion.provider)
227 .await
228 {
229 truncate_result.sender.send(Err(e));
230 return;
231 }
232 }
233
234 info!(
235 "Complete truncating region: {}, kind: {:?}.",
236 region_id, truncate_result.kind
237 );
238
239 truncate_result.sender.send(Ok(0));
240 }
241
242 pub(crate) async fn handle_discard_unflushed_result(&mut self, result: DiscardUnflushedResult) {
244 let region_id = result.region_id;
245 let Some(region) = self.regions.get_region(region_id) else {
246 result.sender.send(RegionNotFoundSnafu { region_id }.fail());
247 return;
248 };
249
250 region.switch_state_to_writable(RegionLeaderState::Truncating);
251
252 if let Err(e) = result.result {
253 result.sender.send(Err(e));
254 return;
255 }
256
257 region
258 .version_control
259 .discard_unflushed(result.discarded_entry_id, result.discarded_sequence);
260
261 self.flush_scheduler.on_region_truncated(region_id);
267 self.compaction_scheduler.on_region_truncated(region_id);
268 self.index_build_scheduler
269 .on_region_truncated(region_id)
270 .await;
271 self.update_topic_latest_entry_id(®ion);
272
273 self.notify_group();
277 self.handle_stalled_requests().await;
278
279 if let Err(e) = self
280 .wal
281 .obsolete(region_id, result.discarded_entry_id, ®ion.provider)
282 .await
283 {
284 result.sender.send(Err(e));
285 return;
286 }
287
288 warn!(
289 "Discarded unflushed data from region {}, entry_id: {}, sequence: {}, estimated rows: {}, estimated bytes: {}",
290 region_id,
291 result.discarded_entry_id,
292 result.discarded_sequence,
293 result.discarded_rows,
294 result.discarded_bytes,
295 );
296 result.sender.send(Ok(0));
297 }
298}