Skip to main content

mz_timely_util/funded_spine/
spine_fueled.rs

1// Copyright (c) 2015 Frank McSherry
2// Copyright Materialize, Inc. and contributors. All rights reserved.
3//
4// Use of this software is governed by the Business Source License
5// included in the LICENSE file.
6//
7// As of the Change Date specified in that file, in accordance with
8// the Business Source License, use of this software will be governed
9// by the Apache License, Version 2.0.
10//
11// Portions of this file are derived from the fueled spine in
12// differential-dataflow. The original source code was retrieved from:
13//
14//     https://github.com/TimelyDataflow/differential-dataflow/blob/0b41ba7198048d7455d6d96c6021ee6c83f22a81/differential-dataflow/src/trace/implementations/spine_fueled.rs
15//
16// The original source code is subject to the terms of the MIT license, a copy
17// of which can be found in the LICENSE file at the root of this repository.
18
19//! An append-only collection of update batches.
20//!
21//! The `Spine` is a general-purpose trace implementation based on collection and merging
22//! immutable batches of updates. It is generic with respect to the batch type, and can be
23//! instantiated for any implementor of [`SpineBatch`].
24//!
25//! ## Design
26//!
27//! This spine is represented as a list of layers, where each element in the list is either
28//!
29//!   1. MergeState::Vacant  empty
30//!   2. MergeState::Single  a single span
31//!   3. MergeState::Double  a pair of spans
32//!
33//! Each "span" has the option to be `None`, indicating a non-span that nonetheless acts
34//! as a number of updates proportionate to the level at which it exists (for bookkeeping).
35//!
36//! Each of the spans at layer i contains at most 2^i elements. The sequence of spans
37//! should have the upper bound of one match the lower bound of the next. Spans may carry
38//! no updates, with matching upper and lower bounds, as a bookkeeping mechanism.
39//!
40//! Each batch at layer i is treated as if it contains exactly 2^i elements, even though it
41//! may actually contain fewer elements. This allows us to decouple the physical representation
42//! from logical amounts of effort invested in each batch. It allows us to begin compaction and
43//! to reduce the number of updates, without compromising our ability to continue to move
44//! updates along the spine. We are explicitly making the trade-off that while some batches
45//! might compact at lower levels, we want to treat them as if they contained their full set of
46//! updates for accounting reasons (to apply work to higher levels).
47//!
48//! We maintain the invariant that for any in-progress merge at level k there should be fewer
49//! than 2^k records at levels lower than k. That is, even if we were to apply an unbounded
50//! amount of effort to those records, we would not have enough records to prompt a merge into
51//! the in-progress merge. Ideally, we maintain the extended invariant that for any in-progress
52//! merge at level k, the remaining effort required (number of records minus applied effort) is
53//! less than the number of records that would need to be added to reach 2^k records in layers
54//! below.
55//!
56//! ## Mathematics
57//!
58//! When a merge is initiated, there should be a non-negative *deficit* of updates before the layers
59//! below could plausibly produce a new batch for the currently merging layer. We must determine a
60//! factor of proportionality, so that newly arrived updates provide at least that amount of "fuel"
61//! towards the merging layer, so that the merge completes before lower levels invade.
62//!
63//! ### Deficit:
64//!
65//! A new merge is initiated only in response to the completion of a prior merge, or the introduction
66//! of new records from outside. The latter case is special, and will maintain our invariant trivially,
67//! so we will focus on the former case.
68//!
69//! When a merge at level k completes, assuming we have maintained our invariant then there should be
70//! fewer than 2^k records at lower levels. The newly created merge at level k+1 will require up to
71//! 2^k+2 units of work, and should not expect a new batch until strictly more than 2^k records are
72//! added. This means that a factor of proportionality of four should be sufficient to ensure that
73//! the merge completes before a new merge is initiated.
74//!
75//! When new records get introduced, we will need to roll up any batches at lower levels, which we
76//! treat as the introduction of records. Each of these virtual records introduced should either be
77//! accounted for the fuel it should contribute, as it results in the promotion of batches closer to
78//! in-progress merges.
79//!
80//! ### Fuel sharing
81//!
82//! We like the idea of applying fuel preferentially to merges at *lower* levels, under the idea that
83//! they are easier to complete, and we benefit from fewer total merges in progress. This does delay
84//! the completion of merges at higher levels, and may not obviously be a total win. If we choose to
85//! do this, we should make sure that we correctly account for completed merges at low layers: they
86//! should still extract fuel from new updates even though they have completed, at least until they
87//! have paid back any "debt" to higher layers by continuing to provide fuel as updates arrive.
88
89use differential_dataflow::lattice::Lattice;
90use differential_dataflow::logging::Logger;
91use differential_dataflow::trace::{Span, Description, ExertionLogic, Trace, TraceReader};
92use differential_dataflow::trace::implementations::spine_fueled::{Merger, SpineBatch};
93
94use ::timely::dataflow::operators::generic::OperatorInfo;
95use ::timely::progress::{Antichain, frontier::AntichainRef};
96use ::timely::order::PartialOrder;
97
98/// The number of updates in a span: its batch's length, or zero when it carries none.
99///
100/// This is how the spine asks whether a span has updates. `Span::has_updates` answers the
101/// weaker question of whether a batch is *present*, which agrees with this only for
102/// producers that report absence when they built nothing; the spine can measure instead,
103/// and does.
104fn span_len<B: SpineBatch>(span: &Span<B::Time, B>) -> usize {
105    span.inner.as_ref().map(|b| b.len()).unwrap_or(0)
106}
107
108/// An append-only collection of update tuples.
109///
110/// A spine maintains a small number of immutable collections of update tuples, merging the collections when
111/// two have similar sizes. In this way, it allows the addition of more tuples, which may then be merged with
112/// other immutable collections.
113pub struct Spine<B: SpineBatch> {
114    operator: OperatorInfo,
115    logger: Option<Logger>,
116    logical_frontier: Antichain<B::Time>,   // Times after which the trace must accumulate correctly.
117    physical_frontier: Antichain<B::Time>,  // Times after which the trace must be able to subset its inputs.
118    merging: Vec<MergeState<B>>,            // Several possibly shared collections of updates.
119    pending: Vec<Span<B::Time, B>>,        // Spans at times in advance of `frontier`.
120    upper: Antichain<B::Time>,
121    effort: usize,
122    activator: Option<timely::scheduling::activate::Activator>,
123    /// Parameters to `exert_logic`, containing tuples of `(index, count, length)`.
124    exert_logic_param: Vec<(usize, usize, usize)>,
125    /// Logic to indicate whether and how many records we should introduce in the absence of actual updates.
126    exert_logic: Option<ExertionLogic>,
127    /// Fuel funded by inserted updates and not yet spent. It does not decay:
128    /// it accumulates in proportion to total input, and later turns spend it
129    /// down, so the total stays proportional to input.
130    consolidation_credit: usize,
131    /// Policy allowances funded by inserted batches and not yet spent.
132    progress_grants: usize,
133}
134
135/// Fuel each inserted update credits for policy-requested effort, scaled by
136/// the spine's effort multiplier, which is 1 unless set with `with_effort`.
137/// A granted request spends the effort the policy returns, so credit funds
138/// one grant per `effort / 8` inserted updates, about 125 at the cluster
139/// policy's effort of 1000. Small batches fund no grant from credit, and the
140/// banked allowances per batch are what bound them. The multiple matches the
141/// least `introduce_batch` already funds per update for active merges,
142/// which is `8 << level`.
143///
144/// NOTE: the credit bounds how many requests are granted, not the work each
145/// grant causes. A grant with no merge in progress introduces a virtual
146/// batch, which can start a merge up into the largest layer, and that merge
147/// then finishes unfunded. See the module documentation of `funded_spine`.
148const CONSOLIDATION_CREDIT_PER_UPDATE: usize = 8;
149
150/// Policy allowances each inserted batch funds regardless of its size.
151const CONSOLIDATION_GRANTS_PER_PROGRESS: usize = 8;
152
153/// Most progress-funded allowances held, eight batches' worth. The bank fills to
154/// this cap while the trace is reduced, so a long quiet spell funds at most this
155/// burst of consolidation when input returns.
156const MAX_BANKED_PROGRESS_GRANTS: usize = 8 * CONSOLIDATION_GRANTS_PER_PROGRESS;
157
158impl<B: SpineBatch+Clone+'static> TraceReader for Spine<B> {
159
160    type Time = B::Time;
161    type Batch = B;
162
163    fn spans_through(&mut self, upper: AntichainRef<Self::Time>) -> Option<Vec<Span<Self::Time, B>>> {
164
165        // If `upper` is the minimum frontier, we can return an empty cursor.
166        // This can happen with operators that are written to expect the ability to acquire cursors
167        // for their prior frontiers, and which start at `[T::minimum()]`, such as `Reduce`, sadly.
168        if upper.less_equal(&<Self::Time as timely::progress::Timestamp>::minimum()) {
169            return Some(Vec::new());
170        }
171
172        // The supplied `upper` should have the property that for each of our
173        // batch `lower` and `upper` frontiers, the supplied upper is comparable
174        // to the frontier; it should not be incomparable, because the frontiers
175        // that we created form a total order. If it is, there is a bug.
176        //
177        // We should acquire a cursor including all batches whose upper is less
178        // or equal to the supplied upper, excluding all batches whose lower is
179        // greater or equal to the supplied upper, and if a batch straddles the
180        // supplied upper it had better be empty.
181
182        // We shouldn't grab a cursor into a closed trace, right?
183        assert!(self.logical_frontier.borrow().len() > 0);
184
185        // Check that `upper` is greater or equal to `self.physical_frontier`.
186        // Otherwise, the cut could be in `self.merging` and it is user error anyhow.
187        assert!(PartialOrder::less_equal(&self.physical_frontier.borrow(), &upper));
188
189        let mut storage = Vec::new();
190
191        for merge_state in self.merging.iter().rev() {
192            match merge_state {
193                MergeState::Double(variant) => {
194                    match variant {
195                        MergeVariant::InProgress(batch1, batch2, _, _) => {
196                            if span_len(batch1) > 0 {
197                                storage.push(batch1.clone());
198                            }
199                            if span_len(batch2) > 0 {
200                                storage.push(batch2.clone());
201                            }
202                        },
203                        MergeVariant::Complete(Some((batch, _))) => {
204                            if span_len(batch) > 0 {
205                                storage.push(batch.clone());
206                            }
207                        }
208                        MergeVariant::Complete(None) => { },
209                    }
210                },
211                MergeState::Single(Some(batch)) => {
212                    if span_len(batch) > 0 {
213                        storage.push(batch.clone());
214                    }
215                },
216                MergeState::Single(None) => { },
217                MergeState::Vacant => { },
218            }
219        }
220
221        for batch in self.pending.iter() {
222
223            if span_len(batch) > 0 {
224
225                // For a non-empty `batch`, it is a catastrophic error if `upper`
226                // requires some-but-not-all of the updates in the batch. We can
227                // determine this from `upper` and the lower and upper bounds of
228                // the batch itself.
229                //
230                // TODO: It is not clear if this is the 100% correct logic, due
231                // to the possible non-total-orderedness of the frontiers.
232
233                let include_lower = PartialOrder::less_equal(&batch.lower().borrow(), &upper);
234                let include_upper = PartialOrder::less_equal(&batch.upper().borrow(), &upper);
235
236                if include_lower != include_upper && upper != batch.lower().borrow() {
237                    panic!("`spans_through`: `upper` straddles batch");
238                }
239
240                // include pending batches
241                if include_upper {
242                    storage.push(batch.clone());
243                }
244            }
245        }
246
247        Some(storage)
248    }
249    #[inline]
250    fn set_logical_compaction(&mut self, frontier: AntichainRef<B::Time>) {
251        self.logical_frontier.clear();
252        self.logical_frontier.extend(frontier.iter().cloned());
253    }
254    #[inline]
255    fn get_logical_compaction(&mut self) -> AntichainRef<'_, B::Time> { self.logical_frontier.borrow() }
256    #[inline]
257    fn set_physical_compaction(&mut self, frontier: AntichainRef<'_, B::Time>) {
258        // We should never request to rewind the frontier.
259        debug_assert!(PartialOrder::less_equal(&self.physical_frontier.borrow(), &frontier), "FAIL\tthrough frontier !<= new frontier {:?} {:?}\n", self.physical_frontier, frontier);
260        self.physical_frontier.clear();
261        self.physical_frontier.extend(frontier.iter().cloned());
262        self.consider_merges();
263    }
264    #[inline]
265    fn get_physical_compaction(&mut self) -> AntichainRef<'_, B::Time> { self.physical_frontier.borrow() }
266
267    #[inline]
268    fn map_spans<F: FnMut(&Span<Self::Time, B>)>(&self, mut f: F) {
269        for batch in self.merging.iter().rev() {
270            match batch {
271                MergeState::Double(MergeVariant::InProgress(batch1, batch2, _, _)) => { f(batch1); f(batch2); },
272                MergeState::Double(MergeVariant::Complete(Some((batch, _)))) => { f(batch) },
273                MergeState::Single(Some(batch)) => { f(batch) },
274                _ => { },
275            }
276        }
277        for batch in self.pending.iter() {
278            f(batch);
279        }
280    }
281}
282
283impl<B: SpineBatch+Clone+'static> Trace for Spine<B> {
284    fn new(
285        info: ::timely::dataflow::operators::generic::OperatorInfo,
286        logging: Option<differential_dataflow::logging::Logger>,
287        activator: Option<timely::scheduling::activate::Activator>,
288    ) -> Self {
289        Self::with_effort(1, info, logging, activator)
290    }
291
292    /// Apply some amount of effort to trace maintenance.
293    ///
294    /// Whether and how much effort to apply is determined by `self.exert_logic`, a closure the user can set.
295    fn exert(&mut self) {
296        // If there is work to be done, ...
297        self.tidy_layers();
298        // Determine whether we should apply effort independent of updates.
299        if let Some(effort) = self.exert_effort() {
300            // Optional effort is paid for by inserted updates and frontier advances
301            // while the input is open; only a closed input lifts that bound. A
302            // merge already in progress must complete before anything else lands
303            // at its level, so advancing it adds no work and proceeds unfunded.
304            // Funding is still spent first when available, so a funded spine does
305            // the same work either way.
306            let funded = self.upper.borrow().is_empty() || self.spend_funding(effort);
307            if funded || self.merging.iter().any(|b| b.is_double()) {
308                self.grant(effort);
309            }
310            // Unlike upstream, a declined request does not reactivate the
311            // operator. There is no funded work until the next insert, and
312            // the insert schedules the operator itself.
313        }
314    }
315
316    fn set_exert_logic(&mut self, logic: ExertionLogic) {
317        self.exert_logic = Some(logic);
318    }
319
320    // Ideally, this method acts as insertion of `span`, even if we are not yet able to begin
321    // merging its batch. This means it is a good time to perform amortized work proportional
322    // to the size of that batch.
323    fn insert(&mut self, span: Span<Self::Time, B>) {
324
325        // Log the introduction of a batch.
326        self.logger.as_ref().map(|l| l.log(differential_dataflow::logging::BatchEvent {
327            operator: self.operator.global_id,
328            length: span_len(&span)
329        }));
330
331        assert!(span.lower() != span.upper());
332        assert_eq!(span.lower(), &self.upper);
333
334        self.upper.clone_from(span.upper());
335        self.consolidation_credit = self.consolidation_credit.saturating_add(
336            span_len(&span).saturating_mul(CONSOLIDATION_CREDIT_PER_UPDATE).saturating_mul(self.effort),
337        );
338        self.progress_grants = (self.progress_grants + CONSOLIDATION_GRANTS_PER_PROGRESS)
339            .min(MAX_BANKED_PROGRESS_GRANTS);
340
341        // TODO: Consolidate or discard spans with no updates.
342        self.pending.push(span);
343        self.consider_merges();
344    }
345
346    /// Completes the trace with a final empty batch.
347    fn close(&mut self) {
348        if !self.upper.borrow().is_empty() {
349            self.insert(Span::empty(self.upper.clone(), Antichain::new()));
350        }
351    }
352}
353
354// Drop implementation allows us to log batch drops, to zero out maintained totals.
355impl<B: SpineBatch> Drop for Spine<B> {
356    fn drop(&mut self) {
357        self.drop_batches();
358    }
359}
360
361
362impl<B: SpineBatch> Spine<B> {
363    /// Drops and logs batches. Used in drop.
364    fn drop_batches(&mut self) {
365        if let Some(logger) = &self.logger {
366            for batch in self.merging.drain(..) {
367                match batch {
368                    MergeState::Single(Some(batch)) => {
369                        logger.log(differential_dataflow::logging::DropEvent {
370                            operator: self.operator.global_id,
371                            length: span_len(&batch),
372                        });
373                    },
374                    MergeState::Double(MergeVariant::InProgress(batch1, batch2, _, _)) => {
375                        logger.log(differential_dataflow::logging::DropEvent {
376                            operator: self.operator.global_id,
377                            length: span_len(&batch1),
378                        });
379                        logger.log(differential_dataflow::logging::DropEvent {
380                            operator: self.operator.global_id,
381                            length: span_len(&batch2),
382                        });
383                    },
384                    MergeState::Double(MergeVariant::Complete(Some((batch, _)))) => {
385                        logger.log(differential_dataflow::logging::DropEvent {
386                            operator: self.operator.global_id,
387                            length: span_len(&batch),
388                        });
389                    }
390                    _ => { },
391                }
392            }
393            for batch in self.pending.drain(..) {
394                logger.log(differential_dataflow::logging::DropEvent {
395                    operator: self.operator.global_id,
396                    length: span_len(&batch),
397                });
398            }
399        }
400    }
401}
402
403impl<B: SpineBatch> Spine<B> {
404    /// Determine the amount of effort we should exert in the absence of updates.
405    ///
406    /// This method prepares an iterator over batches, including the level, count, and length of each layer.
407    /// It supplies this to `self.exert_logic`, who produces the response of the amount of exertion to apply.
408    fn exert_effort(&mut self) -> Option<usize> {
409        self.exert_logic.as_ref().and_then(|exert_logic| {
410            self.exert_logic_param.clear();
411            self.exert_logic_param.extend(self.merging.iter().enumerate().rev().map(|(index, batch)| {
412                match batch {
413                    MergeState::Vacant => (index, 0, 0),
414                    MergeState::Single(_) => (index, 1, batch.len()),
415                    MergeState::Double(_) => (index, 2, batch.len()),
416                }
417            }));
418
419            (exert_logic)(&self.exert_logic_param[..])
420        })
421    }
422
423    /// Allocates a fueled `Spine` with a specified effort multiplier.
424    ///
425    /// This trace will merge batches progressively, with each inserted batch applying a multiple
426    /// of the batch's length in effort to each merge. The `effort` parameter is that multiplier.
427    /// This value should be at least one for the merging to happen; a value of zero is not helpful.
428    pub fn with_effort(
429        mut effort: usize,
430        operator: OperatorInfo,
431        logger: Option<differential_dataflow::logging::Logger>,
432        activator: Option<timely::scheduling::activate::Activator>,
433    ) -> Self {
434
435        // Zero effort is .. not smart.
436        if effort == 0 { effort = 1; }
437
438        Spine {
439            operator,
440            logger,
441            logical_frontier: Antichain::from_elem(<B::Time as timely::progress::Timestamp>::minimum()),
442            physical_frontier: Antichain::from_elem(<B::Time as timely::progress::Timestamp>::minimum()),
443            merging: Vec::new(),
444            pending: Vec::new(),
445            upper: Antichain::from_elem(<B::Time as timely::progress::Timestamp>::minimum()),
446            effort,
447            activator,
448            exert_logic_param: Vec::default(),
449            exert_logic: None,
450            consolidation_credit: 0,
451            progress_grants: 0,
452        }
453    }
454
455    /// Spend funding for one policy request, returning whether any was available.
456    fn spend_funding(&mut self, effort: usize) -> bool {
457        if self.consolidation_credit >= effort {
458            self.consolidation_credit -= effort;
459            true
460        } else if self.progress_grants > 0 {
461            self.progress_grants -= 1;
462            true
463        } else {
464            false
465        }
466    }
467
468    /// Apply one policy allowance: fuel for active merges, or a virtual introduction.
469    fn grant(&mut self, effort: usize) {
470        // If any merges exist, we can directly call `apply_fuel`.
471        if self.merging.iter().any(|b| b.is_double()) {
472            self.apply_fuel(&mut (effort as isize));
473        }
474        // Otherwise, we'll need to introduce fake updates to move merges along.
475        else {
476            // Introduce an empty batch with roughly *effort number of virtual updates.
477            let level = effort.next_power_of_two().trailing_zeros() as usize;
478            self.introduce_batch(None, level);
479        }
480        // We were not in reduced form, so let's check again in the future.
481        if let Some(activator) = &self.activator {
482            activator.activate();
483        }
484    }
485
486    /// Migrate data from `self.pending` into `self.merging`.
487    ///
488    /// This method reflects on the bookmarks held by others that may prevent merging, and in the
489    /// case that new batches can be introduced to the pile of mergeable batches, it gets on that.
490    #[inline(never)]
491    fn consider_merges(&mut self) {
492
493        // TODO: Consider merging pending batches before introducing them.
494        // TODO: We could use a `VecDeque` here to draw from the front and append to the back.
495        while !self.pending.is_empty() && PartialOrder::less_equal(self.pending[0].upper(), &self.physical_frontier)
496        {
497            // Batch can be taken in optimized insertion.
498            // Otherwise it is inserted normally at the end of the method.
499            let mut batch = Some(self.pending.remove(0));
500
501            // If `batch` and the most recently inserted batch are both empty, we can just fuse them.
502            // We can also replace a structurally empty batch with this empty batch, preserving the
503            // apparent record count but now with non-trivial lower and upper bounds.
504            if span_len(batch.as_ref().unwrap()) == 0 {
505                if let Some(position) = self.merging.iter().position(|m| !m.is_vacant()) {
506                    if self.merging[position].is_single() && self.merging[position].len() == 0 {
507                        self.insert_at(batch.take(), position);
508                        let merged = self.complete_at(position);
509                        self.merging[position] = MergeState::Single(merged);
510                    }
511                }
512            }
513
514            // Normal insertion for the batch.
515            if let Some(batch) = batch {
516                let index = span_len(&batch).next_power_of_two();
517                self.introduce_batch(Some(batch), index.trailing_zeros() as usize);
518            }
519        }
520
521        // Having performed all of our work, if we should perform more work reschedule ourselves.
522        if self.exert_effort().is_some() {
523            if let Some(activator) = &self.activator {
524                activator.activate();
525            }
526        }
527    }
528
529    /// Introduces a batch at an indicated level.
530    ///
531    /// The level indication is often related to the size of the batch, but
532    /// it can also be used to artificially fuel the computation by supplying
533    /// empty batches at non-trivial indices, to move merges along.
534    pub fn introduce_batch(&mut self, batch: Option<Span<B::Time, B>>, batch_index: usize) {
535
536        // Step 0.  Determine an amount of fuel to use for the computation.
537        //
538        //          Fuel is used to drive maintenance of the data structure,
539        //          and in particular are used to make progress through merges
540        //          that are in progress. The amount of fuel to use should be
541        //          proportional to the number of records introduced, so that
542        //          we are guaranteed to complete all merges before they are
543        //          required as arguments to merges again.
544        //
545        //          The fuel use policy is negotiable, in that we might aim
546        //          to use relatively less when we can, so that we return
547        //          control promptly, or we might account more work to larger
548        //          batches. Not clear to me which are best, of if there
549        //          should be a configuration knob controlling this.
550
551        // The amount of fuel to use is proportional to 2^batch_index, scaled
552        // by a factor of self.effort which determines how eager we are in
553        // performing maintenance work. We need to ensure that each merge in
554        // progress receives fuel for each introduced batch, and so multiply
555        // by that as well.
556        if batch_index > 32 { println!("Large batch index: {}", batch_index); }
557
558        // We believe that eight units of fuel is sufficient for each introduced
559        // record, accounted as four for each record, and a potential four more
560        // for each virtual record associated with promoting existing smaller
561        // batches. We could try and make this be less, or be scaled to merges
562        // based on their deficit at time of instantiation. For now, we remain
563        // conservative.
564        let mut fuel = 8 << batch_index;
565        // Scale up by the effort parameter, which is calibrated to one as the
566        // minimum amount of effort.
567        fuel *= self.effort;
568        // Convert to an `isize` so we can observe any fuel shortfall.
569        let mut fuel = fuel as isize;
570
571        // Step 1.  Apply fuel to each in-progress merge.
572        //
573        //          Before we can introduce new updates, we must apply any
574        //          fuel to in-progress merges, as this fuel is what ensures
575        //          that the merges will be complete by the time we insert
576        //          the updates.
577        self.apply_fuel(&mut fuel);
578
579        // Step 2.  We must ensure the invariant that adjacent layers do not
580        //          contain two batches will be satisfied when we insert the
581        //          batch. We forcibly completing all merges at layers lower
582        //          than and including `batch_index`, so that the new batch
583        //          is inserted into an empty layer.
584        //
585        //          We could relax this to "strictly less than `batch_index`"
586        //          if the layer above has only a single batch in it, which
587        //          seems not implausible if it has been the focus of effort.
588        //
589        //          This should be interpreted as the introduction of some
590        //          volume of fake updates, and we will need to fuel merges
591        //          by a proportional amount to ensure that they are not
592        //          surprised later on. The number of fake updates should
593        //          correspond to the deficit for the layer, which perhaps
594        //          we should track explicitly.
595        self.roll_up(batch_index);
596
597        // Step 3. This insertion should be into an empty layer. It is a
598        //         logical error otherwise, as we may be violating our
599        //         invariant, from which all wonderment derives.
600        self.insert_at(batch, batch_index);
601
602        // Step 4. Tidy the largest layers.
603        //
604        //         It is important that we not tidy only smaller layers,
605        //         as their ascension is what ensures the merging and
606        //         eventual compaction of the largest layers.
607        self.tidy_layers();
608    }
609
610    /// Ensures that an insertion at layer `index` will succeed.
611    ///
612    /// This method is subject to the constraint that all existing batches
613    /// should occur at higher levels, which requires it to "roll up" batches
614    /// present at lower levels before the method is called. In doing this,
615    /// we should not introduce more virtual records than 2^index, as that
616    /// is the amount of excess fuel we have budgeted for completing merges.
617    fn roll_up(&mut self, index: usize) {
618
619        // Ensure entries sufficient for `index`.
620        while self.merging.len() <= index {
621            self.merging.push(MergeState::Vacant);
622        }
623
624        // We only need to roll up if there are non-vacant layers.
625        if self.merging[.. index].iter().any(|m| !m.is_vacant()) {
626
627            // Collect and merge all batches at layers up to but not including `index`.
628            let mut merged = None;
629            for i in 0 .. index {
630                self.insert_at(merged, i);
631                merged = self.complete_at(i);
632            }
633
634            // The merged results should be introduced at level `index`, which should
635            // be ready to absorb them (possibly creating a new merge at the time).
636            self.insert_at(merged, index);
637
638            // If the insertion results in a merge, we should complete it to ensure
639            // the upcoming insertion at `index` does not panic.
640            if self.merging[index].is_double() {
641                let merged = self.complete_at(index);
642                self.insert_at(merged, index + 1);
643            }
644        }
645    }
646
647    /// Applies an amount of fuel to merges in progress.
648    ///
649    /// The supplied `fuel` is for each in progress merge, and if we want to spend
650    /// the fuel non-uniformly (e.g. prioritizing merges at low layers) we could do
651    /// so in order to maintain fewer batches on average (at the risk of completing
652    /// merges of large batches later, but tbh probably not much later).
653    pub fn apply_fuel(&mut self, fuel: &mut isize) {
654        // For the moment our strategy is to apply fuel independently to each merge
655        // in progress, rather than prioritizing small merges. This sounds like a
656        // great idea, but we need better accounting in place to ensure that merges
657        // that borrow against later layers but then complete still "acquire" fuel
658        // to pay back their debts.
659        for index in 0 .. self.merging.len() {
660            // Give each level independent fuel, for now.
661            let mut fuel = *fuel;
662            // Pass along various logging stuffs, in case we need to report success.
663            self.merging[index].work(&mut fuel);
664            // `fuel` could have a deficit at this point, meaning we over-spent when
665            // we took a merge step. We could ignore this, or maintain the deficit
666            // and account future fuel against it before spending again. It isn't
667            // clear why that would be especially helpful to do; we might want to
668            // avoid overspends at multiple layers in the same invocation (to limit
669            // latencies), but there is probably a rich policy space here.
670
671            // If a merge completes, we can immediately merge it in to the next
672            // level, which is "guaranteed" to be complete at this point, by our
673            // fueling discipline.
674            if self.merging[index].is_complete() {
675                let complete = self.complete_at(index);
676                self.insert_at(complete, index+1);
677            }
678        }
679    }
680
681    /// Inserts a batch at a specific location.
682    ///
683    /// This is a non-public internal method that can panic if we try and insert into a
684    /// layer which already contains two batches (and is still in the process of merging).
685    fn insert_at(&mut self, batch: Option<Span<B::Time, B>>, index: usize) {
686        // Ensure the spine is large enough.
687        while self.merging.len() <= index {
688            self.merging.push(MergeState::Vacant);
689        }
690
691        // Insert the batch at the location.
692        match self.merging[index].take() {
693            MergeState::Vacant => {
694                self.merging[index] = MergeState::Single(batch);
695            }
696            MergeState::Single(old) => {
697                // Log the initiation of a merge.
698                self.logger.as_ref().map(|l| l.log(
699                    differential_dataflow::logging::MergeEvent {
700                        operator: self.operator.global_id,
701                        scale: index,
702                        length1: old.as_ref().map(span_len).unwrap_or(0),
703                        length2: batch.as_ref().map(span_len).unwrap_or(0),
704                        complete: None,
705                    }
706                ));
707                // Two present spans merge without work when at least one carries no batch. Such a
708                // merge is complete at once, and `complete_at` will not log it, so we do so here.
709                if let (Some(old), Some(batch)) = (&old, &batch) {
710                    if old.inner.is_none() || batch.inner.is_none() {
711                        self.logger.as_ref().map(|l| l.log(
712                            differential_dataflow::logging::MergeEvent {
713                                operator: self.operator.global_id,
714                                scale: index,
715                                length1: span_len(old),
716                                length2: span_len(batch),
717                                complete: Some(span_len(old) + span_len(batch)),
718                            }
719                        ));
720                    }
721                }
722                let compaction_frontier = self.logical_frontier.borrow();
723                self.merging[index] = MergeState::begin_merge(old, batch, compaction_frontier);
724            }
725            MergeState::Double(_) => {
726                panic!("Attempted to insert batch into incomplete merge!")
727            }
728        };
729    }
730
731    /// Completes and extracts what ever is at layer `index`.
732    fn complete_at(&mut self, index: usize) -> Option<Span<B::Time, B>> {
733        if let Some((merged, inputs)) = self.merging[index].complete() {
734            if let Some((input1, input2)) = inputs {
735                // Log the completion of a merge from existing parts.
736                self.logger.as_ref().map(|l| l.log(
737                    differential_dataflow::logging::MergeEvent {
738                        operator: self.operator.global_id,
739                        scale: index,
740                        length1: span_len(&input1),
741                        length2: span_len(&input2),
742                        complete: Some(span_len(&merged)),
743                    }
744                ));
745            }
746            Some(merged)
747        }
748        else {
749            None
750        }
751    }
752
753    /// Attempts to draw down large layers to size appropriate layers.
754    fn tidy_layers(&mut self) {
755
756        // If the largest layer is complete (not merging), we can attempt
757        // to draw it down to the next layer. This is permitted if we can
758        // maintain our invariant that below each merge there are at most
759        // half the records that would be required to invade the merge.
760        if !self.merging.is_empty() {
761            let mut length = self.merging.len();
762            if self.merging[length-1].is_single() {
763
764                // To move a batch down, we require that it contain few
765                // enough records that the lower level is appropriate,
766                // and that moving the batch would not create a merge
767                // violating our invariant.
768
769                let appropriate_level = self.merging[length-1].len().next_power_of_two().trailing_zeros() as usize;
770
771                // Continue only as far as is appropriate
772                while appropriate_level < length-1 {
773
774                    match self.merging[length-2].take() {
775                        // Vacant or structurally empty batches can be absorbed.
776                        MergeState::Vacant | MergeState::Single(None) => {
777                            self.merging.remove(length-2);
778                            length = self.merging.len();
779                        }
780                        // Single batches may initiate a merge, if sizes are
781                        // within bounds, but terminate the loop either way.
782                        MergeState::Single(Some(batch)) => {
783
784                            // Determine the number of records that might lead
785                            // to a merge. Importantly, this is not the number
786                            // of actual records, but the sum of upper bounds
787                            // based on indices.
788                            let mut smaller = 0;
789                            for (index, batch) in self.merging[..(length-2)].iter().enumerate() {
790                                match batch {
791                                    MergeState::Vacant => { },
792                                    MergeState::Single(_) => { smaller += 1 << index; },
793                                    MergeState::Double(_) => { smaller += 2 << index; },
794                                }
795                            }
796
797                            if smaller <= (1 << length) / 8 {
798                                self.merging.remove(length-2);
799                                self.insert_at(Some(batch), length-2);
800                            }
801                            else {
802                                self.merging[length-2] = MergeState::Single(Some(batch));
803                            }
804                            return;
805                        }
806                        // If a merge is in progress there is nothing to do.
807                        MergeState::Double(state) => {
808                            self.merging[length-2] = MergeState::Double(state);
809                            return;
810                        }
811                    }
812                }
813            }
814        }
815    }
816}
817
818
819/// Describes the state of a layer.
820///
821/// A layer can be empty, contain a single span, or contain a pair of spans
822/// that are in the process of merging into a span for the next layer.
823///
824/// Note the two distinct kinds of nothing here: `Single(None)` is a *structural*
825/// absence, a stand-in for virtual updates with no description at all, used for
826/// fuel bookkeeping; a present span carrying no batch is a *recorded* update-free
827/// interval of time, with a real description.
828enum MergeState<B: SpineBatch> {
829    /// An empty layer, containing no updates.
830    Vacant,
831    /// A layer containing a single span.
832    ///
833    /// The `None` variant is used to represent a structurally empty layer present
834    /// to ensure the progress of maintenance work.
835    Single(Option<Span<B::Time, B>>),
836    /// A layer containing two spans, in the process of merging.
837    Double(MergeVariant<B>),
838}
839
840impl<B: SpineBatch> MergeState<B> {
841
842    /// The number of actual updates contained in the level.
843    fn len(&self) -> usize {
844        match self {
845            MergeState::Single(Some(b)) => span_len(b),
846            MergeState::Double(MergeVariant::InProgress(b1,b2,_,_)) => span_len(b1) + span_len(b2),
847            MergeState::Double(MergeVariant::Complete(Some((b, _)))) => span_len(b),
848            _ => 0,
849        }
850    }
851
852    /// True only for the MergeState::Vacant variant.
853    fn is_vacant(&self) -> bool {
854        if let MergeState::Vacant = self { true } else { false }
855    }
856
857    /// True only for the MergeState::Single variant.
858    fn is_single(&self) -> bool {
859        if let MergeState::Single(_) = self { true } else { false }
860    }
861
862    /// True only for the MergeState::Double variant.
863    fn is_double(&self) -> bool {
864        if let MergeState::Double(_) = self { true } else { false }
865    }
866
867    /// Immediately complete any merge.
868    ///
869    /// The result is either a batch, if there is a non-trivial batch to return
870    /// or `None` if there is no meaningful batch to return. This does not distinguish
871    /// between Vacant entries and structurally empty batches, which should be done
872    /// with the `is_complete()` method.
873    ///
874    /// There is the additional option of input batches.
875    fn complete(&mut self) -> Option<(Span<B::Time, B>, Option<(Span<B::Time, B>, Span<B::Time, B>)>)>  {
876        match std::mem::replace(self, MergeState::Vacant) {
877            MergeState::Vacant => None,
878            MergeState::Single(batch) => batch.map(|b| (b, None)),
879            MergeState::Double(variant) => variant.complete(),
880        }
881    }
882
883    /// True iff the layer is a complete merge, ready for extraction.
884    fn is_complete(&mut self) -> bool {
885        if let MergeState::Double(MergeVariant::Complete(_)) = self {
886            true
887        }
888        else {
889            false
890        }
891    }
892
893    /// Performs a bounded amount of work towards a merge.
894    ///
895    /// If the merge completes, the resulting batch is returned.
896    /// If a batch is returned, it is the obligation of the caller
897    /// to correctly install the result.
898    fn work(&mut self, fuel: &mut isize) {
899        // We only perform work for merges in progress.
900        if let MergeState::Double(layer) = self {
901            layer.work(fuel)
902        }
903    }
904
905    /// Extract the merge state, typically temporarily.
906    fn take(&mut self) -> Self {
907        std::mem::replace(self, MergeState::Vacant)
908    }
909
910    /// Initiates the merge of an "old" batch with a "new" batch.
911    ///
912    /// The upper frontier of the old batch should match the lower
913    /// frontier of the new batch, with the resulting batch describing
914    /// their composed interval, from the lower frontier of the old
915    /// batch to the upper frontier of the new batch.
916    ///
917    /// Either batch may be `None` which corresponds to a structurally
918    /// empty batch whose upper and lower frontiers are equal. This
919    /// option exists purely for bookkeeping purposes, and no computation
920    /// is performed to merge the two batches.
921    fn begin_merge(batch1: Option<Span<B::Time, B>>, batch2: Option<Span<B::Time, B>>, compaction_frontier: AntichainRef<B::Time>) -> MergeState<B> {
922        let variant =
923        match (batch1, batch2) {
924            (Some(batch1), Some(batch2)) => {
925                assert!(batch1.upper() == batch2.lower());
926                let since = batch1.desc.since().join(batch2.desc.since()).join(&compaction_frontier.to_owned());
927                let description = Description::new(batch1.lower().clone(), batch2.upper().clone(), since);
928                match (&batch1.inner, &batch2.inner) {
929                    (Some(source1), Some(source2)) => {
930                        let merger = B::Merger::new(source1, source2, description.since().borrow());
931                        MergeVariant::InProgress(batch1, batch2, description, merger)
932                    }
933                    // With at most one side carrying updates there is nothing to merge, and the
934                    // surviving updates' times are not advanced by the compaction frontier.
935                    _ => {
936                        let inner = batch1.inner.or(batch2.inner);
937                        MergeVariant::Complete(Some((Span::new(description, inner), None)))
938                    }
939                }
940            }
941            (None, Some(x)) => MergeVariant::Complete(Some((x, None))),
942            (Some(x), None) => MergeVariant::Complete(Some((x, None))),
943            (None, None) => MergeVariant::Complete(None),
944        };
945
946        MergeState::Double(variant)
947    }
948}
949
950enum MergeVariant<B: SpineBatch> {
951    /// Describes an actual in-progress merge between two non-trivial batches.
952    ///
953    /// Beyond the two source batches, this records the description their merge
954    /// will bear (their composed interval, with the compaction frontier as its
955    /// `since`) and the merger itself.
956    InProgress(Span<B::Time, B>, Span<B::Time, B>, Description<B::Time>, B::Merger),
957    /// A merge that requires no further work. May or may not represent a non-trivial batch.
958    Complete(Option<(Span<B::Time, B>, Option<(Span<B::Time, B>, Span<B::Time, B>)>)>),
959}
960
961impl<B: SpineBatch> MergeVariant<B> {
962
963    /// Completes and extracts the batch, unless structurally empty.
964    ///
965    /// The result is either `None`, for structurally empty batches,
966    /// or a batch and optionally input batches from which it derived.
967    fn complete(mut self) -> Option<(Span<B::Time, B>, Option<(Span<B::Time, B>, Span<B::Time, B>)>)> {
968        let mut fuel = isize::MAX;
969        self.work(&mut fuel);
970        if let MergeVariant::Complete(batch) = self { batch }
971        else { panic!("Failed to complete a merge!"); }
972    }
973
974    /// Applies some amount of work, potentially completing the merge.
975    ///
976    /// In case the work completes, the source batches are returned.
977    /// This allows the caller to manage the released resources.
978    fn work(&mut self, fuel: &mut isize) {
979        let variant = std::mem::replace(self, MergeVariant::Complete(None));
980        if let MergeVariant::InProgress(b1, b2, description, mut merge) = variant {
981            merge.work(b1.inner.as_ref().unwrap(), b2.inner.as_ref().unwrap(), fuel);
982            if *fuel > 0 {
983                let inner = merge.done();
984                *self = MergeVariant::Complete(Some((Span::new(description, inner), Some((b1, b2)))));
985            }
986            else {
987                *self = MergeVariant::InProgress(b1, b2, description, merge);
988            }
989        }
990        else {
991            *self = variant;
992        }
993    }
994}