Cache-Efficient Sweeping-Based Interval Joins for Extended Allen Relation Predicates (Extended Version)
CCache-Efficient Sweeping-Based Interval Joins for Extended AllenRelation Predicates (Extended Version)
Danila Piatov , Sven Helmer , Anton Dign¨os , and Fabio Persia Free University of Bozen-Bolzano, Bozen-Bolzano, Italy [email protected] University of Zurich, Zurich, Switzerland [email protected]
Abstract
We develop a family of efficient plane-sweeping interval joinalgorithms that can evaluate a wide range of interval predicatessuch as Allen’s relationships and parameterized relationships.Our technique is based on a framework, components of whichcan be flexibly combined in different manners to support therequired interval relation. In temporal databases, our algo-rithms can exploit a well-known and flexible access method,the Timeline Index, thus expanding the set of operations itsupports even further. Additionally, employing a compact datastructure, the gapless hash map, we utilize the CPU cacheefficiently. In an experimental evaluation, we show that ourapproach is several times faster and scales better than state-of-the-art techniques, while being much better suited for real-timeevent processing.
Temporal data is found in many financial, business, and sci-entific applications running on top of database managementsystems (DBMSs), i.e., supporting these applications throughefficient temporal operator implementations is crucial. Forexample, Kaufmann states that there are several temporalqueries in the hundred most expensive queries executed onSAP ERP [23], many of which have to be implemented inthe application layer, as the underlying infrastructure does notdirectly support the processing of temporal data. Accordingto [23], customers of SAP desperately need (advanced) tempo-ral operators for efficiently running queries pertaining to legal,compliance, and auditing processes.Although the introduction of temporal operators into theSQL standard has started with SQL:2011 [29], the providedimplementation is far from complete or lacking in performance.There is a renewed interest in temporal data processing, andresearchers and developers are busy filling the gaps. One exam-ple are join operators involving temporal predicates: there areseveral recent publications on overlap interval joins [8, 14, 36].However, this is not the only possible join predicate for match-ing (temporal) intervals. Allen defined a set of binary relationsbetween intervals originally designed for reasoning about in- r r r r t Figure 1: Example temporal relation r tervals and interval-based temporal descriptions of events [2].These relations have been extended for event detection byparameterizing them [20]. Strictly speaking, all these relation-ships could be formulated in regular SQL WHERE clauses (seealso the right-hand column of Table 1 for a formal definitionof Allen’s relations and extensions). The evaluation of thesepredicates using the implementation present in contemporaryrelational database management systems (RDBMSs) wouldbe very inefficient, though, as a lot of inequality predicatesare involved [26]. We also note that the predicates in theaforementioned overlap interval joins ([8, 14, 36]) only checkfor any form of overlap between intervals. Basically, they donot distinguish between many of the relationships defined byAllen and do not cover the
BEFORE and
MEETS relations at all.Additionally, many of the approaches so far lack parameter-ized versions, in which further range-based constraints can beformulated directly in the join predicate.In Figure 1 we see an example relation showing which em-ployees ( r , r , r , and r ) were working on a certain projectduring which month. The tuple validity intervals (visualizedas line segments) are as follows: tuple r is valid from time 0to time 2 (exclusive) with a starting timestamp T s = 0 and anending timestamp T e = 2 , tuple r is valid on interval [0 , with T s = 0 and T e = 5 , and so on. With a simple overlap in-terval join we can merely detect that r and r worked togetheron the project for some time (as did r and r ). However, wemay also be interested in who started working at the same time( r and r ), who started working after someone had alreadyleft ( r coming in after r had left and r starting after every-one else had left), or even who took over from someone else,i.e., the ending of one interval coincides with the beginning ofanother one ( r and r ). For even more sophisticated queries,we may want to add thresholds: who worked together withsomeone else and then left the project within two months of1 a r X i v : . [ c s . D B ] A ug he other person ( r and r ).Allen’s relationships are not only used in temporal databases,but also in event detection systems for temporal pattern match-ing [20, 27, 28]. In this context, it is also important to beable to specify concrete timeframes within which certain pat-terns are encountered, introducing the need for parameterizedversions of Allen’s relationships. For instance, K¨orber et al.use their TPStream framework for analyzing real-time traf-fic data [27, 28], while we previously employed a languagecalled ISEQL to specify events in a video surveillance con-text [20]. Event detection motivated us to develop an approachthat is also applicable for event stream processing environ-ments, meaning that our join operators are non-blocking andproduce output tuples as early as logically possible, withoutnecessarily waiting for the intervals to finish. Moreover, wedemonstrate how these joins can be processed efficiently intemporal databases using a sweeping-based framework that issupported by cache-efficient data structures and by a TimelineIndex — a flexible and general temporal index — supporting awide range of temporal operators, used in a prototype imple-mentation of SAP HANA [25]. We therefore extend the set ofoperations Timeline Index supports, increasing its usefulnesseven further.In particular, we make the following contributions:• We develop a family of plane-sweeping interval join al-gorithms that can evaluate a wide range of interval rela-tionship predicates going even beyond Allen’s relations.• At the core of this framework sits one base algorithm,called interval-timestamp join, that can be parameterizedusing a set of iterators traversing a Timeline Index. Thisoffers an elegant way of configuring and adapting the basealgorithm for processing different interval join predicates,improving code maintainability.• Additionally, our algorithm utilizes the CPU cache effi-ciently, relying on a compact hash map data structure formanaging data during the processing of the join operation.Together with the index, in many cases we can achievelinear run time.• In an experimental evaluation, we show that our approachis faster than state-of-the-art methods: an order of magni-tude faster than a direct competitor and several orders ofmagnitude faster than an inequality join. There is a renewed interest in employing Allen’s interval rela-tions in different areas, e.g. for describing complex temporalevents in event detection frameworks [20, 27, 28] as well asfor querying temporal relationships in knowledge graphs viaSPARQL [11]. One reason is that it is more natural for humansto work with chunks of information, such as labeled intervals,rather than individual values [21].
Leung and Muntz worked on efficiently implementing joinswith predicates based on Allen’s relations in the 1990s [30, 31]and it turns out that their solution is still competitive today. Infact, they also apply a plane-sweeping strategy, but impose atotal order on the tuples of a relation. Theoretically, there arefour different orders tuples can be sorted in for this algorithm: T s ascending, T s descending, T e ascending, and T e descending.When joining two relations, they can be sorted in differentorders independently of each other.The actual algorithm is similar to a sort-merge join. A tupleis read from one of the relations (outer or inner) and placedinto the corresponding set of active tuples for that relation.Each tuple in the set of the other relation is checked whetherit matches the tuple that was just read. When a matching pairis found, it is transferred to the result set. While searchingfor matching tuples, the algorithm also performs a garbagecollection, removing tuples that will no longer be able to finda matching partner. (Not all join predicates and sort ordersallow for a garbage collection, though.) A heuristic, basedon tuple distribution and garbage collection statistics, decidesfrom which relation to read the next tuple. In a follow-uppaper, further strategies for parallelization and temporal queryprocessing are discussed [32].In contrast to our approach, in which we handle tuple start-ing and ending events separately (an idea also covered moregenerally in [15, 33, 39]), the algorithm of Leung and Muntzrequires streams of whole tuples. A tuple is not complete untilits ending endpoint T e is encountered. This has a major impactfor applications such as real-time event detection. Waiting fora tuple to finish can delay the whole joining process, as tuplesfollowing it in the sort order cannot be reported yet.Chekol et al. claim to cover the complete set of Allen’srelations in their join algorithm for intervals in the context ofSPARQL, but the description for some relations is missing [11].It seems they are using our algorithm from [36] as a basis.They are not able to handle parameterized versions and haveto create different indexes for different relations, though.There is also research on integrating Allen’s predicate inter-val joins in a MapReduce framework [10, 37]. However, theseapproaches focus on the effective distribution of the data overMapReduce workers rather than on effective joins. One of the earliest publications to look at performance issues oftemporal joins is by Segev and Gunadhi [38, 18], who comparedifferent sort-merge and nested-loop implementations of theirevent join. They refined existing algorithms by applying anauxiliary access method called an append-only tree, assumingthat temporal data is only appended to existing relations andnever updated or deleted.Some of the work on spatial joins can also be applied tointerval joins. Arge et al. [4] used a sweeping-based intervaljoin algorithm as a building block for a two-dimensional spatialrectangle join, but did not investigate it as a standalone interval2oin. It was picked up again by Gao et al. [16], who give ataxonomy of temporal join operators and provide a surveyand an empirical study of a considerable number of non-index-based temporal join algorithms, such as variants of nested-loop,sort-merge, and partitioning-based methods.The fastest partitioning join, the overlap interval partition-ing (OIP) join, was developed by Dign¨os et al. [14]. The(temporal) domain is divided into equally-sized granules andadjacent granules can be combined to form containers of dif-ferent sizes. Intervals are assigned to the smallest containerthat covers them and the join algorithm then matches intervalsin overlapping containers.The Timeline Index, introduced by Kaufmann et al. [25], andthe supported temporal operators have also received renewedattention recently. Kaufmann et al. showed that a single indexper temporal relation supports such operations as time travel,temporal joins, overlap interval joins and temporal aggregationon constant intervals (temporal grouping by instant). Thework was done in the context of developing a prototype for acommercial temporal database and later extended to supportbitemporal data [24].In earlier work [36], we defined a simplified, but function-ally equivalent version of the Timeline Index, called EndpointIndex (from now on we will use both terms interchangeably).We introduced a cache-optimized algorithm for the overlapinterval join, based on the Endpoint Index, and showed thatthe Timeline Index is not only a universal index that supportsmany operations, but that it can also outperform the state-of-the-art specialized indexes (including [14]). The technique ofsweeping-based algorithms has recently been applied to tempo-ral aggregation as well [9, 35], extending the set of operationssupported by the Timeline Index even further.The basic idea of Bouros and Mamoulis is to do a forwardscan on the input collection to determine the join partners,hence their algorithm is called forward scan (FS) join [8]. Incontrast, our approach does a backward scan by traversingalready encountered intervals, which have to be stored in ahash table. In FS, both input relations, r and s , are sorted onthe starting endpoint of each interval and then the algorithmsweeps through the endpoints of r and s in order. Everytime in encounters an endpoint of an interval, it scans theother relation and joins the interval with all matching intervals.Bouros and Mamoulis introduce a couple of optimizations toimprove the performance. First, consecutively swept intervalsare grouped and processed in batch (this is called gFS). Second,the (temporal) domain is split into tiles and intervals startingin such a tile are stored in a bucket associated with this tile.While scanning for join partners for a tuple r , all intervals inbuckets corresponding to tiles that are completely covered bythe interval of r can be joined without further comparisons.Combined with the previous technique, this results in a variantcalled bgFS. Doing a forward scan or a backward scan hascertain implications. Introducing their optimizations to FS,Bouros and Mamoulis showed that forward scanning is usuallymore efficient than backward scanning (particularly when itcomes to parallelizing the algorithm). However, there is also a downside: forward scanning needs to have access to thecomplete relations to work, while backward scanning considersonly already encountered endpoints, i.e., backward scanningcan be utilized in a streaming context (for forward scanningthis is not possible). As we will see in Table 1, most of the interval joins can bebroken down into inequality joins, which becomes very ineffi-cient as soon as more than one inequality predicate is involved:Khayyat et al. [26] point out that these joins are handled vianaive nested-loop joins in contemporary RDBMSs. They de-velop a more efficient inequality join (IEJoin), which first sortsthe relations according to the join attributes. For the sake ofsimplicity, we just consider two inequality predicates here, i.e.,for every relation r , we have two versions, r and r sorted bythe two join attributes, which helps us to find the values satisfy-ing an inequality predicates more efficiently. (The connectionsbetween tuples from r and r are made using a permutationarray.) Some additional data structures, offset arrays and bitarrays, help the algorithm to take shortcuts, but essentially thebasic join algorithm still consists of two nested loops, leadingto a quadratic run-time complexity (albeit with a performancethat is an order of magnitude better than a naive nested-loopjoin). Interval Data:
We define a temporal tuple as a relationaltuple containing two additional attributes, T s and T e , denotingthe start and end of the half-open tuple validity interval T =[ T s , T e ) . We will use a period (.) to denote an attribute of atuple, e.g. r.T s or s.T . The length of the tuple validity interval, | r | , is therefore r.T e − r.T s . We use the terms interval and tuple interchangeably. With r and s we denote the left-hand-side and the right-hand-side tuples in a join, respectively. Weuse integers for the timestamps to simplify the explanations.Our approach would work with any discrete time domain: werequire a total order on the timestamps and a fixed granularity,i.e., given a timestamp we have to be able to unambiguouslydetermine the following one. Interval Relations:
As intervals accommodate the humanperception of time-based patterns much better than individualvalues [21], intervals and their relationships are a well-knownand widespread approach to handle temporal data [22]. Herewe look at two different ways to define binary relationshipsbetween intervals: Allen’s relations [2] and the Interval-basedSurveillance Event Query Language (ISEQL) [6, 20].Allen designed his framework to support reasoning aboutintervals and it comprises thirteen relations in total. The sevenbasic
Allen’s relations are shown in the top half of Table 1.For example, interval r MEETS interval s when r finishes im-mediately before s starts. This is illustrated by the doodle inthe table. We will use smaller versions of the doodles also3able 1: Allen’s and ISEQL interval relations Relation Doodle Formal definition
OVERLAPS rs r.T s < s.T s < r.T e < s.T e DURING rs s.T s < r.T s ∧ r.T e < s.T e BEFORE rs r.T e < s.T s MEETS rs r.T e = s.T s EQUALS rs r.T s = s.T s ∧ r.T e = s.T e STARTS rs r.T s = s.T s ∧ r.T e < s.T e FINISHES rs s.T s < r.T s ∧ r.T e = s.T e STARTPRECEDING rs r.T s (cid:54) s.T s < r.T e s.T s − r.T s (cid:54) δ ENDFOLLOWING rs r.T s < s.T e (cid:54) r.T e r.T e − s.T e (cid:54) ε BEFORE rs r.T e (cid:54) s.T s s.T s − r.T e (cid:54) δ LEFTOVERLAP rs r.T s (cid:54) s.T s < r.T e (cid:54) s.T e s.T s − r.T s (cid:54) δs.T e − r.T e (cid:54) ε DURING rs s.T s (cid:54) r.T s ∧ r.T e (cid:54) s.T e r.T s − s.T s (cid:54) δs.T e − r.T e (cid:54) ε in the text: ( ). The first six relations in the table also havean inverse counterpart (hence thirteen relations). For exam-ple, relation “ r INVERSE MEETS s ” describes s immediatelyfinishing before r starts: ( ).ISEQL originated in the context of complex event detectionand covers a different set of requirements. The list of thefive basic ISEQL relations is presented in the bottom half ofTable 1; each of them has an inverse counterpart. Additionally,ISEQL relations are parameterized. The parameters controladditional constraints and allow a much more fine-graineddefinition of join predicates. This is similar to the simpletemporal problem (STP) formalism, which defines an intervalthat restricts the temporal distance between two events [3,13]. Let us consider the BEFORE
ISEQL relation ( ). Ithas one parameter δ , which controls the maximum alloweddistance between the intervals (events). When δ = 0 , thisrelation is equivalent to the Allen’s MEETS ( ). When δ > ,it is a disjunction of Allen’s MEETS and
BEFORE , and themaximum allowed distance between the events is δ timepoints.Any ISEQL relation parameter can be relaxed (set to infinity),which removes the corresponding constraint. Joins on Interval Relations:
For each binary interval re-lation (Allen’s or ISEQL) we define a predicate P ( r, s ) asits indicator function: its value is ‘true’ if the argument tu-ples satisfy the relation and ‘false’ otherwise. From now on we will use the terms ‘predicate’ and ‘binary interval rela-tion’ interchangeably. We perceive ISEQL relation parameters,if not relaxed, as part of the definition of P (e.g. P δ ). Wealso define a temporal relation (not to be confused with bi-nary relations described before) as a set of temporal tuples: r = { r , r , . . . , r n } .Let us take a generic relational predicate P ( r, s ) . We de-fine the P - join of two relations r and s as an operator thatreturns all pairs of r and s tuples that satisfy predicate P .We can express this in pseudo-SQL as “ SELECT * FROM r , s WHERE P ( r, s ) ”. For example, we define the ISEQL LEFT OVERLAP JOIN as “
SELECT * FROM r , s WHERE r LEFT OVERLAP s ”. If the predicate is parameterized, thejoin operator will also be parameterized. Example 1
As an example (see Figure 2), let us assume thatwe have two temporal relations: r = { r = [0 , , r =[1 , , r = [2 , } and s = { s = [1 , , s = [3 , } . r r r s s t Figure 2: Example relations r and s Let us now take the ISEQL
BEFORE JOIN ( ) with theparameter δ = 1 . Its result consists of two pairs (cid:104) r , s (cid:105) and (cid:104) r , s (cid:105) , because only they satisfy this particular join predicate.If we relax the parameter (set δ to ∞ ), then an additional thirdpair (cid:104) r , s (cid:105) would be added to the result of the join. By replacing the relational predicate by its formal defini-tion (Table 1), we can implement an interval join in any re-lational database. However, such an implementation resultsin a relational join with inequality predicates, which is notefficiently supported by RDBMSs: they have to fall back onthe nested-loop implementation in this case [26].
We opt for a relational algebra representation to be able tomake formal statements (e.g. proofs) about the different opera-tors. Before fully formalizing our approach, we first introducea map operator ( χ ) as an addition to the standard selection,projection, and join operators in traditional relational algebra.This operator is used for materializing values as described in[7]. We also introduce our new interval-timestamp join ( (cid:111)(cid:110) • ),allowing us to replace costly non-equi-join predicates withan operator that, as we show later, can be implemented muchmore efficiently. Definition 1
The map operator χ a : e ( r ) evaluates the expres-sion e on each tuple of r and concatenates the result to thetuple as attribute a : χ a : e ( r ) = { r ◦ [ a : e ( r )] | r ∈ r } . f the attribute a already exists in a tuple, we instead overwriteits value. Definition 2
The interval-timestamp join r (cid:111)(cid:110) • s matches theintervals in the tuples of relation r with the timestamps of thetuples in s . It comes in two flavors, depending on the timestampchosen for s , i.e., T s or T e . So, the interval-starting-timestampjoin is defined as r (cid:111)(cid:110) • θ start s = { r × s | r ∈ r , s ∈ s : r.T s θ s.T s < r.T e } with θ ∈ { <, (cid:54) } , whereas the interval-ending-timestamp joinboils down to r (cid:111)(cid:110) • θ end s = { r × s | r ∈ r , s ∈ s : r.T s < s.T e θ r.T e } with θ ∈ { <, (cid:54) } . Now we are ready to formulate the joins on interval rela-tions shown in Table 1 in relational algebra extended by ournew join operator (cid:111)(cid:110) • . We first cover the non-parameterizedversions (i.e., setting δ and ε to infinity) and then move onto the parameterized ones. Table 2 gives an overview of therelational algebra formulations. Proof for the correctness ofour rewrites can be found in Appendix A. The non-parameterized joins include all the Allen’s relationsand the ISEQL joins with relaxed parameters. The
EQUALS , STARTS , FINISHES , and
MEETS predicates could be evaluatedusing a regular equi-join. Nevertheless, we formulate themvia interval-timestamp joins, which are better suited to stream-ing environments and can be processed more quickly for lownumbers of matching tuples. We present the joins roughly inthe order of their complexity, i.e., how many other differentoperators we need to define them.
ISEQL Start Preceding Join:
The
START PRECEDING predicate ( ) joins two tuples r and s if they overlap and r does not start after s (we relax the parameter δ here). Thiscan be expressed in our extended relational algebra in thefollowing way: r (cid:111)(cid:110) • (cid:54) start s . ISEQL End Following Join:
As before, we first considerthe ISEQL
END FOLLOWING predicate ( ) with a relaxed ε parameter, meaning that tuples r and s should overlap and r is not allowed to end before s . In relational algebra this boilsdown to r (cid:111)(cid:110) • (cid:54) end s . Overlap Join:
If we look at the
LEFT OVERLAP join ( ),we notice that it looks very similar to a
START PRECEDING join.The main difference is that it has one additional constraint: the r tuple has to end before the s tuple. Formulated in relationalalgebra this is equal to σ r .T e (cid:54) s .T e ( r (cid:111)(cid:110) • (cid:54) start s ) . For the
RIGHT OVERLAP join ( ), we could just swap theroles of r and s , or we could use an END FOLLOWING joincombined with a selection predicate stating that the s tuple hasto start before the r tuple: σ s .T s (cid:54) r .T s ( r (cid:111)(cid:110) • (cid:54) end s ) . For the more strict Allen’s
LEFT OVERLAP and
INVERSEOVERLAP joins we use “ < ” for the θ of the join and the selec-tion predicate (or, alternatively, the parameterized version ofthe OVERLAP join, which is introduced later).
During Join:
For the
DURING join ( ), we have to swapthe roles of r and s . Formulated with the help of a STARTPRECEDING join, it becomes σ r .T e (cid:54) s .T e ( s (cid:111)(cid:110) • (cid:54) start r ) or, alternatively, with an END FOLLOWING join we get σ r .T s (cid:62) s .T s ( s (cid:111)(cid:110) • (cid:54) end r ) . A REVERSE DURING join maps more naturally to a
STARTPRECEDING or END FOLLOWING join, i.e., we do not haveto swap the roles of r and s . For the Allen relation we use“ < ” for the θ of the join and the selection predicate (or theparameterized version of the DURING join).
Before Join:
In the case of the
BEFORE predicate ( ), thetuples should not overlap at all. We achieve this by convertingthe ending events of r into starting ones and setting the endingevents to infinity (see Figure 3, the dashed lines are the originaltuples, the solid lines the newly created ones). Formulated inrelational algebra we get for the ISEQL version of BEFORE : χ T e : ∞ ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s . For the Allen relation we use θ = “ < ” for the join. r r r s s t Figure 3: Formulating Allen’s
BEFORE JOIN
Meets Join:
For the
MEETS predicate ( ) each tuple ofrelation r should only be active for a short interval of lengthone when it ends. We achieve this by converting the end eventsof tuples in r into start events and adding a new end event thatshifts the old end event by one. Expressed in relational algebrathis looks as follows. χ T e : T e +1 ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s . Relation Doodle Relational algebra expression
START PRECEDING rs r (cid:111)(cid:110) • (cid:54) start s END FOLLOWING rs r (cid:111)(cid:110) • (cid:54) end s non - p a r a m e t e r i ze d LEFT OVERLAP rs σ r .T e (cid:54) s .T e ( r (cid:111)(cid:110) • (cid:54) start s ) DURING rs σ r .T e (cid:54) s .T e ( s (cid:111)(cid:110) • (cid:54) start r ) BEFORE rs χ T e : ∞ ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s (ISEQL); χ T e : ∞ ( χ T s : T e ( r )) (cid:111)(cid:110) • < start s (Allen) MEETS rs χ T e : T e +1 ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s EQUALS rs σ r .T (cid:48) e = s .T e ( χ T e : T s +1 ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) STARTS rs σ r .T (cid:48) e < s .T e ( χ T e : T s +1 ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) FINISHES rs σ s .T s < r .T (cid:48) s ( χ T s : T e − ( χ T (cid:48) s : T s ( r )) (cid:111)(cid:110) • (cid:54) end s ) START PRECEDING rs χ T e :min( T e ,T s + δ +1) ( r ) (cid:111)(cid:110) • (cid:54) start s p a r a m e t e r i ze d END FOLLOWING rs χ T s :max( T s ,T e − ε − ( r ) (cid:111)(cid:110) • (cid:54) end s BEFORE rs χ T e : T e + δ +1 ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s LEFT OVERLAP rs σ r .T (cid:48) e (cid:54) s .T e (cid:54) r .T (cid:48) e + ε ( χ T e :min( T e ,T s + δ +1) ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) DURING rs σ s .T (cid:48) e − ε (cid:54) r .T e (cid:54) s .T (cid:48) e ( χ T e :min( T e ,T s + δ +1) ( χ T (cid:48) e : T e ( s )) (cid:111)(cid:110) • (cid:54) start r ) Equals Join:
For the
EQUALS predicate ( ) we check thatstarting events match via an interval-timestamp join and thenadd a selection to check the ending events: σ r .T (cid:48) e = s .T e ( χ T e : T s +1 ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) . Starts Join:
For a
STARTS predicate ( ) we first checkthat the starting events are the same, the ending events areagain compared in a selection afterwards. Although one of thepredicates uses a comparison based on inequality, this happensin the selection, not the join: σ r .T (cid:48) e < s .T e ( χ T e : T s +1 ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) . Finishes Join:
The
FINISHES predicate ( ) works similarto a
STARTS predicate. We use an interval-ending-timestampjoin and swap the roles of the starting and ending events (dueto the different definition of the (cid:111)(cid:110) • (cid:54) end join, we also have toshift the timestamps by one): σ s .T s < r .T (cid:48) s ( χ T s : T e − ( χ T (cid:48) s : T s ( r )) (cid:111)(cid:110) • (cid:54) end s ) . We now move on to the parameterized versions of the ISEQLjoin operators found in Table 1.
Start Preceding Join with δ : Defining a value for the pa-rameter δ for the ISEQL START PRECEDING join ( ) meansthat the tuple from s has to start between the start of the r tuple and within δ time units of the r tuple starting or the endof the r tuple (whichever happens first). Basically, we shortenthe long tuples. Expressed in relational algebra, this becomes χ T e :min( T e ,T s + δ +1) ( r ) (cid:111)(cid:110) • (cid:54) start s . End Following Join with ε : For the parameterized
ENDFOLLOWING ( ) join we have to make sure that the s tupleends within ε time units distance from the end of the r tuple(but after the start of the r tuple). The formal definition inrelational algebra is χ T s :max( T s ,T e − ε − ( r ) (cid:111)(cid:110) • (cid:54) end s . The Before Join with δ : For the parameterized
BEFORE join we have to make sure that the s tuple starts within a timewindow of length δ after the r tuple ends: χ T e : T e + δ +1 ( χ T s : T e ( r )) (cid:111)(cid:110) • (cid:54) start s Overlap Join with δ and ε : Similar to the non-parameterized version we use the
START PRECEDING jointo define the parameterized
LEFT OVERLAP join: σ r .T (cid:48) e (cid:54) s .T e (cid:54) r .T (cid:48) e + ε ( χ T e :min( T e ,T s + δ +1) ( χ T (cid:48) e : T e ( r )) (cid:111)(cid:110) • (cid:54) start s ) . RIGHT OVERLAP join we can eitherswap the roles of r and s or use the END FOLLOWING join: σ r .T (cid:48) s − δ (cid:54) s .T s (cid:54) r .T (cid:48) s ( χ T s :max( T s ,T e − ε − ( χ T (cid:48) s : T s ( r )) (cid:111)(cid:110) • (cid:54) end s ) . During Join with δ and ε : We can use a parameterized
START PRECEDING or END FOLLOWING join as a buildingblock for a parameterized
DURING join, swapping the roles of r and s . With a START PRECEDING join we get σ s .T (cid:48) e − ε (cid:54) r .T e (cid:54) s .T (cid:48) e ( χ T e :min( T e ,T s + δ +1) ( χ T (cid:48) e : T e ( s )) (cid:111)(cid:110) • (cid:54) start r ) , whereas with an END FOLLOWING join it boils down to σ s .T (cid:48) s (cid:54) r .T s (cid:54) s .T (cid:48) s + δ ( χ T s :max( T s ,T e − ε − ( χ T (cid:48) s : T s ( s )) (cid:111)(cid:110) • (cid:54) end r ) . After introducing the interval joins formally, we now turn totheir efficient implementation. We develop a framework toexpress the different interval joins with the help of just onecore join algorithm. The framework also includes an indexand several iterators for scanning through sets of intervals toincrease the performance and flexibility.
We can gain a lot of speed-up by sweeping through the intervalendpoints in chronological order using an Endpoint Index,which is a simplified version of the Timeline Index [25]. Theidea of the
Endpoint Index is that intervals, which can be seenas points in a two-dimensional space, are mapped onto one-dimensional endpoints or events .Let r be an interval relation with tuples r i , where (cid:54) i (cid:54) n .A tuple r i in an Endpoint Index is represented by two events ofthe form e = (cid:104) timestamp , type , tuple id (cid:105) , where timestamp is the T s or T e of the tuple, type is either a start or end flag,and tuple id is the tuple identifier, i.e., the two events for atuple r i are (cid:104) r i .T s , start , i (cid:105) and (cid:104) r i .T e , end , i (cid:105) . For instance,for r .T = [3 , , the two events are (cid:104) , start , (cid:105) and (cid:104) , end , (cid:105) , which can be seen as “at time 3 tuple 3 started” and “attime 5 tuple 3 ended”.Since events represent timestamps, we can impose a total or-der among events, where the order is according to timestamp and ties are broken by type . In our case of half-open intervals,the order of type values is: end < start . Endpoints with equaltimestamps and types but different tuple identifiers are consid-ered equal. An Endpoint Index for interval relation r is builtby first extracting the interval endpoints from the relation andthen creating the ordered list of events [ e , e , . . . , e n ] sortedin ascending order. In case of event detection, the endpoints(events) can be taken directly from the event stream and we donot even have to construct an index.Consider exemplary interval relation r from Figure 2. TheEndpoint Index for it is [ (cid:104) , start , (cid:105) , (cid:104) , end , (cid:105) , (cid:104) , start , (cid:105) , (cid:104) , start , (cid:105) , (cid:104) , end , (cid:105) , (cid:104) , end , (cid:105) ] . Before continuing with the join algorithms, we introduce theconcept of the Endpoint Iterator, upon which our family ofalgorithms is based. An
Endpoint Iterator represents a cursor,that allows forward traversing a list of endpoints (e.g., anEndpoint Index). More formally, it is an abstract data type (aninterface), that supports three operations:• getEndpoint : returns the endpoint, which the iteratoris currently pointing to (initially returns the first endpointin the list);• moveToNextEndpoint : advances the cursor to thenext endpoint;• isFinished : return true if the cursor is pointingbeyond the last endpoint of the list, false otherwise.More details on the implementation of Endpoint Iterators canbe found in Appendix B.The basic implementation of the Endpoint Iterator is the
Index Iterator , which provides an Endpoint Iterator interfaceto a physical Endpoint Index. Given an instance of the index,such an iterator traverses all Endpoint Index elements usingthe native method applicable to the Endpoint Index. In the textand in the algorithm descriptions we use the terms “EndpointIndex” and “Index Iterator” interchangeably, i.e., we create anIndex Iterator for an Endpoint Index implicitly if needed.There are also wrapping iterators. Such iterators do not havedirect access to an Endpoint Index, but modify, filter and/orcombine the output of one or several source
Endpoint Iterators.In software design pattern terminology such iterators are called decorators . We conclude this subsection by introducing onesuch wrapping iterator. We introduce more of them later, asneeded.The simplest wrapping Endpoint Iterator is the
FilteringIterator . It receives, upon construction, a source EndpointIterator and an endpoint type ( start or end ). It then traversesonly endpoints having the specified type. JoinByS
We are now ready to define the core algorithm, which formsthe basis of all our joins. This algorithm receives the relationsto be joined r and s , Endpoint Iterators for them, a comparisonpredicate, and a callback function that will be called for eachresult pair. The algorithm performs the interleaved scan ofthe endpoint iterators. While doing so, it maintains the setof active r tuples. Every endpoint for relation s triggers theoutput—the Cartesian product of the corresponding tuple s and the set of active r tuples. The comparison predicate isused to define the order in which equal endpoints of differentrelations are handled (“equal” meaning endpoints having thesame timestamp and type).The pseudocode for the core algorithm JoinByS is presentedin Algorithm 1. The algorithm starts by initializing an active r tuple set implemented via a map (an associative array) of tupleidentifiers to tuples.7 lgorithm 1: JoinByS( r , s , itR , itS , comp , consumer ) input : argument relations r and s , corresponding EndpointIterators itR and itS , endpoint comparison predicate comp (‘ < ’ or ‘ (cid:54) ’), function consumer( r, s ) forresult pairs var activeR ← new Map of tuple identifiers to tuples while not itR . isFinished and not itS . isFinished do if comp(itR . getEndpoint , itS . getEndpoint ) then // handle an r endpoint (maintain active r tuples) tid ← itR . getEndpoint . tuple id if itR . getEndpoint = start then r ← r [ tid ] // load the tuple activeR . insert( tid , r ) else activeR . remove( tid ) itR . moveToNextEndpoint else // handle an s endpoint (trigger output) s ← s [ itS . getEndpoint . tuple id ] // load tuple s foreach r ∈ activeR do // with every active tuple r consumer( r , s ) // produce output pair (cid:104) r, s (cid:105) itS . moveToNextEndpoint The main loop (line 2) and the main “if” (line 3) implementthe interleaved scan of the endpoint indices (like in a sort-merge join). The tricky part here is that instead of a hardwiredcomparison operator (‘ < ’ or ‘ (cid:54) ’), we use the function comp ,that we pass as an argument to the algorithm. In case of the START PRECEDING JOIN , for instance, if both current end-points of r and s are equal, we have to handle the r endpointfirst (Section 4.1 on page 5), and thus we have to use the ‘ (cid:54) ’predicate. In case of the END FOLLOWING JOIN , on the otherhand, if both current endpoints of r and s are equal, we have tohandle the s endpoint first (Section 4.1 on page 5), and thus wehave to use the ‘ < ’ predicate. Having the predicate as an argu-ment of the algorithm allows us to choose the needed predicateupon using the algorithm, which prevents code duplication. The rest of the algorithm consists of two parts. The first part(lines 4–10) handles an r endpoint and manages the active r tuple set. When a tuple starts, the algorithm loads it from therelation by the tuple identifier stored in the endpoint and putsthe tuple in the map using the identifier as the key. When atuple ends, the algorithm removes it from the active tuple map,again using the tuple identifier as the key.The second part (lines 12–15) handles an s endpoint. It firstloads the corresponding tuple s from the relation. Then it iter-ates through all elements in the active r tuple map. For everyactive r tuple the algorithm outputs the pair (cid:104) r, s (cid:105) by passingit into the consumer function, which is another function-typeargument of the algorithm. In some cases, the consumer has todo additional work such as evaluating a selection predicate. Wecall these consumers filteringConsumers . If they have access Note that the comparison function is not the same as the parameter θ ofthe interval-timestamp join. The comparison operator in JoinByS makes surethat the events are processed in the right order. to the full tuple, they can check the predicate and immediatelyoutput a result tuple. In a streaming environment, we do nothave access to the end events immediately, which means that afilteringConsumer also needs to buffer data until these eventsbecome available. We now show how to construct the different interval relationsusing our JoinByS operator and iterators. We start with theexpressions from Section 4 that do not include map operators,followed by those that do.
Start Preceding and End Following Joins:
These two joinpredicates are the easiest to implement, as they can be mappeddirectly to the JoinByS operator. For the
START PRECEDINGJOIN ( ) we have to keep track of the active r tuples, andtrigger the output by the start of an s tuple. If two tuples startat the same time, we have to handle the r tuple first. Therefore,we call the JoinByS function, passing to it only the starting s endpoints. This is achieved by using a Filtering Iterator(Section 5.2 on page 7). We also have to pass the ‘ (cid:54) ’ predicateas the comparison function. A START PRECEDING JOIN thenboils down to a single call of JoinByS (see Algorithm 2).
Algorithm 2:
StartPrecedingJoin( r , s , itR , itS , consumer ) JoinByS( r , s , itR , FilteringIterator( itS , start ), ‘ (cid:54) ’, consumer ) The algorithm StartPrecedingJoin receives iterators to theEndpoint Indexes. When using this algorithm with EndpointIndices, we simply wrap each index in an Index Iterator—anoperation, which, as noted before, we consider implicit.We define the algorithm for the
END FOLLOWING JOIN ( ) similarly, but filter the ending endpoints of s , and passthe ‘ < ’ as the comparison function. The pseudocode of theEndFollowingJoin is presented in Algorithm 3. Algorithm 3:
EndFollowingJoin( r , s , itR , itS , consumer ) JoinByS( r , s , itR , FilteringIterator( itS , end ), ‘ < ’, consumer ) Overlap Joins:
The
LEFT OVERLAP JOIN ( ) can be im-plemented using the StartPrecedingJoin algorithm with an ad-ditional constraint r.T e (cid:54) s.T e . The pseudocode is shown inAlgorithm 4. The RIGHT OVERLAP JOIN ( ) is implementedalong similar lines using the EndFollowingJoin algorithm andthe selection predicate s.T s (cid:54) r.T s .For the Allen versions of the overlap joins, we use strict ver-sions of Algorithms 2 and 3, StartPrecedingStrictJoin and
End-FollowingStrictJoin , which do not allow a tuple r to start witha tuple s or a tuple s to end with a tuple r , respectively. They8 lgorithm 4: LeftOverlapJoin( r , s , idxR , idxS , consumer ) filteringConsumer ← function ( r, s ) if r.T e (cid:54) s.T e then consumer( r , s ) StartPrecedingJoin( r , s , idxR , idxS , filteringConsumer ) are just simple variations: StartPrecedingStrictJoin merely re-places the ‘ (cid:54) ’ in Algorithm 2 with ‘ < ’ and EndFollowingStric-tJoin replaces the ‘ < ’ in Algorithm 3 with ‘ (cid:54) ’. Additionally,we change the ‘ (cid:54) ’ in the selection predicates in the filtering-Consumer functions to ‘ < ’. During Joins:
Implementing the
DURING join ( ) is simi-lar to Algorithm 4: we just have to swap the arguments for r and s (alternatively, we could also use the END FOLLOWING variant). For the Allen version of
DURING joins, we replacethe StartPrecedingJoin, EndFollowingJoin, and selection pred-icates with their strict counterparts.If we simply call an algorithm with swapped arguments, theelements of the result pairs appear in a different order, i.e., (cid:104) s, r (cid:105) instead of the expected (cid:104) r, s (cid:105) . If this is an issue, wecan swap them back using a lambda function as the consumer.Putting everything together, we get Algorithm 5.
Algorithm 5:
DuringJoin( r , s , idxR , idxS , consumer ) reversingConsumer ← function ( s, r ) consumer( r , s ) filteringConsumer ← function ( s, r ) if r.T e (cid:54) s.T e then reversingConsumer ( s , r ) StartPrecedingJoin( s , r , idxS , idxR , filteringConsumer ) In order to avoid physically changing tuple values or even theEndpoint Index, we apply the changes made by the map opera-tors virtually with an iterator. While performing an interleavedscan of two Endpoint Indexes, instead of simply comparingthe two endpoints r e and s e (as in r e < s e ), we shift the times-tamp of one of them when comparing: r e + δ < s e . In thisway the algorithm performs an interleaved scan of the indexesas if we had shifted all r tuples in time by + δ .During an interleaved scan, instead of forcing the iteratorsof the two Endpoint Indexes (for the relations r and s ) to movesynchronously as in all the operators so far, now one of theiterators lags behind by a constant offset. This behavior canbe easily incorporated into our framework by using a specialEndpoint Iterator that shifts the timestamp of every endpoint itreturns on-the-fly.There is a second issue: the new starting endpoint often isactually a shifted ending endpoint or vice versa. Consequently,we have to change the endpoint type as well. With the helpof our Shifting Iterator , we can shift timestamps and alsochange endpoint types. As input parameters a shifting iterator receives a source Endpoint Iterator, the shifting distance, andan endpoint type (start or end).The final issue is separately shifting the starting and endingendpoints by different amounts. We solve this by having inde-pendent iterators for both starting and ending endpoints andmerging them on-the-fly in an interleaved fashion. The inputparameters of the
Merging Iterator are two other iterators, theevents of which it merges. See Appendix B for more details.
Before and Meets Joins:
We are now ready to create a
Gen-eralBeforeJoin (see Algorithm 6 and Figure 4 for a schematicrepresentation); we already handle the parameterized versionhere as well. This algorithm performs a virtual three-waysort-merge join of the two Endpoint Indexes. One pointer willtraverse the Endpoint Index for relation s , and two pointerswill traverse the Endpoint Index for relation r , all three point-ers moving synchronously, but at different positions. This iswhy we had to (implicitly) create two Index Iterators for thesame index (lines 4 and 6)—each of them represents a physicalpointer to the same Endpoint Index, therefore we need two ofthem. Algorithm 6:
GeneralBeforeJoin( r , s , idxR , idxS , β , δ , con-sumer ) StartPrecedingJoin( r , s , MergingIterator( // T e + β → T s ShiftingIterator( FilteringIterator( idxR , end ), β , start ), // T e + δ + 1 → T e ShiftingIterator( FilteringIterator( idxR , end ), δ + 1 , end )), IndexIterator( idxS ), consumer ) rs filterfilterindex shiftshift mergefilter JoinBySFigure 4: Schematic representation of GeneralBeforeJoin
We express the Allen’s
BEFORE JOIN ( ) by substituting1 and + ∞ for β and δ , respectively; Allen’s MEETS JOIN ( ) by substituting 0 and 0, respectively; and the ISEQL
BEFORE JOIN by substituting 0 for β and only using the δ for the parameterized version. The parameter β distinguishesbetween the strict (Allen) and non-strict (ISEQL) versions ofthe operator. Equals and Starts Joins:
For the
EQUALS JOIN ( ) wekeep the original starting endpoints of r and use as ending end-points the starting endpoints shifted by one and then executea StartPrecedingJoin. This matches tuples from r and s withthe same starting endpoints. We check that we have matchingending endpoints in the filteringConsumer function, which9eceives the actual tuples as input and thus has access to thetimestamp attributes of the original tuples (see Algorithm 7)for the pseudocode. Algorithm 7:
EqualsJoin( r , s , idxR , idxS , consumer ) filteringConsumer ← function ( r, s ) if r.T e = s.T e then consumer( r , s ) StartPrecedingJoin( r , s , MergingIterator( // keep the original r starting endpoints FilteringIterator( idxR , start ), // T s + 1 → T e ShiftingIterator( FilteringIterator( idxR , start ), , end )), IndexIterator( idxS ), filteringConsumer ) For a
STARTS JOIN ( ) we just have to change the predicatein the filteringConsumer function from ‘ = ’ to ‘ < ’. Finishes Join:
For the tuples in r we turn the ending eventsinto starting events and shift the ending events by one beforejoining them to the tuples in s via an EndFollowingJoin (Algo-rithm 3). Finally, we check that the tuple from s started beforethe one from r . For the pseudocode of the FINISHES JOIN ( ),see Algorithm 8.
Algorithm 8:
FinishesJoin( r , s , idxR , idxS , consumer ) filteringConsumer ← function ( r, s ) if s.T s < r.T s then consumer( r , s ) EndFollowingJoin( r , s , MergingIterator( // T e − → T s ShiftingIterator( FilteringIterator( idxR , end ), − , start ), // T e → T e ShiftingIterator( FilteringIterator( idxR , end ), , end )), IndexIterator( idxS ), filteringConsumer ) Parameterized Start Preceding Join:
We now turn to theparameterized variant of the
START PRECEDING JOIN ( ),which has the parameter δ constraining the maximum distancebetween tuple starting endpoints. The basic idea is to take thestarting endpoints of relation r , shift them by δ + 1 , changetheir type to ending endpoints, and add these virtual endpointsto the original endpoints of r . This way each r tuple willbe represented by three endpoints: the original starting andending endpoints and the virtual ending endpoint. Then theparameterless StartPrecedingJoin algorithm (Algorithm 2) isapplied to both streams of r and s endpoints. When encoun-tering the second ending endpoint in the merged iterator, itcan simply be ignored when its corresponding tuple cannot be found in the active tuple set (see Appendix B.5). Algorithm 9depicts the pseudocode. Algorithm 9:
PStartPrecedingJoin( r , s , idxR , idxS , δ , con-sumer ) StartPrecedingJoin( r , s , FirstEndIterator( MergingIterator( // keep the original r endpoints IndexIterator( idxR ), // T s + δ + 1 → T e ShiftingIterator( FilteringIterator( idxR , start ), δ + 1 , end ))), IndexIterator( idxS ), consumer ) Parameterized End Following Join:
A similar parameter-ized
END FOLLOWING JOIN ( ) is more complicated. Theproblem here is that each r tuple will have to be representedby two starting endpoints. The algorithm must consider a tu-ple activated only if both starting endpoints (and no endingendpoint) have been encountered.We achieve this by introducing an iterator, called SecondStart Iterator , that stores the tuple identifiers of events forwhich we have only encountered one starting endpoint in ahash set (see Appendix B.6). Only the second starting endpointof this tuple will return the starting event. The pseudocodefor the parameterized
END FOLLOWING JOIN is shown inAlgorithm 10.
Algorithm 10:
PEndFollowingJoin( r , s , idxR , idxS , ε , con-sumer ) EndFollowingJoin( r , s , SecondStartIterator( MergingIterator( // keep the original r endpoints IndexIterator( idxR ), // T e − ε − → T s ShiftingIterator( FilteringIterator( idxR , end ), − ε − , start ))), IndexIterator( idxS ), consumer ) Parameterized Overlap Join:
Now that we have an algo-rithm for the parameterized StartPrecedingJoin, we can definethe parameterized
LEFT OVERLAP JOIN ( ) by combiningPStartPrecedingJoin with a filteringConsumer function, simi-larly to what we have done for the non-parameterized overlapjoin. Algorithm 11 shows the pseudocode. Alternatively, wecan use a PEndFollowingJoin and then check the predicate forthe starting endpoint of the s tuple in the filteringConsumer function.The RIGHT OVERLAP JOIN ( ) uses a PEndFollowingJoinwith the corresponding predicate in the filteringConsumer function.10 lgorithm 11:
PLeftOverlapJoin( r , s , idxR , idxS , δ , ε , con-sumer ) filteringConsumer ← function ( r, s ) if r.T e (cid:54) s.T e (cid:54) r.T e + ε then consumer( r , s ) PStartPrecedingJoin( r , s , idxR , idxS , δ , filteringConsumer ) Parameterized During Join:
The parameterized
DURINGJOIN ( ) looks similar to Algorithm 11, we apply changesalong the lines of those shown in the paragraph for the non-parameterized
DURING JOIN . (There is also an alternativeversion using an PEndFollowingJoin.)
Showing the correctness of our algorithms boils down to illus-trating that we handle the map operators correctly and demon-strating the correctness of the StartPreceding and EndFollow-ing joins, as our algorithms are either StartPreceding and End-Following joins or are built on top them.
Iterators and Map Operators:
Here we show how to im-plement map operators with the help of iterators. Instead ofmaterializing the result (e.g. on disk), we make the correspond-ing changes in a tuple as it passes through an iterator. If westill need a copy of the old event later on, we feed this eventthrough another iterator and merge the two tuple streams usinga merge iterator.
StartPreceding Join:
We have to show that all tuples cre-ated by Algorithm 2 satisfy the predicate r.T s (cid:54) s.T s < r.T e .A Filter Iterator removes all the ending events from s , so weonly have to deal with starting events from s and with bothtypes of events from r . As comparison operator we use ‘ (cid:54) ’.This determines the order in which events are dealt with.First, let us look at the case that both upcoming events in itR and itS are starting events. If r.T s (cid:54) s.T s , then r will beinserted into the active tuple set before s is processed, meaningthat the (later) arrival of s will trigger the join with r . If r.T s > s.T s , then s will be processed first, not encountering r in the active tuple set, meaning that the two will not join.Second, if the next event in r is an ending event and thenext event in s a starting event, then the two events can neverbe equal. Even if they have the same timestamp, the endingendpoint of r will always be considered less than the startingendpoint of s . Therefore, if r.T e (cid:54) s.T s , r will be removedfirst, so r and s will not join, and if r.T e > s.T s , s will stilljoin with r .So, in summary, all the tuples generated by Algorithm 2satisfy the predicate r.T s (cid:54) s.T s < r.T e .For a StrictStartPreceding join we run Algorithm 2 with thecomparison operator ‘ < ’, yielding output tuples that satisfythe predicate r.T s < s.T s < r.T e . If both upcoming eventsin itR and itS are starting events, we get the correct behavior: r.T s < s.T s will lead to a join, r.T s (cid:62) s.T s will not. If the r event is an ending event and the s event is a starting one, wealso get the correct behavior: r.T e (cid:54) s.T s will not join the r and s tuple, r.T e > s.T s will (the ending event of r is alwaysless than the starting event of s ). EndFollowing Join:
We show that all tuples created by Al-gorithm 3 satisfy the predicate r.T s < s.T e (cid:54) r.T e . This timea Filter Iterator removes all the starting events from s , so weonly have to deal with ending events from s and with bothtypes of events from r . The comparison operator used for thenon-strict version is ‘ < ’.First, assume that the next event in itR is a starting event andthe next event in itS is an ending event. As an ending eventtakes precedence over a starting event, if r.T s = s.T e , the s event will come first. In turn this means that if r.T s < s.T e , r is added to the active set first, resulting in a join, and if r.T s (cid:62) s.T e , s is processed first, meaning there is no join.Second, we now look at the case that both events are endingevents. Due to the comparison operator ‘ < ’, the events arehandled in the right way: if r.T e < s.T e , we remove r first, sothere is no join, and if r.T e (cid:62) s.T e we handle s first, resultingin a join.For a StrictEndFollowing join we run Algorithm 3 with‘ (cid:54) ’ as comparison operator to obtain tuples that satisfy thepredicate r.T s < s.T e < r.T e . Let us first look at a startingevent for r and an ending event for s . As ending events areprocessed before starting events with the same timestamp, weget: if r.T s < s.T e , then r is added first, resulting in a join,and if r.T s (cid:62) s.T e , then s is removed first, meaning there isno join. Finally, we investigate the case that both events areending events: if r.T e (cid:54) s.T e , then r is removed first, i.e., nojoin, and if r.T e > s.T e , then s is processed first, joining r and s . In this section we look at techniques to implement our frame-work efficiently, in particular how to represent an active tupleset, utilizing contemporary hardware. We also investigate theoverhead caused by our heavy use of abstractions (such asiterators).
For managing the active tuple set we need a data structure intowhich we can insert key-value pairs, remove them, andquickly enumerate (scan) one by one all the values containedin the data structure via the operation getnext . In our case,the keys are tuple identifiers and the values are the tuplesthemselves. The data structure of choice here is a map orassociative array.The most efficient implementation of a map optimiz-ing the insert and remove operations is a hash table(with O (1) time complexities for these operations). How-ever, hash tables are not well-suited for scanning. The11 td::unordered map class in the C++ Standard Template Li-brary and the java.util.HashMap in the Java Class Library, forinstance, scan through all the buckets of a hash table, mak-ing the performance of a scan operation linear with respect tothe capacity of the hash table and not to the actual amount ofelements in it.In order to achieve an O (1) complexity for getnext , theelements in the hash table can be connected via a doubly-linked list (see Figure 5). The hash table stores pointers toelements, which in turn contain a key, a value, two pointersfor the doubly-linked list ( list prev and list next ) and a pointerfor chaining elements of the same bucket for collision resolu-tion (pointer bucket next ). This approach is employed in the java.util.LinkedHashMap in the Java Class Library. Key NextBucketPrevList NextList
Hash table HeadListValue
Tuple 5Tuple 7Tuple 9Tuple 2
Figure 5: Linked hash mapWhile this data structure offers a constant complexity for getnext , the execution times of different calls of getnext can vary widely in practice, depending on the memory foot-print of the map. After a series of insertions and deletions theelements of the linked list become randomly scattered in mem-ory, which has an impact on caching: sometimes the next listelement is still in the cache (resulting in fast retrieval), some-times it is not (resulting in slow random memory accesses).Additionally, the pointer structure make it hard for a prefetcherto determine where the next elements are located. However,for our approach it is crucial that getnext can be executedvery efficiently, as it is typically called much more often than insert and remove . We will see in Section 7.4 how toimplement a hash map more efficiently.
The fastest getnext operations are actually those that are notexecuted. We modify our algorithm to boost its performanceby significantly reducing the number of getnext operationsneeded to generate the output.We illustrate our point using the example setting in Figure 6.Assume we have just encountered the left endpoint of s , whichmeans that our algorithm now scans the tuple set active r ,which contains r and r . After that we scan it again andagain when encountering the left endpoints of s , s , and s .However, since no endpoints of r were encountered during that time, we scan the same version of active r four times. Wecan reduce this to one scan if we keep track of the tuples s , s , s , and s in a (contiguous) buffer, delaying the scan untilthere is about to be a change in active r . t sr r r r s s s s s Figure 6: Example interval relationsTo remedy this situation, we collect all consecutively en-countered s tuples in a small buffer that fits into the L1 cache.Scanning the active tuple set when producing the output nowrequires only one traversal. Thanks to the design of our joinalgorithms we can incorporate this optimization into the wholeframework by modifying JoinByS. The optimized version isshown in Algorithm 12. This technique has been introducedfor overlap joins in [36], here we generalize it to the JoinBySalgorithm. We recommend using a size for the buffer c thatis smaller than the size of the L1d CPU cache (usually 32Kilobytes) for this method to be effective.For the sake of simplicity, we only refer to the JoinBySalgorithm in the following section. It can be replaced by theLazyJoinByS algorithm without any change in functionality. Before describing further optimizations, we briefly reviewmechanisms employed by contemporary hardware to decreasemain memory latency. This latency can have a huge impact, asfetching data from main memory may easily use up more thana hundred CPU cycles.
Mechanisms:
Usually, there is a hierarchy of caches, withsmaller, faster ones closer to CPU registers. Cache memory hasa far lower latency than main memory, so a CPU first checkswhether the requested data is already in one of the caches(starting with the L1 cache, working down the hierarchy). Notfinding data in a cache is called a cache miss and only in thecase of cache misses on all levels, main memory is accessed.In practice an algorithm with a small memory footprint runsmuch quicker, because in the ideal case, when an algorithm’sdata (and code) fits into the cache, the main memory only hasto be accessed once at the very beginning, loading the data(and code) into the cache.Besides the size of a memory footprint, the access patternalso plays a crucial role, as contemporary hardware contains prefetchers that speculate on which blocks of memory will beneeded next and preemptively load them into the cache. Theeasier the access pattern can be recognized by a prefetcher,the more effective it becomes. Sequential access is a pattern12 lgorithm 12:
LazyJoinByS( r , s , itR , itS , comp , consumer ) input : argument relations r and s , corresponding EndpointIterators itR and itS , endpoint comparison predicate comp (‘ < ’ or ‘ (cid:54) ’), function consumer( r, s ) forresult pairs var activeR ← new Map of tuple identifiers to tuples var buffer ← new array of capacity c while not itR . isFinished and not itS . isFinished do if comp(itR . getEndpoint , itS . getEndpoint ) then // handle an r endpoint (maintain active r tuples) tid ← itR . getEndpoint . tuple id if itR . getEndpoint = start then r ← r [ tid ] // load the tuple activeR . insert( tid , r ) else activeR . remove( tid ) itR . moveToNextEndpoint else // get sequence of s tuples uninterrupted by r events repeat s ← s [ itS . getEndpoint . tuple id ] buffer . insert( s ) itS . moveToNextEndpoint until itS . isFinished or comp(itR . getEndpoint , itS . getEndpoint ) or buffer . isFull // produce Cartesian product with active r tuples foreach r ∈ activeR do // scan the active r tuples once foreach s ∈ buffer do // the inner loop, in L1 cache consumer( r , s ) // produce output pair buffer . clear that can be picked up by prefetchers very easily, while randomaccess effectively renders them useless.Also, programs do not access physical memory directly, butthrough a virtual memory manager, i.e., virtual addresses haveto be mapped to physical ones. Part of the mapping table iscached in a so-called translation lookaside buffer (TLB). Asthe size of the TLB is limited, a program with a high level oflocality will run faster, as all look-ups can be served by theTLB.Out-of-order execution (also called dynamic execution ) al-lows a CPU to deviate from the original order of the instruc-tions and run them as the data they process becomes available.Clearly, this can only be done when the instructions are in-dependent of each other and can be run concurrently withoutchanging the program logic.Finally, certain properties of DRAM (dynamic random ac-cess memory) chips also influence latency. Accessing memoryusing fast page or a similar mode means accessing data storedwithin the same page or bank without incurring the overheadof selecting it. This mechanism favors memory accesses witha high level of locality. Performance Numbers:
We provide some numbers to givean impression of the performance of currently used hardware.For contemporary processors, such as “Core” and “Xeon” by Intel , one random memory access within the L1 data ( L1d )cache (32 KB per core) takes 4 CPU cycles. Within the L2cache (256 KB per core) one random memory access takes11–12 cycles. Within the L3 cache (3–45 MB) one randommemory access takes 30–40 CPU cycles. Finally, one randomphysical RAM access takes around 70–100 ns (200–300 pro-cessor cycles). It follows that the performance gap betweenan L1 cache access and a main memory access is huge: twoorders of magnitude.
As we will see later in an experimental evaluation, managingthe active tuple set efficiently in terms of memory accesses iscrucial for the performance of the join algorithm. Otherwisewe run the risk of starving the CPU while processing a join.Our goals have to be to store the active tuple set as compactlyas possible and to access it sequentially, allowing the hardwareto get the data to the CPU in an efficient manner.We store the elements of our hash map in a contiguousmemory area. For the insert operation this means thatwe always append a new element at the end of the storagearea. Removing the last element from the storage area isstraightforward. If the element to be removed is not the lastin the storage area, we swap it with the last element and thenremove it. When doing so, we have to update all the referencesto the swapped elements. Scanning involves stepping throughthe contiguous storage area sequentially. We call our datastructure a gapless hash map (see Figure 7).
Key Prev NextHash table TailBucketBucket Values
Tuple 5Tuple 7Tuple 9Tuple 2
Figure 7: Gapless hash mapWe also separate the tuples from the elements, storing themin a different contiguous memory area in corresponding loca-tions. Assuming fixed-size records, all basic element opera-tions (append and move) are mirrored for the correspondingtuples. This slightly increases the costs for insertions and re-moval of tuples. However, scanning the tuples is as fast as itcan become, because we do not need to read any metadata,only tuple information. We use the cache and memory latencies obtained for the Sandy Bridgefamily of Intel CPUs using the SiSoftware Sandra benchmark, . bucket next , solid arrows),and a pointer bucket prev to a hash table entry or an element(whichever holds the forward pointer to this element, dashedarrows). The latter is used for updating the reference to an ele-ment when changing the element position. The main differenceto the random memory access of a linked hash map (Fig. 5)is the allocation of all elements in a contiguous memory area,allowing for fast sequential memory access when enumeratingthe values. Example 2
Assume we want to remove tuple 7 from the struc-ture depicted in Figure 7. First of all, the bucket-next pointerof the element with key 5 is set to NULL. Next, the last elementin the storage area (tuple 2) is moved to the position of the ele-ment with key 7. Following the bucket-prev pointer of the justmoved element we find the reference to the element in the hashtable and update it. Finally, the variable tail is decremented topoint to the element with key 9.
All the abstractions we use (iterators, predicates passed asfunction arguments, and lambda functions) allow us to expressall joins by means of a single function, which is extremelypractical due to the huge simplification of implementationand subsequent maintenance of the code. In this section weexplain why the impact of this architecture on the performanceis minimal for C++ and not significant for Java.We compare our implementation empirically to a manualrewrite without abstractions of a selected join algorithm. Here,we show the results for our most complicated implementation,Algorithm 6. We compare its performance to a version thatwas fully inlined manually into a single leaf function. We didso for C++ and also for Java. We then launched each one ofthe four versions separately using the synthetic dataset of tuples with an average number of active tuples equal to (see Section 9.1 for the dataset). Each version was executingthe join several times sequentially to allow the JVM to per-form all necessary optimizations. The results are shown inFigure 8. We see that the C++ version is several times fasterthan the Java version. Moreover, we see that the C++ com-piler was able to optimize our abstracted code so well that itsperformance is indistinguishable from the manually optimizedversion. The situation with Java is more complicated, in theend the manually optimized version was ∼ faster. C++:
This language was designed to support penalty-freeabstractions. Not all abstractions in C++ are penalty-free,though. We first implemented the family of Endpoint Iteratorsas a hierarchy of virtual classes and found that the compil-ers we used (GCC and Clang) were not able to inline virtualmethod calls (even though they had all the required informa-tion to do so). We then rewrote the code using templates andfunctors, each iterator becoming a non-virtual class, passed . . . . Iteration sequential number E x ec u ti on ti m e , s Java normal Java inlinedC++ normal C++ inlined
Figure 8: Overhead of the abstractions used in the algorithmsinto the join algorithm as template argument. The compara-tor used by the core algorithm was a functor std::less or std::less equals . The consumers were defined asC++11 lambda-functions, also passed as a template parameter.This time both compilers were able to inline all method callsand generate very optimized code with all variables (includingiterator fields) kept in CPU registers. Java:
We face a different situation with Java, as the opti-mization is not performed by the compiler, but the Java virtualmachine (JVM) during run-time. The JVM (in particular, thestandard Oracle HotSpot implementation) compiles, optimizesand recompiles the code while executing it. It can potentiallyapply a wider range of optimizations (e.g., speculative opti-mization) than a C++ compiler can, as it actively learns aboutthe actual workload, but in the case of Java we have limitedcontrol over this process. As we show in Figure 8, Java does infact optimize the code with abstractions. Not as well as C++,but the performance difference is very small compared to amanually rewritten join.
While parallelization is not a main focus of this paper, we knowhow to parallelize our scheme and have implemented a parallelversion of our earlier EBI-Join operator [36]. We give a briefdescription here: the tuples in both input relations, r and s , aresorted by their starting time and then partitioned in a round-robin fashion, i.e., the i -th tuple of a relation is assigned topartition ( i mod k ) of that relation, where k is the number ofpartitions. By assigning close neighbors to different partitions,we lower the size of the active tuple sets, which is a crucialparameter for the performance of our algorithm. We then doa pairwise join between all partitions of r with all partitionsof s . As all partitions are disjoint, the joins can run in parallelindependently of each other. A downside of this approachis that we need k processes. Nevertheless, we achieved anaverage speed-up of 2.7, 4.3, and 5.3 for k =
2, 3, and 4,respectively, on a machine with two CPUs (eight cores each).One major difference between JoinByS and EBI-Join is thatJoinByS maintains only one active tuple set (for r ), whereasEBI-Join maintains two (one for r and one for s ). So, in order14o keep the active tuple set small, for JoinByS we only need topartition r , resulting in one process for each of the k partitions.The tuples in s are fed to each of these processes. One-dimensional Overlap:
Our approach is related to find-ing all the intersecting line segments, or intervals, givena set of n segments in one-dimensional space. The opti-mal (plane-sweeping) algorithm for doing so has complex-ity O ( n log n + k ) , where k is the number of intersectingsegments [12]. In the worst case, when we have a largenumber of intersecting segments, the complexity becomes O ( n log n + n ) . In this case, the run time of the algorithm isdominated by the output cardinality.Each segment s i is split up into a left starting event (cid:104) l i , start (cid:105) and a right ending event (cid:104) r i , end (cid:105) . Afterwards theevents of all segments are sorted, which takes O ( n log n ) time.We then traverse the sorted list of events. When encountering aleft endpoint, we insert it into a data structure D , which keepstrack of the currently active segments. When encountering aright endpoint, we remove it from D and join it with all thesegments currently stored in D . If we use a balanced searchtree for D (e.g. a red-black tree), then inserting and removingan endpoint will cost us O (log n ) . As we have n endpoints,we arrive at a total of O (2 n log 2 n ) = O ( n log n ) . Generatingall the output will take O ( k ) . If we use a hash table, insertionand removal of endpoints can be done in O (1) , for a total of O ( n ) . As long as we make sure that the entries in the hashtable are linked or packed compactly (as in our gapless hashmap), this will have an overall complexity of O ( k ) . Generalization:
Joins with predicates involving Allen orISEQL relations are not exactly the same as the one-dimensional line segment intersection. Nevertheless, the joinscan be mapped onto orthogonal line segment intersection,which is a special case of two-dimensional line segment inter-section that can also be done in O ( n log n + k ) , with k = n in the worst case, using a plane-sweeping algorithm that tra-verses the segments sorted by one dimension [12]. This alsoexplains why there were no further developments for intervaljoins recently, as the state-of-the-art algorithms achieve thiscomplexity. However, when generating the output, we cannotjust join a segment with all active ones, we need to check addi-tional constraints: two segments can overlap on the x-axis, butmay or may not do so on the y-axis. As we will see shortly,this has implications for the data structure D . Complexity of Different Join Predicates:
Let us now havea closer look at the different join predicates. For all of them,we need the relations r and s to be sorted. Either we keepthem in a Timeline Index or operate in a streaming environ-ment, in which they are already sorted, or we need to sort themin O ( n log n ) . The non-parameterized and parameterized ver-sions of START PRECEDING , END FOLLOWING , and
BEFORE (which includes
MEETS in its parameterized version) are nothard to analyze. They all have a complexity of O ( n log n + k ) .For START PRECEDING , we maintain the active tuple set of r in a gapless hash map, which means O (1) for the insertion andremoval of a single tuple, or O (2 n ) = O ( n ) in total. Addition-ally, whenever we encounter a starting event of s , we generateresult tuples, resulting in a total of O ( k ) for generating all theoutput. For the parameterized version, we merely shift theendpoints of the tuples in r . END FOLLOWING is very simi-lar, the only differences being that we generate output whenencountering ending events of s and for the parameterizedvariant, we shift the starting points of r . BEFORE is not muchdifferent, we shift both events of tuples in r and whenever weencounter starting events of s , we generate the output.We now turn to OVERLAP and
DURING joins, which weimplement using
START PRECEDING (or
END FOLLOWING )joins; the same reasoning also holds for our implementation ofthe
EQUALS , STARTS , and
FINISHES joins. Processing a
LEFTOVERLAP or a
REVERSE DURING join, we cannot just outputthe results in a straightforward way when encountering a start-ing event in s as before, as at this point we cannot determinewhether two intervals are in a left-overlap or reverse-duringrelationship: the relationship between the starting events bothlook the same, we need to see the ending events to make afinal decision. A similar argument holds for implementing OVERLAP and
DURING joins with
END FOLLOWING joins:the role of the starting and ending events are switched in thiscase. The textbook solution is to keep the intervals sorted byending events, e.g. in a tree. We can then search quickly forthe qualifying tuples in this tree and generate the output, re-sulting in an overall complexity of O ( n log n + k ) . However,it is more difficult to do this in a cache-friendly manner, as atree traversal entails more random I/O than a sequential scan.Using a gapless hash map instead, we go through all the tuplesin the active tuple set. Compared to the tree data structure, theprocessing of the join generates a larger intermediate result,as we join all intervals that satisfy an
OVERLAP or DURING join predicate. We filter out the tuples satisfying the predicatewe are not interested in afterwards with a selection opera-tor. Consequently, our approach has an overall complexity of O ( n log n + k (cid:48) ) for OVERLAP and
DURING joins, with k (cid:48) geqk .However, we utilize a sequential scan during the processingand as we will see in the experimental evaluation, introducingrandom I/O into the traversal of the active tuple set (like in atree data structure) starves the CPU and slows down the wholeprocess by two orders of magnitude. On paper, our approachlooks worse, but in practice it outperforms the allegedly bettermethod. Environment:
All algorithms were implemented in-memory in C++ by the same author and compiled with Assuming that insertion and removal costs us O (log n ) . | r.T | r.T s and r.T e dataset n min avg max domain -O3 optimization flag.We executed the code on a machine with two Intel Xeon E5-2667 v3 processors under Linux. All experiments used 12-bytetuples containing two 32-bit timestamp attributes ( T s and T e )and a 32-bit integer payload. All experiments were repeated(also with bigger tuple sizes) on a seven-year-old Intel XeonX5550 processor and on a notebook processor i5-4258U, show-ing a similar behavior. Algorithms:
We compare our approach with the Leung-Muntz family of sweeping algorithms [30, 31] and with analgorithm for generic inequality joins, IEJoin [26]. We imple-mented the Leung-Muntz algorithms in the most effective way,i.e., performing all stages of the algorithm simultaneously, asrecommended by the authors. For a fair comparison, we storedthe set of started tuples in a Gapless List, adapting the GaplessHash Map technique (Section 7.4) to the Leung-Muntz algo-rithms to boost their performance. We implemented IEJoinusing all optimizations from the original paper. Our algorithmswere implemented as described before, i.e., using abstractionsand lambda-functions.The workload for all algorithms consisted of accumulatingthe sum of T s attributes of the joined tuples. For benchmark-ing, we implemented the tuples as structures and the relationsas std::vector containers. The Endpoint Index was im-plemented analogously, using structures for the endpoints anda vector for the index. Synthetic Datasets:
To show particular performance as-pects of the algorithms we create synthetic datasets with uni-formly distributed starting points of the intervals in the rangeof [1 , ] . The duration of the intervals is distributed expo-nentially with rate parameter λ (with an average duration /λ ).To perform a join, both relations in an individual workloadfollow the same distribution, but are generated independentlywith a different seed. In the experiments, for a specific valueof λ , we varied the cardinality of the generated relations. Real-World Datasets:
We use five real-world datasets thatdiffer in size and data distribution. The main properties of themare summarized in Table 3. Here n is the number of tuples, | r.T | is the tuple interval length, “ r.T s and r.T e domain” isthe size of the time domain of the dataset and “ r.T s and r.T e flight dataset [5] is a collection of international flightsfor November 2014, start and end of the intervals representplane departure and arrival times with minute precision. TheIncumbent ( inc ) dataset [17] records the history of employeesassigned to projects over a sixteen year period at a granularityof days. The web dataset [1] records the history of files inthe SVN repository of the Webkit project over an eleven yearperiod at a granularity of seconds. The valid times indicate theperiods in which a file did not change. The feed dataset recordsthe history of measured nutritive values of animal feeds over a24 year period at a granularity of days; a measurement remainsvalid until a new measurement for the same nutritive value andfeed becomes available [14]. Finally, rather than using time asa domain, the dataset basf contains NMR spectroscopy datadescribing the resonating frequencies of different atomic nuclei[19]. As these frequencies can shift, depending on the bonds anatom forms, they are defined as intervals. For the experimentswe used self-joins of these datasets, the only exception are the“wi” and “fi” workloads, where we joined the “web” and “feed”datasets with “incumbent”. First, we look at the impact of improving the cache efficiencyof the data structure used for maintaining the active tuple set.We investigate the average latency of a getnext operation,which is crucial for generating the result tuples. We comparea linked hash map (Section 7.1), a gapless hash map (Sec-tion 7.4), and a tree structure (mentioned in Section 8). Thetree was implemented using a red-black tree (std::map) fromthe C++ Standard Library.We filled the data structures with various numbers of 32-bytetuples, then randomly added and removed tuples to simulatethe management of an active tuple set. Afterwards, we per-formed several scans of the data structures. Figure 9, showsthe average latency of a getnext operation depending on thenumber of tuples (note the double-logarithmic scale). Number of tuples getnext l a t e n c y , n s Tree Linked hash map Gapless hash map
Figure 9: Latency of getnext operationWe see that the latency of a getnext operation is not con-stant but grows depending on the memory footprint of thetuples. In order to find the cause of this, we used the Perfor-mance Application Programming Interface (PAPI) library toread out the CPU performance counters [34]. When looking at16he average number of stalled CPU cycles (PAPI-RES-STL)per getnext operation, we get a very similar picture (seeFigure 10). Therefore, the latency is clearly caused by theCPU memory subsystem. Number of tuples S t a ll e d c y c l e s Tree Linked hash map Gapless hash map
Figure 10: Stalled CPU cycles per getnext operationIn Figure 9 we can easily identify three distinct transitions.In case of a small number of tuples, all of them fit into theL1d CPU cache (32 KB per core) and we have a low latency.For the tree and linked hash map, as the tuple count growstowards 500 tuples, we start using the L2 cache (256 KB percore) with a greater latency. When we increase the number oftuples further and start reaching 4000 tuples, the data is mostlyheld in the L3 cache (20 MB in total, shared by all cores)and, finally, after arriving at a tuple count of around 300 000,the tuples are mostly located in RAM. We make a coupleof important observations. First, due to the more compactstorage scheme of the gapless hash map, the transitions setin later (at 5000, 10 000, and 600 000 tuples, respectively).Second, the improvement gains of the gapless hash map areconsiderable and can be measured in orders of magnitude(note the logarithmic scale). Third, the latency of a getnext operation for the gapless hash map plateaus at around 2.7 ns,while the latency for the linked hash map and the tree reaches100 ns.Cache misses alone do not explain all of the latency. Fig-ure 11 shows the average number of cache misses for theL1d (PAPI-L1-DCM), the L2 (PAPI-L2-TCM), and the L3cache (PAPI-L3-TCM). While in general the average numberof cache misses per getnext operation is lower for the gap-less hash map, the factor between the data structures in terms ofstalled CPU cycles is disproportionately higher (please note thedouble-logarithmic scale in Figure 10). Also, the cache missesdo not explain the left-most part of Figure 10, in which thereare no cache misses at all. The additional performance booststems from out-of-order execution. Examining the different(slightly simplified) versions of the machine code generatedfor getnext makes this clear. For the gapless hash map, thecode looks like this: loop:add rax, [rdx]add rdx, 32 ; pointer += 32 (increment)cmp rcx, rdxjne loop All CPUs have 32 KB and 256 KB per core for the L1d and L2 cache,respectively. The L3 cache for the Xeon X5550 is 8 MB and for the i5-4258u3 MB, which means that they reach the last phase earlier. while for the linked hash map we have the following picture(we omit the code for the tree, as it is much more complex): loop:add rax, [rdx]mov rdx, [rdx + 32] ; pointer = pointer->field (dereference)cmp rcx, rdxjne loop
When scanning through a gapless hash map, we add a constantto the pointer, which means that there is no data dependencybetween loop iterations. Consequently, the CPU is able topredict the instructions that will be executed in the future andcan already start preparing them out-of-order (i.e., issue cachemisses up front for the referenced data) while some of theinstructions are still waiting for data from the L1 cache. Forthe linked hash map and the tree the CPU has to wait until apointer to the next item has been dereferenced. In summary,multiple parallel cache misses in a sequential access patternare processed much faster than isolated requests to randommemory locations.We made another observation: there were no L1 instruction(L1i) cache misses. The increase of L1d cache misses for thelinked hash map and the tree for large numbers of tuples iscaused by TLB cache misses.We obtained very similar results for different CPUs on dif-ferent machines (the diagrams shown here are for an Intel XeonE5-2667 v3 processor), which led us to the conclusion that thetechniques we employ will generally improve the performanceon CPU architectures with a cache hierarchy, prefetching, andout-of-order execution. For the remainder of the experimentswe only consider the gapless hash map, as it clearly outper-forms the linked hash map.
For every tuple in s , the basic JoinByS algorithm (Section 5.3)scans the current set of active tuples in r . Using the improvedLazyJoinByS algorithm from Section 7.2, we can reduce thenumber of scans considerably. As long as we only encounterstarting events of tuples in s and no events caused by tuples in r , we can delay the scanning of the active tuple set of r . Analyzing the Data:
We now take a closer look at how fre-quently such uninterrupted sequences of events of one relationappear. Figure 12 shows this data for the table “Incumbent”from the real-world datasets when joining it with itself. Onthe x-axis we have the length of uninterrupted sequences ofstarting events and on the y-axis their relative frequency ofappearance. In 60% of the cases we have sequences of lengthten or more, meaning that our lazy joining technique can avoida considerable number of scans on active tuple sets.We found that starting events of intervals are generally notuniformly distributed in real-world datasets, but tend to clusteraround certain time points. This can be recognized by lookingat the number of distinct points in Table 3. For example, for the“Incumbent” dataset, employees are usually not assigned to newprojects on random days, the assignments tend to happen at thebeginning of a week or month. For the “Feed” dataset, multiple17 Number of tuples L cac h e m i ss e s Number of tuples L cac h e m i ss e s Tree Linked hash map Gapless hash map Number of tuples L cac h e m i ss e s Figure 11: Cache misses per getnext operationmeasurements (which are valid until the next one is made) aretaken in the course of a day, resulting in a whole batch ofintervals starting at the same time. The clustering is not justdue to the relatively coarse granularity (one day) of these twodatasets. The “Webkit” repository dataset, which looks atintervals in which files are not modified, has a granularitymeasured in milliseconds. Still we observe a clustering ofstarting events: a commit usually affects and modifies severalfiles. The “Flight” dataset, which has a granularity of minutes,also exhibits a similar pattern in the form of batched departuretimes. Even for the frequency data of the “BASF” dataset, thevalues for the start and end points of the intervals seem to beclustered around multiples of one hundred.1 2 3 4 5 6 7 8 9 10+ % % % % % % % . % . % . % . % . % . % . % . % % Uninterrupted endpoint sequence length R e l a ti v e fr e qu e n c y Figure 12: Distribution of uninterrupted sequence lengths forself-join of the “inc” dataset
Reduction Factor:
The real performance implication is thatLazyJoinByS executes fewer getnext operations than Join-ByS in such a scenario. The actual reduction depends not onlyon the clusteredness of the events, but also on the size of thecorresponding active tuple set and the buffer capacity reservedin LazyJoinByS. We define a getnext operation reductionfactor ( GNORF ), changing the cost for scanning throughactive tuple sets for the LazyJoinByS to k · c getnext / GNORF ,where k is the cardinality of the result set.For the self-join of the “Incumbent” dataset and for buffer ca-pacity of 32 the GNORF is equal to 23.6, which correspondsto huge savings in terms of run time. We also calculated thisstatistic for self-joins of other real-world datasets (“feed”: 31.2,“web”: 9.73, “flight”: 7.14, “basf”: 11.2). Even for self-joinswe get a considerable reduction factor: when encounteringmultiple starting events with the same timestamp, we first deal with all those of one relation before those of the other.
Join Performance:
Next we investigate the relative perfor-mance of an actual join operation, employing JoinByS andLazyJoinByS for an overlap join. Figure 13 depicts the re-sults for the “Incumbent” (inc), “Webkit” (web), “Feed” (feed),“flight”, and “basf” datasets, showing that LazyJoinByS outper-forms JoinByS by up to a factor of eight. Therefore, we onlyconsider LazyJoinByS from here on. i n c . . LazyJoinByS, gapless JoinByS, gapless w e b f ee d , fl i gh t . . b a s f , , Joining time, s (linear scale)
Figure 13: JoinByS vs LazyJoinByS, real-world data
To test the scalability of our algorithms we compared themto the Leung-Muntz and IEJoin algorithms while varying thecardinality of the synthetic datasets. For the IEJoin we usedthe algebraic expressions in Table 1. Due to space constraints,we limit ourselves to three characteristic joins (join-only, joinwith selection, and parameterized join with map operators):
START PRECEDING ( ),
INVERSE DURING ( ), and
BEFORE join ( ), where δ is set to the average tuple length in the outerrelation. We tested the algorithms using short, medium-lengthand long tuples with average lengths of . · , . · , and . · time points, respectively. The speedup of our approachcompared to Leung-Muntz and IEJoin is shown in Figures 14and 15, respectively. We see that our solution quickly becomesfaster than the Leung-Muntz algorithms and that the difference18 ong tuples (gapless) Medium tuples (gapless) Short tuples (gapless)Long tuples (tree) Medium tuples (tree) Short tuples (tree) . Relation cardinalities R e l . p e rf ., ti m e s f a s t e r INVERSE DURING JOIN Relation cardinalities
START PRECEDING JOIN . Relation cardinalities
BEFORE JOIN
Figure 14: Performance of our solution w.r.t. Leung-Muntz algorithm, synthetic data
Long tuples Medium tuples Short tuples Relation cardinalities R e l . p e rf ., ti m e s f a s t e r INVERSE DURING JOIN Relation cardinalities
START PRECEDING JOIN Relation cardinalities
BEFORE JOIN
Figure 15: Performance of our solution w.r.t. IEJoin, synthetic datagrows with increasing numbers of tuples and their lengths.Only for small relations and short tuples, Leung-Muntz is faster.Leung-Muntz is a simpler algorithm, so for light workloads,i.e., small relations and short intervals (resulting in smallerresult sets), it shows a good performance as it does not have aninitialization overhead. However, it does not scale as well asour algorithm. In the left-most diagram of Figure 14 (
INVERSEDURING JOIN ), we also show the difference between usinggapless hash maps and trees for managing the active tuple set.We only do so for the
INVERSE DURING JOIN , as for the otherjoins a tree would only add overhead without any benefits. Forthe
INVERSE DURING JOIN , with a tree we generate only validtuples, meaning that we do not need a selection operator asneeded for the gapless hash map. While the tree-based activetuple set seems to pay off for long tuples (meaning largeractive tuple sets), for shorter tuples the situation is not thatclear. Consequently, we propose to use gapless hash maps, asthis avoids the implementation and integration of an additionaldata structure that is only useful for some interval relations andeven then does not always show superior performance. For theremainder of Section 9, we restrict ourselves to gapless hashmaps.For the IEJoin, the performance differs by one to several or-ders of magnitude depending on relation cardinalities and tuplelengths. Even though the IEJoin is highly optimized, it still hasquadratic complexity and cannot compete with specialized al-gorithms. Therefore, we drop it from the further investigation.Because the Leung-Muntz and the IEJoin algorithms do notscale well, we stopped running experiments for larger relation flight inc web basf feed wi fi
Real-world dataset R e l . p e rf ., ti m e s f a s t e r DURING START PRECEDING BEFORE
Figure 16: Performance of our solution w.r.t. Leung-Muntz,real-world datacardinalities when they took a few hours to conduct. We alsorestrict ourselves to the
INVERSE DURING join from now on,since Leung-Muntz showed the best performance for it.
We repeated the experiments on the real-world workloads.The speedup is shown in Figure 16. Here the performancedifference is two orders of magnitude in some cases. On theone hand, this is due to the lazy joining cache optimization,which is more effective for the real-world datasets (cf. [36]).On the other hand, the heuristics used in the Leung-Muntzalgorithm work worse for real-worlds workloads and especiallythose where the relation cardinalities differ substantially.19 .2.5 Comparison with bgFS
Our approach and bgFS [8] follow different paradigms for pro-cessing the data: backward scanning versus forward scanning.The iterator framework we utilize has been geared completelytowards the backward scanning paradigm, allowing us to intro-duce changes to endpoint timestamps on the fly. This makesit challenging to integrate bgFS into our iterator frameworkeffectively. Clearly, we can add shifted intervals to the tuplesin the relations before executing an bgFS join. However, thisrequires and additional sweep over the relations, eating up theefficiency gained by forward scanning the relations. On top ofthat, bgFS will start producing output tuples at the very endof the processing time. Figure 17 shows the run time of pro-cessing a (general)
BEFORE join using bgFS and our approach(this was run on an i7-4870HQ CPU with four cores, 32 KBand 256 KB per core for the L1d and L2 cache, respectively,and 6 MB L3 cache). i n c . . bgFS JoinByS w e b i n c - w e b . w e b - i n c . . Joining time, s (linear scale)
Figure 17: Comparison of JoinByS with bgFS, real-world data
To explore the source of the performance difference betweenthe algorithms, we tested the selectivity of the selection opera-tion that is applied after the join in the Leung-Muntz algorithmsand, in some cases, in ours. The results for the
INVERSE DUR - ING JOIN are shown in Figure 18. The other joins exhibit asimilar behavior. We see that both algorithms are keeping thesizes of the working sets similar. Our algorithm is slightlymore effective, but insufficiently so to explain the performancedifference. We look at the real cause in the next section.
In this experiment we measured the number of tuple endpointcomparison operations (e.g., “ r.T s < s.T s ”). The results forthe INVERSE DURING JOIN are shown in Figure 19. We seethat the difference in the number of comparisons is huge. TheLeung-Muntz algorithm performs many more comparisonsbecause it has to heuristically estimate the next tuple to be readand to perform the garbage collection of the outdated tuples.The selection operation of the Leung-Muntz algorithm requirestwo comparisons. Our algorithm, on the other hand, requires a flight inc web basf feed wi fi . Real-world dataset S e l ec ti v it y Our solution Leung-Muntz join
Figure 18: Selectivity of the filtering selection operator afterthe main join,
INVERSE DURING JOIN flight inc web basf feed wi fi Real-world dataset C o m p a r i s on c oun t Our solution Leung-Muntz join
Figure 19: Join comparison counts, real-world datasingle comparison in the selection operation for the
INVERSEDURING JOIN , and no tuple comparisons at all for the
BEFORE and
START PRECEDING JOIN . In this experiment we measure the delay in producing outputtuples of the Leung-Muntz algorithms. A low latency is animportant feature for event detection systems. While our al-gorithms generates output as soon as possible, when all of therequired endpoints have been spotted, the Leung-Muntz has adelay caused by the fact that it requires streams of completeand ordered tuples as the input. The average latency (expressedin average tuple lengths, as the different data sets have vastlydifferent granularities) is shown in Figure 20. Depending onthe workload the differences in latency can in some cases reachten or even a hundred times the average tuple length.
10 Conclusions and Future Work
We developed a family of effective, robust and cache-optimizedplane-sweeping algorithms for interval joins on different in-terval relationship predicates such as Allen’s or parameterizedISEQL relations. The algorithms can be used in temporaldatabases, exploiting the Timeline Index, which made its wayinto a prototype of a commercial temporal RDBMS as themain universal index supporting temporal joins, temporal ag-gregation and time travel. We thus extend the set of operationssupported by this index. Our solution is based on a flexible20 ight inc web basf feed wi fi . . Real-world dataset A vg l a t e n c y i n a vg t up l e l e ng t h Leung-Muntz
Figure 20: Algorithm reporting latency,
REVERSE DURINGJOIN , real-world dataframework, that allows combining its components in differentways to elegantly and efficiently express any interval join interms of a single core function. Additionally, our approachmakes good use of the features of contemporary hardware,utilizing the cache infrastructure well.We compared the performance of our solution with the state-of-the-art in interval joins on Allen’s predicates—the Leung-Muntz and the IEJoin algorithm. The results show that oursolution is several times faster, scales better and is more stable.Another major advantage of our approach is that it can bedirectly applied to real-time stream event processing, as it willreport the results as soon as logically possible for the appliedpredicate, without necessarily waiting for intervals to finish.The Leung-Muntz algorithm has to wait for the tuples to finishbefore processing them. Moreover, the requirement for tuplesto be processed chronologically allows any unfinished tupleto block the processing of all following tuples. The IEJoinis also not suitable for a streaming environment: it needs thecomplete relations to work.For future work, we want to explore the possibilities of em-bedding our solution into a real-time complex event processingframework. In particular, we want to combine the results ofmultiple joins to detect patterns within n streams of events ofdifferent type. Additional research directions are working outthe details of parallelizing our approach and the handling ofmultiple predicates in a join operation. References [1] The webkit open source project. webkit.org , 2012.[2] J. F. Allen. Maintaining knowledge about temporal inter-vals.
Commun. ACM , 26(11), Nov. 1983.[3] M. R. ´Alvarez, P. F´elix, and P. Cari˜nena. Discovering met-ric temporal constraint networks on temporal databases.
Artif. Intell. Med. , 58(3):139–154, July 2013.[4] L. Arge, O. Procopiuc, S. Ramaswamy, T. Suel, and J. S.Vitter. Scalable sweeping-based spatial join. In
VLDB ,1998. [5] A. Behrend and G. Sch¨uller. A case study in optimizingcontinuous queries using the magic update technique. In
SSDBM , 2014.[6] F. Bettini, F. Persia, and S. Helmer. An interactive frame-work for video surveillance event detection and modeling.In
CIKM , 2017.[7] J. A. Blakeley, W. J. McKenna, and G. Graefe. Expe-riences building the open OODB query optimizer. In
SIGMOD , 1993.[8] P. Bouros and N. Mamoulis. A forward scan basedplane sweep algorithm for parallel interval joins.
PVLDB ,10(11), Aug. 2017.[9] P. Bouros and N. Mamoulis. Interval count semi-joins.In
EDBT , 2018.[10] B. Chawda, H. Gupta, S. Negi, T. A. Faruquie, L. V.Subramaniam, and M. Mohania. Processing intervaljoins on Map-Reduce. In
EDBT , 2014.[11] M. W. Chekol, G. Pirr`o, and H. Stuckenschmidt. Fast in-terval joins for temporal SPARQL queries. In
Companionof WWW , pages 1148–1154, 2019.[12] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein.
Introduction to Algorithms . MIT Press, 3rd edition, 2009.[13] R. Dechter, I. Meiri, and J. Pearl. Temporal constraintnetworks.
Artif. Intell. , 49(1-3):61–95, May 1991.[14] A. Dign¨os, M. H. B¨ohlen, and J. Gamper. Overlap inter-val partition join. In
SIGMOD , 2014.[15] C. Freksa. Temporal reasoning based on semi-intervals.
Artif. Intell. , 54(1-2):199–227, Mar. 1992.[16] D. Gao, C. S. Jensen, R. T. Snodgrass, and M. D. Soo.Join operations in temporal databases.
VLDB J. , 14(1),Mar. 2005.[17] J. A. G. Gendrano, R. Shah, R. T. Snodgrass, and J. Yang.University information system (UIS) dataset. TimeCenterCD-1, 1998.[18] H. Gunadhi and A. Segev. Query processing algorithmsfor temporal intersection joins. In
ICDE , 1991.[19] S. Helmer. An interval-based index structure for structureelucidation in chemical databases. In
IFSA , 2007.[20] S. Helmer and F. Persia. ISEQL, an interval-based surveil-lance event query language.
IJMDEM , 7(4), Oct. 2016.[21] F. H¨oppner and S. Peter. Temporal interval pattern lan-guages to characterize time flow.
WIREs: Data Miningand Knowledge Discovery , 4(3):196–212, 2014.[22] C. S. Jensen and R. T. Snodgrass. Temporal data man-agement.
IEEE Trans. Knowl. Data Eng. , 11(1):36–44,1999.2123] M. Kaufmann.
Storing and Processing Temporal Data inMain Memory Column Stores . PhD thesis, ETH Zurich,2014.[24] M. Kaufmann, P. M. Fischer, N. May, C. Ge, A. K. Goel,and D. Kossmann. Bi-temporal Timeline Index: A datastructure for processing queries on bi-temporal data. In
ICDE , 2015.[25] M. Kaufmann, A. A. Manjili, P. Vagenas, P. M. Fischer,D. Kossmann, F. F¨arber, and N. May. Timeline Index: Aunified data structure for processing queries on temporaldata in SAP HANA. In
SIGMOD , 2013.[26] Z. Khayyat, W. Lucia, M. Singh, M. Ouzzani, P. Papotti,J.-A. Quian´e-Ruiz, N. Tang, and P. Kalnis. Fast andscalable inequality joins.
VLDB J. , 26(1), Feb. 2017.[27] M. K¨orber, N. Glombiewski, A. Morgen, and B. Seeger.Tpstream: low-latency and high-throughput temporal pat-tern matching on event streams.
Distributed and ParallelDatabases , Jul 2019.[28] M. K¨orber, N. Glombiewski, and B. Seeger. Tpstream:Low-latency temporal pattern matching on event streams.In
EDBT , pages 313–324, 2018.[29] K. G. Kulkarni and J. Michels. Temporal features in SQL:2011.
SIGMOD Record , 41(3):34–43, 2012.[30] T. Y. C. Leung and R. R. Muntz. Query processingfor temporal databases. Technical Report CSD-890020,University of California, 1989.[31] T. Y. C. Leung and R. R. Muntz. Query processing fortemporal databases. In
ICDE , 1990.[32] T. Y. C. Leung and R. R. Muntz. Temporal query pro-cessing and optimization in multiprocessor database ma-chines. In
VLDB , 1992.[33] J. Ma and P. J. Hayes. Primitive intervals versus point-based intervals: Rivals or allies?
Comput. J. , 49(1):32–41, 2006.[34] S. Moore and J. Ralph. User-defined events for hardwareperformance monitoring. In
ICCS Workshop , 2011.[35] D. Piatov and S. Helmer. Sweeping-based temporal ag-gregation. In
SSTD , 2017.[36] D. Piatov, S. Helmer, and A. Dign¨os. An interval joinoptimized for modern hardware. In
ICDE , 2016.[37] J. Pilourdault, V. Leroy, and S. Amer-Yahia. Distributedevaluation of top-k temporal joins. In
SIGMOD , 2016.[38] A. Segev and H. Gunadhi. Event-join optimization intemporal relational databases. In
VLDB , 1989.[39] S.-Y. Wu and Y.-L. Chen. Discovering hybrid temporalpatterns from sequences consisting of point- and interval-based events.
Data Knowl. Eng. , 68(11):1309–1330, Nov.2009.
A Correctness of Rewrites
Joining the tuples in the relations r and s such that they satisfythe Allen and ISEQL relations shown in Table 1 results ina tuple set RS name = { r × s | r ∈ r , s ∈ s : P ( r, s ) } where name is the name of the relation in the first column and P ( r, s ) is the formal definition in the third column. We followthe same order as in Section 4. A.1 Non-parameterized Joins
ISEQL Start Preceding and End Following Joins:
Thesefollow directly from the definition of our interval-timestampjoins (cid:111)(cid:110) • θ start and (cid:111)(cid:110) • θ end . Overlap and During Joins:
For the
LEFT OVERLAP JOIN ,the tuples have to satisfy P ( r, s ) = r.T s (cid:54) s.T s < r.T e (cid:54) s.T e . The inequalities r.T s (cid:54) s.T s < r.T e are enforced bythe join (cid:111)(cid:110) • (cid:54) start , the inequality r.T e (cid:54) s.T e by the selectionpredicate. For the right overlap join, this works analogouslyusing the join (cid:111)(cid:110) • (cid:54) end and a selection.For the (reverse) DURING JOIN , only the inequality enforcedby the selection operator changes.
Before Join:
By replacing T s with T e and T e with ∞ inevery tuple in r and then running a (cid:111)(cid:110) • (cid:54) start join, we get tuplesthat satisfy the predicate r.T e (cid:54) s.T s < ∞ , which is equiva-lent to the predicate for the BEFORE relation. For the Allenrelation we use (cid:111)(cid:110) • < start instead. Meets Join:
Applying the map operators and running a (cid:111)(cid:110) • (cid:54) start join, P ( r, s ) becomes r.T e (cid:54) s.T s < r.T e + 1 . Sincewe use integer timestamps, this means that r.T e = s.T s . Equals and Starts Joins:
For the
EQUALS JOIN the (cid:111)(cid:110) • (cid:54) start join enforces the predicate r.T s (cid:54) s.T s < r.T s + 1 , whichbecomes r.T s = s.T s due to the integer timestamps, while theselection does so for r.T e = s.T e . For the STARTS JOIN thepredicate enforced by the selection operator changes accord-ingly.
Finishes Join:
For the
FINISHES JOIN we use the (cid:111)(cid:110) • (cid:54) end operator arriving at r.T e − < s.T e (cid:54) r.T e , which meansthat r.T e = s.T e . Together with the selection predicate of s.T s < r.T s , we complete the predicate for the FINISHESJOIN . A.2 Parameterized Joins
ISEQL Start Preceding and End Following Joins:
The (cid:111)(cid:110) • (cid:54) start join (together with the map operators) guarantees usthat r.T s (cid:54) s.T s < min( r.T e , r.T s + δ + 1) . Assume that theminimum is r.T e , which means that r.T e (cid:54) r.T s + δ + 1 . Thefirst part of P ( r, s ) , r.T s (cid:54) s.T s < r.T e follows directly. Wealso know that s.T s < r.T e (cid:54) r.T s + δ + 1 and due to integertimestamps can conclude that s.T s − r.T s (cid:54) δ .22or the other case, the minimum is r.T s + δ + 1 , whichmeans r.T s + δ + 1 (cid:54) r.T e . Due to integer timestamps, itimmediately follows that s.T s (cid:54) r.T s + δ . We also know that r.T s (cid:54) s.T s < r.T s + δ + 1 (cid:54) r.T e and therefore r.T s (cid:54) s.T s < r.T e .The proof for the END FOLLOWING JOIN follows alongsimilar lines.
Before Join:
The (cid:111)(cid:110) • (cid:54) start join enforces r.T e (cid:54) s.T s Here we only show the prooffor the LEFT OVERLAP JOIN , the proofs for the remainingoperators follow the same pattern.With the (cid:111)(cid:110) • (cid:54) start join we already make sure that r.T s (cid:54) s.T s and with the selection operator that r.T e (cid:54) s.T e and s.T e (cid:54) r.T e + ε ⇔ s.T e − r.T e (cid:54) ε . If r.T e (cid:54) r.T s + δ + 1 then s.T s < r.T e follows from the (cid:111)(cid:110) • (cid:54) start as well and because s.T s < r.T e (cid:54) r.T s + δ , we know that s.T s < r.T s + δ ,which is equivalent to s.T s − r.T e (cid:54) δ for integer intervals. If r.T s + δ +1 (cid:54) r.T e , then s.T s < r.T s + δ +1 ⇔ s.T s − r.T s (cid:54) δ (for integer intervals) follows from the (cid:111)(cid:110) • (cid:54) start and because s.T s < r.T s + δ + 1 (cid:54) r.T e , we also know that s.T s < r.T e . B Implementation of Iterators In this section we illustrate the inner workings of the differ-ent iterators and show how they can be implemented. If wehave an instance of an Endpoint Iterator, iterator , then totraverse all endpoints it represents, we can use the followingpseudocode: while not iterator.isFinished dooutput(iterator.getEndpoint)iterator.moveToNextEndpointend B.1 Index Iterator We use the std::vector container of the C++ StandardTemplate Library (STL) as the implementation of the EndpointIndex, resulting in the following code: • IndexIterator(endpointIndex) : this.it = endpointIndex.begin();this.end = endpointIndex.end(); • getEndpoint : return *it; • moveToNextEndpoint : ++it; • isFinished : return it == end; B.2 Filtering Iterator • FilteringIterator(iterator, type) : this.iterator = iterator;this.type = type;while getEndpoint.type (cid:54) = type domoveToNextEndpoint; • getEndpoint : return iterator.getEndpoint; • moveToNextEndpoint : do iterator.moveToNextEndpoint;while not isFinished andgetEndpoint.type (cid:54) = type; • isFinished : return iterator.isFinished; B.3 Shifting Iterator • ShiftingIterator(iterator, delta, type) : this.iterator = iterator;this.delta = delta;this.type = type; • getEndpoint : var endpoint = iterator.getEndpoint;endpoint.timestamp += delta;endpoint.type = type;return endpoint; • moveToNextEndpoint : iterator.moveToNextEndpoint; • isFinished : return iterator.isFinished; B.4 Merging Iterator • MergingIterator(iterator1, iterator2) : this.it1 = iterator1;this.it2 = iterator2;moveToNextEndpoint; • getEndpoint : return this.endpoint; • moveToNextEndpoint : if it2.isFinished or not it1.isFinishedand it1.getEndpoint < it2.getEndpointthenthis.endpoint = it1.getEndpoint;it1.moveToNextEndpoint;elsethis.endpoint = it2.getEndpoint;it2.moveToNextEndpoint;end • isFinished : return it1.isFinished and it2.isFinished; B.5 First End Iterator • FirstEndIterator(iterator) : this.iterator = iterator;this.hs = new HashSet; • getEndpoint : return this.endpoint; moveToNextEndpoint : do iterator.moveToNextEndpoint;if getEndpoint.type = end thenif getEndpoint.tuple id (cid:54)∈ hs theninsert getEndpoint.tuple id intohs; break;elseremove getEndpoint.tuple id fromhs;while not isFinished; • isFinished : return iterator.isFinished; B.6 Second Start Iterator • SecondStartIterator(iterator) : this.iterator = iterator;this.hs = new HashSet;while getEndpoint.type = start andgetEndPoint.tuple id (cid:54)∈ hsdo moveToNextEndpoint; • getEndpoint : return this.endpoint; • moveToNextEndpoint : do iterator.moveToNextEndpoint;if getEndpoint.type = start thenif getEndpoint.tuple id ∈ hs thenremove getEndpoint.tuple id fromhs; break;elseinsert getEndpoint.tuple id intohs;while not isFinished; • isFinished : return iterator.isFinished;return iterator.isFinished;