1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use chrono::{DateTime, DurationRound};
4use chrono::{TimeDelta, Utc};
5use micromegas_analytics::delete::delete_old_data;
6use micromegas_analytics::lakehouse::batch_update::materialize_partition_range;
7use micromegas_analytics::lakehouse::lakehouse_context::LakehouseContext;
8use micromegas_analytics::lakehouse::partition_cache::PartitionCache;
9use micromegas_analytics::lakehouse::temp::delete_expired_temporary_files;
10use micromegas_analytics::lakehouse::view::View;
11use micromegas_analytics::lakehouse::view_factory::ViewFactory;
12use micromegas_analytics::response_writer::ResponseWriter;
13use micromegas_analytics::time::TimeRange;
14use micromegas_tracing::prelude::*;
15use std::future::Future;
16use std::sync::Arc;
17use std::time::Duration;
18use tokio::task::{JoinError, JoinSet};
19
20use super::cron_task::{CronTask, TaskCallback};
21use super::pg_stats::PgStatsTask;
22
23type Views = Arc<Vec<Arc<dyn View>>>;
24
25#[span_fn]
30pub async fn materialize_all_views(
31 lakehouse: Arc<LakehouseContext>,
32 views: Views,
33 insert_range: TimeRange,
34 partition_time_delta: TimeDelta,
35) -> Result<()> {
36 let mut last_group = views.first().unwrap().get_update_group();
37 let mut partitions_all_views = Arc::new(
38 PartitionCache::fetch_overlapping_insert_range(&lakehouse.lake().db_pool, insert_range)
39 .await?,
40 );
41 let null_response_writer = Arc::new(ResponseWriter::new(None));
42 for view in &*views {
43 if view.get_update_group() != last_group {
44 last_group = view.get_update_group();
46 partitions_all_views = Arc::new(
47 PartitionCache::fetch_overlapping_insert_range(
48 &lakehouse.lake().db_pool,
50 insert_range,
51 )
52 .await?,
53 );
54 }
55 materialize_partition_range(
56 partitions_all_views.clone(),
57 lakehouse.clone(),
58 view.clone(),
59 insert_range,
60 partition_time_delta,
61 null_response_writer.clone(),
62 )
63 .await?;
64 }
65 Ok(())
66}
67
68pub struct EveryDayTask {
70 pub lakehouse: Arc<LakehouseContext>,
71 pub views: Views,
72}
73
74#[async_trait]
75impl TaskCallback for EveryDayTask {
76 #[span_fn]
77 async fn run(&self, task_scheduled_time: DateTime<Utc>) -> Result<()> {
78 let partition_time_delta = TimeDelta::days(1);
79 let trunc_task_time = task_scheduled_time.duration_trunc(partition_time_delta)?;
80 let begin_range = trunc_task_time - (partition_time_delta * 2);
81 let end_range = trunc_task_time;
82 materialize_all_views(
83 self.lakehouse.clone(),
84 self.views.clone(),
85 TimeRange::new(begin_range, end_range),
86 partition_time_delta,
87 )
88 .await
89 }
90}
91
92pub struct EveryHourTask {
94 pub lakehouse: Arc<LakehouseContext>,
95 pub views: Views,
96 pub retention_days: i32,
97}
98
99#[async_trait]
100impl TaskCallback for EveryHourTask {
101 #[span_fn]
102 async fn run(&self, task_scheduled_time: DateTime<Utc>) -> Result<()> {
103 delete_old_data(self.lakehouse.lake(), self.retention_days).await?;
104 delete_expired_temporary_files(self.lakehouse.lake().clone()).await?;
105
106 let partition_time_delta = TimeDelta::hours(1);
107 let trunc_task_time = task_scheduled_time.duration_trunc(partition_time_delta)?;
108 let begin_range = trunc_task_time - (partition_time_delta * 2);
109 let end_range = trunc_task_time;
110 materialize_all_views(
111 self.lakehouse.clone(),
112 self.views.clone(),
113 TimeRange::new(begin_range, end_range),
114 partition_time_delta,
115 )
116 .await
117 }
118}
119
120pub struct EveryMinuteTask {
122 pub lakehouse: Arc<LakehouseContext>,
123 pub views: Views,
124}
125
126#[async_trait]
127impl TaskCallback for EveryMinuteTask {
128 #[span_fn]
129 async fn run(&self, task_scheduled_time: DateTime<Utc>) -> Result<()> {
130 let partition_time_delta = TimeDelta::minutes(1);
131 let trunc_task_time = task_scheduled_time.duration_trunc(partition_time_delta)?;
132 let begin_range = trunc_task_time - (partition_time_delta * 2);
133 let end_range = trunc_task_time - partition_time_delta;
135 materialize_all_views(
136 self.lakehouse.clone(),
137 self.views.clone(),
138 TimeRange::new(begin_range, end_range),
139 partition_time_delta,
140 )
141 .await
142 }
143}
144
145pub struct EverySecondTask {
147 pub lakehouse: Arc<LakehouseContext>,
148 pub views: Views,
149}
150
151#[async_trait]
152impl TaskCallback for EverySecondTask {
153 #[span_fn]
154 async fn run(&self, task_scheduled_time: DateTime<Utc>) -> Result<()> {
155 let delay = Utc::now() - task_scheduled_time;
156 if delay > TimeDelta::seconds(10) {
157 warn!("skipping `seconds` task, delay={delay}");
159 return Ok(());
160 }
161 let partition_time_delta = TimeDelta::seconds(1);
162 let trunc_task_time = task_scheduled_time.duration_trunc(partition_time_delta)?;
163 let begin_range = trunc_task_time - (partition_time_delta * 2);
164 let end_range = trunc_task_time - partition_time_delta;
166 materialize_all_views(
167 self.lakehouse.clone(),
168 self.views.clone(),
169 TimeRange::new(begin_range, end_range),
170 partition_time_delta,
171 )
172 .await
173 }
174}
175
176fn log_task_result(res: Result<Result<Result<()>, JoinError>, JoinError>) {
183 match res {
184 Ok(Ok(Ok(()))) => {}
185 Ok(Ok(Err(e))) => error!("{e:?}"),
186 Ok(Err(e)) => error!("{e:?}"),
187 Err(e) => error!("{e:?}"),
188 }
189}
190
191async fn drain_task_set(task_set: &mut JoinSet<Result<Result<()>, JoinError>>) {
196 while let Some(res) = task_set.join_next().await {
197 log_task_result(res);
198 }
199}
200
201pub async fn run_tasks_forever<F>(mut tasks: Vec<CronTask>, max_parallelism: usize, shutdown: F)
206where
207 F: Future<Output = ()>,
208{
209 tokio::pin!(shutdown);
210 let mut task_set = JoinSet::new();
211 loop {
212 let mut next_task_run = Utc::now() + TimeDelta::days(2);
213 for task in &mut tasks {
214 if task.get_next_run() < Utc::now() {
215 task_set.spawn(task.spawn().await);
216 if task_set.len() >= max_parallelism {
217 tokio::select! {
218 res = task_set.join_next() => {
219 if let Some(res) = res {
220 log_task_result(res);
221 }
222 }
223 _ = &mut shutdown => {
224 drain_task_set(&mut task_set).await;
225 return;
226 }
227 }
228 }
229 }
230 let task_next_run = task.get_next_run();
231 if task_next_run < next_task_run {
232 next_task_run = task_next_run;
233 }
234 }
235 let time_until_next_task = next_task_run - Utc::now();
236 if time_until_next_task > TimeDelta::zero() {
237 match time_until_next_task
238 .to_std()
239 .with_context(|| "delay.to_std")
240 {
241 Ok(wait) => {
242 tokio::select! {
243 _ = tokio::time::sleep(wait) => {}
244 _ = &mut shutdown => {
245 drain_task_set(&mut task_set).await;
246 return;
247 }
248 }
249 }
250 Err(e) => warn!("{e:?}"),
251 }
252 } else {
253 tokio::select! {
256 biased;
257 _ = &mut shutdown => {
258 drain_task_set(&mut task_set).await;
259 return;
260 }
261 _ = tokio::task::yield_now() => {}
262 }
263 }
264 }
265}
266
267pub fn get_global_views_with_update_group(view_factory: &ViewFactory) -> Vec<Arc<dyn View>> {
272 view_factory
273 .get_global_views()
274 .iter()
275 .filter(|v| v.get_update_group().is_some())
276 .cloned()
277 .collect()
278}
279
280pub async fn daemon<F>(
297 lakehouse: Arc<LakehouseContext>,
298 mut views_to_update: Vec<Arc<dyn View>>,
299 retention_days: i32,
300 shutdown: F,
301 grace: Duration,
302) -> Result<()>
303where
304 F: Future<Output = ()> + Send + 'static,
305{
306 use super::shutdown::ShutdownFanout;
307
308 views_to_update.sort_by_key(|v| v.get_update_group().unwrap_or(i32::MAX));
309 let views = Arc::new(views_to_update);
310
311 let every_day = CronTask::new(
312 String::from("every_day"),
313 TimeDelta::days(1),
314 TimeDelta::hours(4),
315 Arc::new(EveryDayTask {
316 lakehouse: lakehouse.clone(),
317 views: views.clone(),
318 }),
319 )?;
320 let every_hour = CronTask::new(
321 String::from("every_hour"),
322 TimeDelta::hours(1),
323 TimeDelta::minutes(10),
324 Arc::new(EveryHourTask {
325 lakehouse: lakehouse.clone(),
326 views: views.clone(),
327 retention_days,
328 }),
329 )?;
330 let every_minute = CronTask::new(
331 String::from("every minute"),
332 TimeDelta::minutes(1),
333 TimeDelta::seconds(30),
334 Arc::new(EveryMinuteTask {
335 lakehouse: lakehouse.clone(),
336 views: views.clone(),
337 }),
338 )?;
339 let pg_stats = CronTask::new(
340 String::from("pg_stats"),
341 TimeDelta::minutes(1),
342 TimeDelta::seconds(15), Arc::new(PgStatsTask {
344 lakehouse: lakehouse.clone(),
345 }),
346 )?;
347 let every_second = CronTask::new(
348 String::from("every second"),
349 TimeDelta::seconds(1),
350 TimeDelta::milliseconds(500),
351 Arc::new(EverySecondTask { lakehouse, views }),
352 )?;
353
354 let fanout = ShutdownFanout::new(shutdown);
355 let grace_secs = grace.as_secs();
356
357 let mut runners = tokio::task::JoinSet::new();
358 runners.spawn(run_tasks_forever(vec![every_day], 1, fanout.subscribe()));
359 runners.spawn(run_tasks_forever(vec![every_hour], 1, fanout.subscribe()));
360 runners.spawn(run_tasks_forever(vec![every_minute], 5, fanout.subscribe()));
361 runners.spawn(run_tasks_forever(vec![every_second], 5, fanout.subscribe()));
362 runners.spawn(run_tasks_forever(vec![pg_stats], 1, fanout.subscribe()));
363
364 let deadline = {
365 let d = fanout.subscribe();
366 async move {
367 d.await;
368 tokio::time::sleep(grace).await;
369 }
370 };
371
372 tokio::select! {
373 _ = runners.join_all() => {
374 info!("daemon drain completed");
375 }
376 _ = deadline => {
377 warn!("daemon grace period of {grace_secs}s elapsed with work still in flight");
378 }
379 }
380 Ok(())
381}