Skip to main content

mz_compute/logging/
initialize.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5
6//! Initialization of logging dataflows.
7
8use std::cell::RefCell;
9use std::collections::BTreeMap;
10use std::rc::Rc;
11use std::time::{Duration, Instant};
12
13use differential_dataflow::VecCollection;
14use differential_dataflow::dynamic::pointstamp::PointStamp;
15use differential_dataflow::logging::{DifferentialEvent, DifferentialEventBuilder};
16use mz_compute_client::logging::{LogVariant, LoggingConfig};
17use mz_dyncfg::ConfigSet;
18use mz_ore::metrics::MetricsRegistry;
19use mz_repr::{Diff, Timestamp};
20use mz_storage_operators::persist_source::Subtime;
21use mz_timely_util::columnar::Column;
22use mz_timely_util::columnar::builder::ColumnBuilder;
23use mz_timely_util::operator::CollectionExt;
24use mz_timely_util::scope_label::ScopeExt;
25use prometheus::IntCounter;
26use timely::ContainerBuilder;
27use timely::container::{ContainerBuilder as _, PushInto};
28use timely::logging::{StartStop, TimelyEvent, TimelyEventBuilder, TimelyLogger};
29use timely::logging_core::{Logger, Registry};
30use timely::order::Product;
31use timely::progress::reachability::logging::{TrackerEvent, TrackerEventBuilder};
32
33use crate::arrangement::manager::TraceBundle;
34use crate::extensions::arrange::{KeyCollection, MzArrange};
35use crate::logging::compute::{ComputeEvent, ComputeEventBuilder};
36use crate::logging::{BatchLogger, EventQueue, SharedLoggingState};
37use crate::metrics::LoggingMetrics;
38use crate::render::errors::DataflowErrorSer;
39use crate::typedefs::ErrBatcher;
40
41/// Initialize logging dataflows.
42///
43/// Returns a logger for compute events, and for each `LogVariant` a trace bundle usable for
44/// retrieving logged records as well as the index of the exporting dataflow.
45pub fn initialize(
46    worker: &mut timely::worker::Worker,
47    config: &LoggingConfig,
48    metrics_registry: MetricsRegistry,
49    metrics: LoggingMetrics,
50    worker_config: Rc<ConfigSet>,
51    workers_per_process: usize,
52) -> LoggingTraces {
53    let interval_ms = std::cmp::max(1, config.interval.as_millis());
54
55    // Track time relative to the Unix epoch, rather than when the server
56    // started, so that the logging sources can be joined with tables and
57    // other real time sources for semi-sensible results.
58    let now = Instant::now();
59    let start_offset = std::time::SystemTime::now()
60        .duration_since(std::time::SystemTime::UNIX_EPOCH)
61        .expect("Failed to get duration since Unix epoch");
62
63    let mut context = LoggingContext {
64        worker,
65        config,
66        interval_ms,
67        now,
68        start_offset,
69        t_event_queue: EventQueue::new("t"),
70        r_event_queue: EventQueue::new("r"),
71        d_event_queue: EventQueue::new("d"),
72        c_event_queue: EventQueue::new("c"),
73        shared_state: Default::default(),
74        metrics_registry,
75        metrics,
76        worker_config,
77        workers_per_process,
78    };
79
80    // Depending on whether we should log the creation of the logging dataflows, we register the
81    // loggers with timely either before or after creating them.
82    let dataflow_index = context.worker.next_dataflow_index();
83    let traces = if config.log_logging {
84        context.register_loggers();
85        context.construct_dataflow()
86    } else {
87        let traces = context.construct_dataflow();
88        context.register_loggers();
89        traces
90    };
91
92    let compute_logger = worker.logger_for("materialize/compute").unwrap();
93    LoggingTraces {
94        traces,
95        dataflow_index,
96        compute_logger,
97    }
98}
99
100pub(super) type ReachabilityEvent = (usize, Vec<(usize, usize, bool, Timestamp, Diff)>);
101
102struct LoggingContext<'a> {
103    worker: &'a mut timely::worker::Worker,
104    config: &'a LoggingConfig,
105    interval_ms: u128,
106    now: Instant,
107    start_offset: Duration,
108    t_event_queue: EventQueue<Vec<(Duration, TimelyEvent)>>,
109    r_event_queue: EventQueue<Column<(Duration, ReachabilityEvent)>, 3>,
110    d_event_queue: EventQueue<Vec<(Duration, DifferentialEvent)>>,
111    c_event_queue: EventQueue<Column<(Duration, ComputeEvent)>>,
112    shared_state: Rc<RefCell<SharedLoggingState>>,
113    metrics_registry: MetricsRegistry,
114    metrics: LoggingMetrics,
115    worker_config: Rc<ConfigSet>,
116    workers_per_process: usize,
117}
118
119pub(crate) struct LoggingTraces {
120    /// Exported traces, by log variant.
121    pub traces: BTreeMap<LogVariant, TraceBundle>,
122    /// The index of the dataflow that exports the traces.
123    pub dataflow_index: usize,
124    /// The compute logger.
125    pub compute_logger: super::compute::Logger,
126}
127
128impl LoggingContext<'_> {
129    fn construct_dataflow(&mut self) -> BTreeMap<LogVariant, TraceBundle> {
130        let step_logger = self.step_logger();
131        self.worker.dataflow_core(
132            "Dataflow: logging",
133            step_logger,
134            Box::new(()),
135            |_, scope| {
136                let scope = scope.with_label();
137
138                let mut collections = BTreeMap::new();
139
140                let super::timely::Return {
141                    collections: timely_collections,
142                } = super::timely::construct(
143                    scope,
144                    self.config,
145                    self.t_event_queue.clone(),
146                    Rc::clone(&self.shared_state),
147                );
148                collections.extend(timely_collections);
149
150                let super::reachability::Return {
151                    collections: reachability_collections,
152                } = super::reachability::construct(scope, self.config, self.r_event_queue.clone());
153                collections.extend(reachability_collections);
154
155                let super::differential::Return {
156                    collections: differential_collections,
157                } = super::differential::construct(
158                    scope,
159                    self.config,
160                    self.d_event_queue.clone(),
161                    Rc::clone(&self.shared_state),
162                );
163                collections.extend(differential_collections);
164
165                let super::compute::Return {
166                    collections: compute_collections,
167                } = super::compute::construct(
168                    scope.clone(),
169                    scope.activations(),
170                    self.config,
171                    self.c_event_queue.clone(),
172                    Rc::clone(&self.shared_state),
173                );
174                collections.extend(compute_collections);
175
176                let super::prometheus::Return {
177                    collections: prometheus_collections,
178                } = super::prometheus::construct(
179                    scope,
180                    self.config,
181                    self.metrics_registry.clone(),
182                    self.now,
183                    self.start_offset,
184                    Rc::clone(&self.worker_config),
185                    self.workers_per_process,
186                );
187                collections.extend(prometheus_collections);
188
189                let super::resource_usage::Return {
190                    collections: resource_usage_collections,
191                } = super::resource_usage::construct(
192                    scope,
193                    self.config,
194                    self.now,
195                    self.start_offset,
196                    self.workers_per_process,
197                );
198                collections.extend(resource_usage_collections);
199
200                let errs = scope.scoped("logging errors", |scope| {
201                    let collection: KeyCollection<_, DataflowErrorSer, Diff> =
202                        VecCollection::empty(scope).into();
203                    collection
204                        .mz_arrange::<ErrBatcher<_, _>, _>("Arrange logging err")
205                        .trace
206                });
207
208                let traces = collections
209                    .into_iter()
210                    .map(|(log, collection)| {
211                        let bundle = TraceBundle::new(collection.trace, errs.clone())
212                            .with_drop(collection.token);
213                        (log, bundle)
214                    })
215                    .collect();
216                traces
217            },
218        )
219    }
220
221    /// Construct the timely logger that the worker hands to the logging dataflow itself.
222    ///
223    /// The logging dataflow's operators log nothing unless `log_logging` is set, but the worker
224    /// logs every scheduling of the dataflow as a whole to this logger, which observes the
225    /// duration of each scheduling. With `log_logging` set, returns the worker's timely logger
226    /// instead and observes nothing.
227    fn step_logger(&self) -> Option<TimelyLogger> {
228        if let Some(logger) = self.worker.logging() {
229            // Forwarding events from a second logger would re-timestamp them at flush time, and
230            // `mz_scheduling_elapsed` already covers the logging dataflow in this mode.
231            return Some(logger);
232        }
233
234        // `dataflow_core` allocates the dataflow's identifier first.
235        let dataflow_id = self.worker.peek_identifier();
236        let step_duration_seconds = self.metrics.step_duration_seconds.clone();
237        let mut started = None;
238        let logger = Logger::<TimelyEventBuilder>::new(
239            self.now,
240            self.start_offset,
241            move |_time, data: &mut Option<Vec<(Duration, TimelyEvent)>>| {
242                let Some(data) = data else { return };
243                for (time, event) in data.drain(..) {
244                    if let TimelyEvent::Schedule(schedule) = event
245                        && schedule.id == dataflow_id
246                    {
247                        match schedule.start_stop {
248                            StartStop::Start => started = Some(time),
249                            StartStop::Stop => {
250                                if let Some(start) = started.take() {
251                                    let elapsed = time.saturating_sub(start);
252                                    step_duration_seconds.observe(elapsed.as_secs_f64());
253                                }
254                            }
255                        }
256                    }
257                }
258            },
259        );
260        // The worker flushes only registered loggers at the end of each step. Unregistered,
261        // observations would wait for the logger's buffer to fill.
262        let mut register = self.worker.log_register().expect("Logging must be enabled");
263        register.insert_logger("materialize/logging-step", logger.clone());
264        Some(logger.into())
265    }
266
267    /// Construct a new reachability logger for timestamp type `T`.
268    ///
269    /// Inserts a logger with the name `timely/reachability/{type_name::<T>()}`, following
270    /// Timely naming convention.
271    fn register_reachability_logger<T: ExtractTimestamp>(
272        &self,
273        registry: &mut Registry,
274        index: usize,
275    ) {
276        let logger = self.reachability_logger::<T>(index);
277        let type_name = std::any::type_name::<T>();
278        registry.insert_logger(&format!("timely/reachability/{type_name}"), logger);
279    }
280
281    /// Register all loggers with the timely worker.
282    ///
283    /// Registers the timely, differential, compute, and reachability loggers.
284    fn register_loggers(&self) {
285        let t_logger = self.simple_logger::<TimelyEventBuilder>(
286            self.t_event_queue.clone(),
287            self.metrics.timely_records_total.clone(),
288        );
289        let d_logger = self.simple_logger::<DifferentialEventBuilder>(
290            self.d_event_queue.clone(),
291            self.metrics.differential_records_total.clone(),
292        );
293        let c_logger = self.simple_logger::<ComputeEventBuilder>(
294            self.c_event_queue.clone(),
295            self.metrics.compute_records_total.clone(),
296        );
297
298        let mut register = self.worker.log_register().expect("Logging must be enabled");
299        register.insert_logger("timely", t_logger);
300        // Note that each reachability logger has a unique index, this is crucial to avoid dropping
301        // data because the event link structure is not multi-producer safe.
302        self.register_reachability_logger::<Timestamp>(&mut register, 0);
303        self.register_reachability_logger::<Product<Timestamp, PointStamp<u64>>>(&mut register, 1);
304        self.register_reachability_logger::<(Timestamp, Subtime)>(&mut register, 2);
305        register.insert_logger("differential/arrange", d_logger);
306        register.insert_logger("materialize/compute", c_logger.clone());
307
308        self.shared_state.borrow_mut().compute_logger = Some(c_logger);
309    }
310
311    fn simple_logger<CB: ContainerBuilder>(
312        &self,
313        event_queue: EventQueue<CB::Container>,
314        records_total: IntCounter,
315    ) -> Logger<CB> {
316        let [link] = event_queue.links;
317        let mut logger = BatchLogger::new(link, self.interval_ms, records_total);
318        let activator = event_queue.activator.clone();
319        Logger::new(
320            self.now,
321            self.start_offset,
322            move |time, data: &mut Option<CB::Container>| {
323                if let Some(data) = data.take() {
324                    logger.publish_batch(data);
325                    // Count every batch towards the replay's activation threshold, so the logging
326                    // dataflow drains events in bounded chunks. Without this, the replay only
327                    // wakes once per logging interval and processes the whole interval's events
328                    // in one uninterruptible call, stalling every other dataflow on the worker.
329                    // The activator is worker-local and never unparks the thread: a threshold
330                    // crossed while flushing before a park takes effect on the next wakeup.
331                    activator.activate();
332                } else if logger.report_progress(*time) {
333                    activator.activate();
334                }
335            },
336        )
337    }
338
339    /// Construct a reachability logger for timestamp type `T`. The index must
340    /// refer to a unique link in the reachability event queue.
341    fn reachability_logger<T>(&self, index: usize) -> Logger<TrackerEventBuilder<T>>
342    where
343        T: ExtractTimestamp,
344    {
345        let link = Rc::clone(&self.r_event_queue.links[index]);
346        let mut logger = BatchLogger::new(
347            link,
348            self.interval_ms,
349            self.metrics.reachability_records_total.clone(),
350        );
351        let mut massaged = Vec::new();
352        let mut builder = ColumnBuilder::default();
353        let activator = self.r_event_queue.activator.clone();
354
355        let action = move |batch_time: &Duration, data: &mut Option<Vec<_>>| {
356            if let Some(data) = data {
357                // Handle data
358                for (time, event) in data.drain(..) {
359                    match event {
360                        TrackerEvent::SourceUpdate(update) => {
361                            massaged.extend(update.updates.iter().map(
362                                |(node, port, time, diff)| {
363                                    let is_source = true;
364                                    (*node, *port, is_source, T::extract(time), Diff::from(*diff))
365                                },
366                            ));
367
368                            builder.push_into((time, (update.tracker_id, &massaged)));
369                            massaged.clear();
370                        }
371                        TrackerEvent::TargetUpdate(update) => {
372                            massaged.extend(update.updates.iter().map(
373                                |(node, port, time, diff)| {
374                                    let is_source = false;
375                                    (*node, *port, is_source, time.extract(), Diff::from(*diff))
376                                },
377                            ));
378
379                            builder.push_into((time, (update.tracker_id, &massaged)));
380                            massaged.clear();
381                        }
382                    }
383                    while let Some(container) = builder.extract() {
384                        logger.publish_batch(std::mem::take(container));
385                        // See `simple_logger`.
386                        activator.activate();
387                    }
388                }
389            } else {
390                // Handle a flush
391                while let Some(container) = builder.finish() {
392                    logger.publish_batch(std::mem::take(container));
393                    // See `simple_logger`.
394                    activator.activate();
395                }
396
397                if logger.report_progress(*batch_time) {
398                    activator.activate();
399                }
400            }
401        };
402
403        Logger::new(self.now, self.start_offset, action)
404    }
405}
406
407/// Helper trait to extract a timestamp from various types of timestamp used in rendering.
408trait ExtractTimestamp: Clone + 'static {
409    /// Extracts the timestamp from the type.
410    fn extract(&self) -> Timestamp;
411}
412
413impl ExtractTimestamp for Timestamp {
414    fn extract(&self) -> Timestamp {
415        *self
416    }
417}
418
419impl ExtractTimestamp for Product<Timestamp, PointStamp<u64>> {
420    fn extract(&self) -> Timestamp {
421        self.outer
422    }
423}
424
425impl ExtractTimestamp for (Timestamp, Subtime) {
426    fn extract(&self) -> Timestamp {
427        self.0
428    }
429}