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