micromegas_analytics/lakehouse/
log_view.rs1use super::{
2 batch_update::PartitionCreationStrategy,
3 block_partition_spec::{BlockPartitionSpec, BlockProcessor, BlockProcessorMap},
4 blocks_view::BlocksView,
5 dataframe_time_bounds::{DataFrameTimeBounds, NamedColumnsTimeBounds},
6 jit_partitions::{JitPartitionConfig, write_partition_from_blocks},
7 lakehouse_context::LakehouseContext,
8 log_block_processor::LogBlockProcessor,
9 otel::logs_block_processor::OtelLogsBlockProcessor,
10 partition_cache::PartitionCache,
11 partition_source_data::fetch_partition_source_data,
12 view::{PartitionSpec, View, ViewMetadata},
13 view_factory::ViewMaker,
14};
15use crate::{
16 lakehouse::jit_partitions::{
17 BlockOrder, find_up_to_date_partitions, generate_process_jit_partitions,
18 },
19 log_entries_table::log_table_schema,
20 metadata::find_process,
21 time::{TimeRange, datetime_to_scalar},
22};
23use anyhow::{Context, Result};
24use async_trait::async_trait;
25use chrono::{DateTime, TimeDelta, Utc};
26use datafusion::{
27 arrow::datatypes::Schema,
28 logical_expr::{Between, Expr, col},
29};
30use micromegas_ingestion::web_ingestion_service::{FORMAT_OTLP_LOGS, FORMAT_TRANSIT};
31use micromegas_tracing::prelude::*;
32use std::collections::HashMap;
33use std::sync::Arc;
34use uuid::Uuid;
35
36const VIEW_SET_NAME: &str = "log_entries";
37const SCHEMA_VERSION: u8 = 7;
38lazy_static::lazy_static! {
39 static ref TIME_COLUMN: Arc<String> = Arc::new( String::from("time"));
40}
41
42fn log_processors() -> Arc<BlockProcessorMap> {
44 let mut m: BlockProcessorMap = HashMap::new();
45 m.insert(
46 FORMAT_TRANSIT,
47 Arc::new(LogBlockProcessor {}) as Arc<dyn BlockProcessor>,
48 );
49 m.insert(
50 FORMAT_OTLP_LOGS,
51 Arc::new(OtelLogsBlockProcessor {}) as Arc<dyn BlockProcessor>,
52 );
53 Arc::new(m)
54}
55
56#[derive(Debug)]
58pub struct LogViewMaker {}
59
60impl ViewMaker for LogViewMaker {
61 fn make_view(&self, view_instance_id: &str) -> Result<Arc<dyn View>> {
62 Ok(Arc::new(LogView::new(view_instance_id)?))
63 }
64
65 fn get_schema_hash(&self) -> Vec<u8> {
66 vec![SCHEMA_VERSION]
67 }
68
69 fn get_schema(&self) -> Arc<Schema> {
70 Arc::new(log_table_schema())
71 }
72}
73
74#[derive(Debug)]
76pub struct LogView {
77 view_set_name: Arc<String>,
78 view_instance_id: Arc<String>,
79 process_id: Option<sqlx::types::Uuid>,
80}
81
82impl LogView {
83 pub fn new(view_instance_id: &str) -> Result<Self> {
84 let process_id = if view_instance_id == "global" {
85 None
86 } else {
87 Some(Uuid::parse_str(view_instance_id).with_context(|| "Uuid::parse_str")?)
88 };
89
90 Ok(Self {
91 view_set_name: Arc::new(String::from(VIEW_SET_NAME)),
92 view_instance_id: Arc::new(view_instance_id.into()),
93 process_id,
94 })
95 }
96}
97
98#[async_trait]
99impl View for LogView {
100 fn get_view_set_name(&self) -> Arc<String> {
101 self.view_set_name.clone()
102 }
103
104 fn get_view_instance_id(&self) -> Arc<String> {
105 self.view_instance_id.clone()
106 }
107
108 async fn make_batch_partition_spec(
109 &self,
110 lakehouse: Arc<LakehouseContext>,
111 existing_partitions: Arc<PartitionCache>,
112 insert_range: TimeRange,
113 ) -> Result<Arc<dyn PartitionSpec>> {
114 if *self.view_instance_id != "global" {
115 anyhow::bail!("not supported for jit queries... should it?");
116 }
117 let source_data = Arc::new(
118 fetch_partition_source_data(
119 lakehouse.clone(),
120 existing_partitions,
121 insert_range,
122 "log",
123 )
124 .await
125 .with_context(|| "fetch_partition_source_data")?,
126 );
127 Ok(Arc::new(BlockPartitionSpec {
128 view_metadata: ViewMetadata {
129 view_set_name: self.view_set_name.clone(),
130 view_instance_id: self.view_instance_id.clone(),
131 file_schema_hash: self.get_file_schema_hash(),
132 },
133 schema: self.get_file_schema(),
134 insert_range,
135 source_data,
136 block_processors: log_processors(),
137 }))
138 }
139
140 fn get_file_schema_hash(&self) -> Vec<u8> {
141 vec![SCHEMA_VERSION]
142 }
143
144 fn get_file_schema(&self) -> Arc<Schema> {
145 Arc::new(log_table_schema())
146 }
147
148 #[span_fn]
149 async fn jit_update(
150 &self,
151 lakehouse: Arc<LakehouseContext>,
152 query_range: Option<TimeRange>,
153 ) -> Result<()> {
154 if *self.view_instance_id == "global" {
155 return Ok(());
157 }
158 let process = Arc::new(
159 find_process(
160 &lakehouse.lake().db_pool,
161 &self
162 .process_id
163 .with_context(|| "getting a view's process_id")?,
164 &lakehouse.default_audience(),
165 )
166 .await
167 .with_context(|| "find_process")?,
168 );
169 let query_range =
170 query_range.unwrap_or_else(|| TimeRange::new(process.start_time, chrono::Utc::now()));
171
172 let blocks_view = BlocksView::new(lakehouse.default_audience())?;
173 let all_partitions = generate_process_jit_partitions(
174 &JitPartitionConfig::default(),
175 lakehouse.clone(),
176 &blocks_view,
177 &query_range,
178 process.clone(),
179 "log",
180 )
181 .await
182 .with_context(|| "generate_process_jit_partitions")?;
183 let view_meta = ViewMetadata {
184 view_set_name: self.get_view_set_name(),
185 view_instance_id: self.get_view_instance_id(),
186 file_schema_hash: self.get_file_schema_hash(),
187 };
188 let block_processors = log_processors();
189
190 let up_to_date = find_up_to_date_partitions(
191 &lakehouse.lake().db_pool,
192 view_meta.clone(),
193 BlockOrder::InsertTime,
194 &all_partitions,
195 )
196 .await
197 .with_context(|| "find_up_to_date_partitions")?;
198 for (part, up_to_date) in all_partitions.into_iter().zip(up_to_date) {
199 if !up_to_date {
200 write_partition_from_blocks(
201 lakehouse.lake().clone(),
202 view_meta.clone(),
203 self.get_file_schema(),
204 part,
205 block_processors.clone(),
206 )
207 .await
208 .with_context(|| "write_partition_from_blocks")?;
209 }
210 }
211 Ok(())
212 }
213
214 fn make_time_filter(&self, begin: DateTime<Utc>, end: DateTime<Utc>) -> Result<Vec<Expr>> {
215 Ok(vec![Expr::Between(Between::new(
216 col("time").into(),
217 false,
218 Expr::Literal(datetime_to_scalar(begin), None).into(),
219 Expr::Literal(datetime_to_scalar(end), None).into(),
220 ))])
221 }
222
223 fn get_time_bounds(&self) -> Arc<dyn DataFrameTimeBounds> {
224 Arc::new(NamedColumnsTimeBounds::new(
225 TIME_COLUMN.clone(),
226 TIME_COLUMN.clone(),
227 ))
228 }
229
230 fn get_update_group(&self) -> Option<i32> {
231 if *(self.get_view_instance_id()) == "global" {
232 Some(2000)
233 } else {
234 None
235 }
236 }
237
238 fn get_max_partition_time_delta(&self, _strategy: &PartitionCreationStrategy) -> TimeDelta {
239 TimeDelta::hours(1)
240 }
241}