Skip to main content

differential_dataflow/trace/implementations/
spine_fueled.rs

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