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}