Ausarbeitung Hauptseminar Data Stream Management Systems

Transcription

Ausarbeitung Hauptseminar Data Stream Management Systems
Ausarbeitung
Hauptseminar Data Stream Management Systems
Thema:
Optimierungsmodelle für DSMS
Bearbeiter:
Andreas Unseld
Betreuer:
Steffen Rost
1
1. Einleitung
1.1 Beispiel 1: Beispiel zur Optimierung von Programmen
2. Herkömmliche Optimierungsmodelle
2.1 Ein Überblick über die Verarbeitung von SQL – Statements in System R
2.2 Die Kostenanalyse im Detail
2.3 Beispiel 2
3. Optimierungsmodelle für Kontinuierliche Querys
3.1 Problemstellung
3.2 Beispiel 3: Beispiel zur Problemstellung
4. Moving Window (time-unit based optimization)
4.1 Einführung
4.2 Beispiel 4: Beispiel zu Moving Windows
4.3 Optimierung von Moving Windows
4.4 Funktionsweise von NLJ und HJ
4.5 Allgemeine Kostenabschätzung für Joins
4.6 Kostenabschätzung für Nested Loop Joins
4.7 Kostenabschätzung für Hash Joins
4.8 Beispiel 5
5. Grundlagen der rate-based Optimierung
5.1 Ein Überblick
5.2 Die Ausgaberate als Funktion von der Eingaberate
5.3 Kostenanalyse für Joins
5.4 Beispiel 6
5.5 Genereller Rahmen für die rate-based Optimierung
5.6 Beispiel 7
6. Fazit
7. Anhang
2
1. Einleitung
Optimierungen sind aus den verschiedensten Bereichen bekannt. Nicht nur von der
Informatik, sondern auch aus der Industrie. Beispiele hierfür sind vor allem
Prozessoptimierungen in Fertigungsanlagen oder Optimierungen im Bezug auf
Transaktionen und Budget. In der Informatik assoziieren wir Optimierung mit der
Reduktion von CPU-, Speicher- und I/O Last. Eine solche Ressourcenschonung lässt sich
z..B. durch Verbesserungen im Programmcode erreichen, indem „Überflüssige“ Befehle
wegrationalisiert werden. Hierbei können, - insbesondere bei oft wiederholten
Programmschleifen – deutliche Performancesteigerungen erzielt werden. Diese
sogenannte Code – Optimierung übernimmt mittlerweile der Compiler. Man kann die
Code –Optimierung also als letzen Schritt in der Erstellung eines Programms verstehen.
Die Basis für ein optimiertes Programm ist allerdings ein möglichst optimales
Programmmodell. Unter einem Programmmodell kann man eine Projektion des
Programmablaufs auf eine vereinfachte modellhafte Struktur verstehen. Analog zum
Programmmodell ist ein Optimierungsmodell eine vereinfachte Struktur eines meist
deutlich komplexeren Vorganges.
1.1 Ein Beispiel zur Optimierung von Programmen (Beispiel 1):
Es soll ein Programm erstellt werden, dass die Summe X = ∑x (i = 1..100) berechnet. Eine
Lösung wäre:
X = 0;
For ( i = 1; i <=100; i++) X = X + i;
Die Direkte Übersetzung in Assembler ist:
Mov AX, 0
;X
Mov BX, 1
;i
Mov CX, 100
Mark:
Add AX, BX
Inc BX
Loop Mark
Der Codeoptimierer des Compilers könnte daraus machen:
Mov AX, 0
;X
Mov CX, 100
Rep Add AX,CX
Das verkleinert den Rechenaufwand auf weniger als die Hälfte im Vergleich zum ersten
Programm, behält aber dennoch den Aufwand O(x).
Ein findiger Programmierer könnte allerdings das Programm:
X = n(n+1) / 2
entwickeln, welches für n = 100 das gewünschte Ergebnis liefert und den konstanten
Aufwand O(1) hat.
3
2. Herkömmliche Optimierungsmodelle:
In dieser Ausarbeitung sollen Optimierungsmodelle für Data Stream Management
Systeme (DSMS) behandelt werden. Um einen Überblick zu bekommen, was ein
Optimierungsmodell für DSMS ist, betrachten wir zunächst herkömmliche
Optimierungsmodelle wie sie für Datenbank Management System (DBMS) verwendet
werden. Dazu sehen wir uns die Verarbeitung von SQL – Statements im
DBMS System – R an. Der Optimierer von System R bietet, obwohl schon sehr alt, einen
guten Einblick in das Thema. System R war ein experimentelles Datenbanksystem von
IBM, welches in den siebziger Jahren entwickelt wurde. Es gilt als eines der ersten SQL –
Datenbanksysteme.
Aus dem System R Projekt stammt das „Standart – Paper“ [1] zur Optimierung von
DBMS. Die Autorin Selinger beschreibt darin ein Kosten – basiertes Optimierungsmodell.
2.1 Ein Überblick über die Verarbeitung von SQL – Statements in System R
System R erhält SQL – Statements entweder als Benutzereingabe von einem Terminal,
oder als Eingabe aus einem anderen Programm.
Sobald System R das Statement vollständig erhalten hat, werden vier Schritte zur
Verarbeitung und Beantwortung der Eingabe durchgeführt:
1. Parsen des Statements:
Der Parser untersucht die Query – Blocks der Eingabe auf syntaktische Korrektheit. Ein
Query – Block besteht aus einer SELECT Liste, einer FROM Liste und aus einem
WHERE – Baum. Die SELECT Liste gibt an, welche Attribute gesucht sind. Die FROM
Liste beinhaltet die Tabellen, in denen gesucht werden soll und der WHERE – Baum
enthält die abzurufenden Items, Referenzen auf die benötigten Tabellen und boolesche
Verknüpfungen von Bedingungen die durch den Benutzer (oder ein Programm)
definierten wurden.
Man sollte beachten dass ein Statement aus mehreren Query – Blocks bestehen kann, da
die im WHERE – Baum gestellten Bedingungen wiederum Querys enthalten können. Ein
Beispiel für einen Query – Block ist:
SELECT Name FROM Mitarbeiter
WHERE (Beruf != `Geschäftsführer` AND Alter > 50)
Wenn die syntaktische Analyse beendet wurde, und keine Fehler im Statement gefunden
wurden, so wird das Statement an das Optimierungsmodul weitergereicht.
2. Optimierung:
Das Optimierungsmodul in System R hat den größten Aufgabenbereich. Es leistet die
Vorarbeit, welche für die Optimierung notwendig ist, optimiert die Queries in Bezug auf
die benötigte Verarbeitungszeit und übergibt den resultierenden Ausführungsplan in ASR
(Access Specification Language) an den Code – Generator. Dieser Vorgang soll im
Folgenden präziser erläutert werden.
4
Im Vorfeld der Optimierung berechnet das Optimierungsmodul die Tabellen und Spalten,
die in den Query – Blöcken referenziert werden und verifiziert deren Existenz. Zudem
sammelt das Modul Informationen und Statistiken über die einzelnen Referenzen. Das
sind insbesondere die Physikalische Verteilung der Daten einer Tabelle auf dem
Datenträger, die Kardinalität der Tabellen (Kardinalität = Anzahl von Reihen in einer
Tabelle) und die so genannten Access – Paths. Access – Paths sind Wege über die auf die
gewünschte Information zugegriffen werden kann. Das soll ein Beispiel verdeutlichen:
Gegeben sind 4 Tabellen die sich wie in der Grafik dargestellt referenzieren:
Tabelle D
Tabelle A
Tabelle B
Tabelle C
Grafik: Beispiel für Access – Paths
Gesucht sind Elemente aus der Tabelle C. Das Optimierungsmodul hat aber nur
Referenzen auf die Tabelle A und die Tabelle D. Also muss es sich, um auf die Tabelle C
zugreifen zu können zwischen den Access – Paths A Æ B Æ C oder D Æ C entscheiden.
Da der Path D Æ C der Kürze ist, würde die Entscheidung auf ihn fallen.
Sind die wesentlichen Daten gesammelt, so wird die Ausführungsreihenfolge der Query –
Blocks bestimmt. Ist dies geschehen, so werden die Relationen in den Blocks betrachtet.
Befinden sich in einem Block mehrere Relationen, (dies ist der Fall bei Joins) so wird die
Join – order (die zeitliche Ausführungsreihenfolge der Joins) permutiert. Aus diesen
Permutationen entstehen die Query – Plans, welche dann dem eigentlichen
Optimierungsprozess übergeben werden.
Ein Query – Plan ist eine Möglichkeit eine Query zu beantworten. Haben wir z.B. eine
Query welche die Joins A
B und A
C beinhaltet, so wäre ein Query – Plan:
(A
B)
C und (A
C)
B ein Anderer.
Der in System R verwendete Optimierer (Kernkomponente des Optimierungsmoduls)
optimiert Querys, indem er die Kosten der möglichen Query - Pläne abschätzt, und den
günstigsten Plan zur Ausführung bringt.
Die Kosten eines Plans entstehen aus der Summe von der geschätzten Anzahl zu ladenden
Seiten (IO) und der geschätzten Anzahl von auszuführender Instruktionen (CPU). Diese
Schätzungen werden mit Hilfe der im Vorfeld gesammelten Daten durchgeführt.
Im Folgenden soll nun die Kostenanalyse etwas genauer betrachtet werden.
5
2.2 Die Kostenanalyse im Detail
Eines der Schlüsselkonzepte von System R war die
Klassifikation von Operatoren in Bezug auf ihre
Trennschärfe (engl.: selectivity). Die Trennschärfe gibt
an, wie viele Tupel einer Tabelle durch den Operator
affektiert werden. Das kann an dem oben gezeigten
Beispiel erläutert werden:
Plan
SELECT Name FROM Mitarbeiter
WHERE ( Beruf != `Geschäftsführer` AND Alter > 50)
Hier könnte man annehmen, dass das Alter der
Mitarbeiter zwischen 20 und 60 gleich verteilt ist.
Dementsprechend sind 25 % der Mitarbeiter vom
Operator > in Alter > 50 betroffen. Der Trennschärfe –
Faktor liegt hier also bei 0.25.
Selinger präsentiert in ihrem Paper eine Tabelle von
Operatoren, und Strategien um deren Trennschärfen zu
schätzen. Diese Strategien sind von der physischen
Verteilung der Daten in der Datenbank abhängig. Sind
keine oder nur unzureichende Statistiken bekannt, so
wird der Trennschärfe-Faktor einfach auf F=1/10
(magic number) geschätzt.
DBKatalog:
Kardinalität
en von
einzelnen
Relationen
Statistik
über
Verteilung
von
Relationen
Trennschärf
en von
Prädikaten
Kosten für den Plan
Wenn man die Verteilung von Daten, und den
Trennschärfe – Faktor von jedem Operatortyp kennt,
lassen sich die Kardinalitäten von jeder durch
Datenbankoperatoren entstehenden Tabelle und der
Aufwand die Daten von einem Festspeicher zu lesen,
abschätzen.
Hierzu wird zuerst die Kardinalität von jeder gespeicherten Relation bestimmt. Diese
Information erhält der Optimierer aus dem Datenbankkatalog. Wenn nun ein Operator auf
eine Tabelle in System R angewendet wird, multipliziert der Optimierer die ursprüngliche
Kardinalität mit dem Trennschärfe – Faktor des Operators. Daraus resultiert die
Kardinalität der entsehenden Tabelle. Aus den Kardinalitäten lassen sich nun die Kosten
für die einzelnen Operatoren eines Plans abschätzen. Die Gesamtkosten für den Plan sind
die Summe der Einzelkosten.
6
2.3 Beispiel 2
Das zuvor gezeigte soll nun anhand eines Beispiels verdeutlicht werden:
Wir betrachten das SQL – Statement:
SELECT x FROM A,B,C
WHERE ( A.x = B.y AND A.x = C.z)
Die möglichen Query – Plans sind: (A
B)
C und (A
C)
B.
Der Einfachheit halber nehmen wir an, das die Tabellen A, B und C alle die gleiche
Kardinalität |A| = |B| = |C| = 100 haben. Nehmen wir in Bezug auf die Trennschärfe an,
dass 10 % der Werte A.x gleich der Werten B.y sind und das der Operator A.x = B.y
eine Übereinstimmung von 50 % erzielt.
Auf Basis der gegebenen Daten soll nun der bessere Query – Plan bestimmt werden.
Zunächst stellen wir die Pläne als Graphen dar:
Plan A
|A
A
Plan B
B| = 5
B
A
|B = 100|
|A
A
|A
C| = 50
B
C
A
|C| = 100
|A| = 100
|A| = 100
A
|A
C
A
C| = 5
C
B| = 10
|C| = 100
C
B
|B| = 100
B
Grafische Darstellung von Query - Plans
Stellen wir nun die Kosten für die beiden Pläne auf:
Dazu nehmen wir an, die Kosten für einen Joins seinen das Produkt der Kardinalitäten
von den verwendeten Tabellen. Das ist selbstverständlich eine stark vereinfachte Form
der Kostenanalyse, ist aber zu Beispielzwecken geeignet.
Die Kosten für Plan A betragen:
K ( PlanA) = K ( A × C ) + K ( A × B) = A * C + A * B = 100 *100 + 50 *100 = 15000
Die Kosten für Plan B getragen:
K ( PlanB) = K ( A × B) + K ( A × C ) = A * B + A * C = 100 *100 + 10 *100 = 11000
Man sieht das Plan B günstiger ist als Plan A
7
Hat der Optimierer nun den günstigsten Plan gewählt, so übersetzt er in ASR. Diese
Übersetzung erhält der Codegenerator.
3. Code – Generator:
Der Code – Generator übersetzt den erhaltenen Plan in Maschinensprache. Dies macht er,
indem er für die einzelnen Operatoren des Plans in einer internen, vordefinierten Tabelle
den zugehörigen Maschinen – Code erfragt, und den Operator damit ersetzt.
4. Code – Ausführung:
Die Code – Ausführung ist der letzte Schritt in der Bearbeitung des SQL – Statements,
und liefert das gewünschte Ergebnis.
Das Kostenbasierte Optimierungsmodell ist ein häufig verwendetes Konzept. Es hat sich
schon früh gezeigt, das dieses Modell gute Ergebnisse erreicht und dabei effizient
arbeitet. Neben dem kostenbasierten Ansatz existieren allerdings noch weitere Ansätze.
Schon in System R wurden sogenannte Interesting Orders verwendet. Bei diesem Ansatz
werden Teilabschnitte von Plänen und deren Ergebnisse auf ihr Wiederverwertbarweit
getestet. Existieren z.B. in einem Plan zwei oder mehrere Abschnitte, welche gleiche
Ergebnis liefern, so bekommt dieser Plan eine gute Bewertung, da das Ergebnis für die
besagten Teilabschnitte nur einmal berechnet werden muss, und damit die Teilabschnitte
einfach ersetzt werden können.
8
3. Optimierungsmodelle für kontinuierliche Querys
Im vorherigen Kapitel haben wir Optimierungsverfahren kennen gelernt, wie sie in
traditionellen DBMS verwendet werden. Im Folgenden sollen Optimierungsmodelle für
DSMS gezeigt werden.
3.1 Problemstellung:
Gegeben ist ein DBMS, welches n Eingabeströme und m Ausgabeströme hat. An den
Eingabeströmen kommen Daten in unterschiedlicher Geschwindigkeit an. Dies können
z.B. Datenströme von entfernten Internet-Hosts sein, welche über verschieden schnelle
Anbindungen verfügen.
.
.
.
.
1
1
2
2
Data Stream
Management
System
n
.
.
.
.
m
Die Problemstellung, mit der sich die später gezeigten Optimierungsmodelle beschäftigen
ist: Wie kann der Optimierer eines Data Stream Management System einen Queryplan
wählen, der für die verschieden schnelle Eingabeströme und darauf ausgeführten
Operatoren einen möglichst hohen Datendurchsatz erzielt.
Das Prinzip eines Optimierers wurde im einführenden Kapitel schon erwähnt, soll hier
aber noch mal explizit aufgeführt werden:
Der Optimierer erhält eine Menge von Plänen als Eingabe. Jedem Plan im Suchraum des
Optimierers (Menge der Pläne, aus denen der Optimierer einen Plan selektieren soll) wird
anhand von geeigneten Daten Kosten zugeordnet. Der Plan mit den geringsten Kosten
wird zur Ausführung gebracht.
9
3.2 Beispiel 3:
Wir verwenden wieder die Query aus Beispiel 2. Nur das die Query diesmal nicht auf
einer Datenbank sondern auf Datenströmen ausgeführt wird.
Gegeben sind die Eingabeströme A, B und C. Die Datenraten von A,B und C betragen je
100 Tupel/Sekunde.
Auf diese Ströme soll die Query -Plans (A
B)
C und (A
C)
B ausgeführt
werden.
Plan A
|A
A
Plan B
B| = 5
B
A
|B| = 100
|A
A
|A
C| = 50
B
C
A
|C| = 100
|A| = 100
|A| = 100
A
|A
C
A
C| = 5
C
B| = 10
|C| = 100
C
B
|B| = 100
B
Welchen Plan soll der Optimierer nun wählen? Und nach welcher Strategie?
Theoretisch könnte man ein kardinalitätsbasiertes Optimierungsmodell von einem
herkömmlichen Data Base Management System verwenden. Man berechnet einfach die
Entstehenden Kardinalitäten von A
B und A
C und entscheidet anhand des
Ergebnisses welcher Join zuerst ausgeführt werden soll. Um die Kardinalitäten von
A
B und A
C zu berechnen, benötigt man den Trennschärfe – Faktor und vor
allem die Kardinalitäten der Ströme A, B und C. Der Trennschärfe – Faktor der Joins
könnte eventuell noch geschätzt werden. Aber was sind die Kardinalitäten der Ströme A,
B und C? Diese sind bei kontinuierlichen Querys meistens nicht bekannt, und theoretisch
nicht mal wohl definiert. (Da die Ströme theoretisch unendlich lang sein können.)
Aus diesem Grund ist das kardinalitätsbasierte Optimierungsmodell im Falle von DSMS
nicht praktikabel. Daher müssen neue Ansätze gefunden werden.
Ein weiterer Ansatzpunkt zur Optimierung stellen die verwendeten Operatoren dar. So
gibt es verschiedene Methoden einen Join auszuführen. Welche die effizienteste ist hängt
von der gegebenen Situation ab.
Deswegen sollen in dieser Ausarbeitung zwei Optimierungsansätze vorgestellt werden.
Die erste Optimierungsmethode (Optimierung von Moving Windows) zeigt
Möglichkeiten, wie man in bestimmten Situationen eine möglichst effiziente Join –
Methode wählt. Der Zweite Optimierungsansatz (rate –based Optimization) versucht eine
möglichst optimale Join – order zu wählen.
Die gezeigten Ansätze wurden von der University of Wisconsin-Madison für das
Niagara – Projekt [4] entwickelt.
10
4. Moving Window
4.1 Einführung:
Wenn man sich überlegt, wie man einen Join über zwei Datenströme ausführt, sieht man
schnell folgendes Problem: Wie schon erwähnt können Datenströme als theoretisch
unendlich lang angenommen werden. Um nun, wie bei Joins notwendig, auf
herkömmliche Art und Weise jedes Tupel des einen Stroms mit jedem Tupel des anderen
Stroms zu vergleichen, benötigt man unendlich viel Speicherplatz. Selbstverständlich ist
das nicht praktikabel.
Aus diesem Grund soll nun bevor wir uns mit der Optimierung von Queries auf
Datenströme geschäftigen, eine Technik gezeigt werden, welche die Verarbeitung von
Joins über unendlich langen Datenströmen ermöglicht:
Der Ansatz dabei ist, die Eingabeströme nicht in voller Länge zu betrachten, sondern
ausschnittsweiße.
4.2 Beispiel 4:
Gegeben sei einen Join mit den unendlich langen Eingabeströmen L und R welche die
Eingaberaten rl und rr haben. Man betrachtet nun nicht die Gesamteingaben sondern nur
die letzten tl Sekunden von L und die letzten tr Sekunden von R. Tl und Tr werden als
Zeitfenster der Ströme L und R bezeichnet. Es ist offensichtlich, dass sich der Inhalt der
Fenster mit der Zeit verändert sofern die Eingaberaten größer als Null sind. Man könnte
also sagen, die Fenster bewegten sich mit der Zeit über die Ströme. Daher wird die
Technik moving Window genannt.
Tl
Strom L
Tr
Strom R
Zeit t
Bildliche Darstellung von moving Windows
11
4.3 Optimierung von moving Windows (time – unit based optimization)
Ziel dieses Kapitels wird sein, eine Optimierungsmöglichkeit für moving Windows zu
finden. Es geht nicht darum, wie im vorherigen Kapitel, die Kosten eines gesamten Query
– Plans zu schätzen. Es werden hier nur die Kosten für verschiedene
Join – Methoden unter bestimmten Gegebenheiten geschätzt um die jeweilig beste
Methode zu bestimmen. Dabei betrachten wir hier nur die Join – Methoden Nested Loop
Join (NLJ) und Hash Join (HJ). Gegebenheiten definieren sich insbesondere durch das
Verhältnis Ankunftsrate rl zur Ankunftsrate rr. Gefragt wird also z.B: Im Strom A
kommen fünfmal soviel Tupel wie im Strom B an, welcher Join – Algorithmus ist der
effizienteste?
4.4 Funktionsweise von Nested Loop Joins:
Um die Funktionsweise von NLJ zu erklären, bedienen wir uns wieder des Beispiels 2,
bzw. speziell des Joins A.x = B.y.
Der NLJ Algorithmus macht nichts anderes als jeden Eintrag A.x aus A mit jedem Eintrag
von B.y aus B zu vergleichen. Das wir durch zwei for – Schleifen realisiert:
Algorithmus:
FOR EACH A.x FROM A {
FOR EACH B.y FROM B {
IF [A.x = B.y] {
Füge A.x zur Ausgabe hinzu;
}
}
}
Der Aufwand des NLJ – Algorithmus ist O( A * B ) .
Funktionsweise von Hash Joins:
Wir betrachten den gleichen Join wie bei NLJ.
Der HJ – Algorithmus erstellt zuerst eine Hash – Tabelle von A.x und vergleicht dann
jedes Element von B.y mit der Hash – Tabelle.
Algorithmus:
BAUE HASH TABELLE HASH(A.x) VON A.x
FOR EACH B.y FROM B {
IF [HASH(A.x) CONTAINS B.y] {
Füge A.x zur Ausgabe hinzu;
}
}
Der Aufwand des HJ – Algorithmus ist O( B ) . Allerdings ist zu beachten, dass das
erstellen der Hash – Tabelle auch Kosten verursacht. Eine Hash – Tabelle besteht aus hash
– buckets. Man kann buckets als Untergliederung der Tabelle verstehen. Z. B. werden
bekanntlich die Einträge in einem Telefonbuch anhand ihrer Anfangsbuchstaben in
Kapitel untergliedert. Die Kapitel in einem Telefonbuch entsprechen den hash – buckets in
einer Hash – Tabelle.
12
4.5 Kostenabschätzung für moving Window - Joins (ohne spezifische Join – Technik)
Um entscheiden zu können, welcher Join – Algorithmus für eine Situation am besten
geeignet ist, benötigen wir ein Kostenmodell, welches die Kosten von Join – Algorithmen
in Bezug auf Moving Windows innerhalb einer Zeiteinheit schätzt.
Betrachten wir die Zeitfenster Tl und Tr von Beispiel 4. Solange keine neuen Tupel in den
Zeitfenstern erscheinen, wenn also die Eingaberaten null sind, können auch keine neuen
Ausgabetupel entstehen. Folglich müssen auch keine Aktionen für den Join – Operator
ausgeführt werden. Daher können wir sagen, dass Aktionen genau dann ausgeführt werden
müssen, wenn in einem der Fenster neue Tupel eintreffen.
Nehmen wir an, es trifft ein neues Tupel am Fenster Tl ein (am Fenster deswegen, weil
wir das Einfügen des Tupels in das Fenster mit in unsere Kostenrechnung aufnehmen).
Nun müssen folgende Aktionen ausgeführt werden:
1. Das Fenster Tr muss auf Tupel untersucht werden, welche vom Join – Operator
affektiert werden. Die daraus resultierenden Kosten bezeichnen wir als
probe(Tr).
2. Einfügen des ankommenden Tupels in das Fenster Tl. Die Kosten werden als
insert(Tl) bezeichnet
3. Überprüfen des Fensters Tl auf Tupel, die nicht mehr im Zeitbereich des
Fensters liegen. Dies Kosten bezeichnen wir mit invalidate(Tl).
Analog gehen wir beim Fenster Tr vor. Es ist darauf zu achten, dass die Kosten probe,
insert und invalidate in dieser allgemeinen Darstellung noch nicht definiert sind, da sie
von dem verwendeten Join – Algorithmus abhängen.
Bislang haben wir die nur die Kosten für ein ankommendes Tupel betrachtet. Um die
Kosten des kontinuierlichen Joins während einer Zeiteinheit zu berechnen, müssen wir die
geschätzten mit Ankunftsraten pro Zeiteinheit rl und rr berücksichtigen.
Betrachtet man die Gesamtkosten C für den Join L
R so erhalten wir:
C = rl [ probe(Tr ) + insert (Tl ) + invalidate(Tl ) ] + rr [ probe(Tl ) + insert (Tr ) + invalidate(Tr )
]
(Formel 1)
Zur Wiederholung:
Der erste Teil der Formel beschreibt die Kosten welche durch ankommende Tupel am
Fenster Tl pro Zeiteinheit verursacht werden, der zweite Teil macht das gleiche für Fenster
Tr. Die Kosten für die Überprüfung (invalidate) fallen an, weil die Fenster auf Tupel
überprüft werden müssen, welche schon älter als die vorgegebene Zeitspanne tl bzw. tr
sind.
Man kann die Komponenten in Formel 1 aber auch umsortieren, dass man sieht welche
Kosten beim Zugriff auf Tupel im Fenster Tr und welche beim Zugriff auf Tupel im
Fenster Tl entstehen:
C = rl [ probe(Tr ) + insert (Tl ) + invalidate(Tl ) ] + rr [ probe(Tl ) + insert (Tr ) + invalidate(Tr ) ]
=rl * probe(Tr ) + rl * insert (Tl ) + rl * invalidate(Tl ) + rr * probe(Tl ) + rr * insert (Tr ) + rr * invalidate(Tr )
= rr * probe(Tl ) + rl * (insert (Tl ) + invalidate(Tl )) + rl ( probe(Tr ) + rr * (instert (Tr ) + invalidate(Tr ))
(Kosten für Zugriff auf Tupel in Tl)
(Kosten für Zugriff auf Tupel in Tr)
13
Wozu die Umformung?
Sinn und Zweck der Kostenanalyse ist es, Kosten für Joins hinreichend genau zu schätzen.
Am Ende der Analyse muss die Feststellung stehen, Join – Methode A ist um Faktor X
schneller als Join – Methode B. Dabei ist es nicht sinnvoll, die möglichst exakten Kosten
zu kalkulieren, da die Kalkulation an sich schon Kosten verursacht. Deswegen ist man
bestrebt, Kostenmodelle so einfach wie möglich zu halten. Aus diesem Grund berechnen
wir in den folgenden Abschätzungen für konkrete Join – Algorithmen nur Folgendes:
Kommt ein neues Tupel im Fenster Tl an, so muss es mit den Tupeln des Fensters Tr
„gejoint“ werden (Join Tl to Tr). Der Grossteil der Kosten fällt in diesem Fall beim
Zugriff auf die Tupel des Fensters Tr an. Deswegen werden auch nur die Kosten, welche
im Fenster Tr entstehen berechnet. Diese sind repräsentativ für die Gesamtkosten welche
beim Vorgang Join Tl to Tr entstehen. Analog gehen wir für Join Tr to Tl vor.
4.6 Kostenabschätzung für Nested Loop Joins
Im Folgenden sollen die Kosten für Join(NLJ) Tl to Tr für eine Zeiteinheit behandelt
werden. (Wir erinnern uns an die Zeiteinheiten tl und tr der Fenster Tl und Tr)
Bei der Nested Loop Join Technik, muss bei jedem an Tl ankommenden Tupel jedes
Tupel von Fenster Tr überprüft werden, ob es für den Join notwendig ist. Die Anzahl der
Tupel, welche im Fenster Tr enthalten sind ist das Produkt aus Ankunftsrate rr
(ankommende Tupel pro Zeiteinheit) und der Zeitspanne tr (Anzahl Zeiteinheiten) des
Fensters Tr. Also |Tr| = rr * tr. Die Anzahl ankommender Tupel pro Zeiteinheit am Fenster
Tl ist definitionsgemäß rl. Dementsprechend beträgt der Aufwand das Fenster Tr auf
benötigte Tupel zu überprüfen pro Zeiteinheit rl* | Tr |= rl * rr * tr .
Fehlen noch die Kosten für insert() und invalidate(). Die Kosten um ein Tupel in das
Fenster Tr einzufügen betragen 1. Da am Fenster Tr pro Zeiteinheit rr Tupel ankommen
betragen die Kosten rr.
Für die Kosten invalidate() können wir annehmen, dass im Durchschnitt pro
ankommenden Tupel auch Eins abläuft (veraltet). Deswegen betragen diese Kosten pro
ankommenden Tupel ebenfalls 1. Dementsprechend betragen die Kosten für eine
Zeiteinheit rr.
Aus den drei Kostenteilen probe(), insert() und invalidate() könnten nun die Gesamtkosten
für NLJ für eine Zeiteinheit berechnen. Was noch fehlt, sind die Kosten, welche anfallen
um auf ein Tupel zuzugreifen. Diese Kosten nennen wir Cn. (Bislang entsprechen unsere
Kosten der Anzahl der Zugriffe auf einzelne Tupel, welche bei der Verarbeitung des Joins
anfallen).
C ( NLJ ) =(rl* | Tr | + rr + rr ) * Cn = (rl * rr * tr + 2 * rr ) * Cn (Formel 2)
14
4.7 Kostenabschätzung für Hash Joins
Diese Kostenabschätzung verläuft analog zur Kostenabschätzung von NLJ.
1. Kosten für probe():
Wenn ein Tupel am Fenster Tl ankommt, so muss die hash – Tabelle von Fenster
Tr auf benötigte Tupel überprüft werden. Der Aufwand hierfür hängt von der
Anzahl hash – buckets ab. Diese Anzahl bezeichnen wir für die Tabelle von Tr als
|Br|. Bei guten hash – Algorithmen liegt die Anzahl der hash – buckets nahe der
Anzahl der in der Tabelle enthaltenen Tupel. Der Aufwand für einen Look-up in
der Tabelle ist die Anzahl enthaltener Tupel dividiert durch die Anzahl der hash –
buckets. Zur Wiederholung: Die Anzahl der Elemente im Fenster Tr ist rr*tr.
für einen Look – up. Die
Dementsprechend sind die Kosten rr * tr
| Br |
resultierenden Kosten für probe() während einer Zeiteinheit betragen folglich:
rl * rr * tr
| Br |
2. Analog können wir für die Kosten invalidate() bestimmen. Hier ist nur darauf zu
achten, dass der Vorgang pro Zeiteinheit nicht rl sondern rr mal ausgeführt wird.
(Da im Durchschnitt rr Tupel im Fenster veralten).
Daher sind die Kosten für invalidate: rr * rr * tr
| Br |
3. Die Kosten um ein Tupel in die hash- Tabelle von Tr einzutragen (insert())
betragen 1. Für die eine Zeiteinheit also rr.
Aus den drei Kostenteilen können wir, unter Verwendung des Faktors Ch, welcher
für die Kosten um auf ein Tupel innerhalb einer hash – Tabelle zuzugreifen
repräsentiert, wieder die Gesamtkosten herleiten:
C ( HJ ) = (rl *
rr * tr
rr * tr
+ rr * (
+ 1)) * Ch (Formel 3)
| Br |
| Br |
15
4.8 Beispiel 5:
Erinnern wir uns an Beispiel 3. Aus diesem Beispiel verwenden wir nun Plan B, und
versuchen mit den oben gelernten Formeln herauszufinden, welches jeweils der bessere
Join – Algorithmus für die beiden im Plan vorkommenden Joins ist.
Plan B
|A
A
|A
A
|A| = 100
A
C| = 5
C
B| = 10
|C| = 100
C
B
|B| = 100
B
Welche Daten sind Gegeben?
1. Die Eingaberaten:
ra = rb = rc=100 T/s
rab = 10 T/s (Ausgaberate von A
B)
2. Die Zeitfenstergrößen legen wir auf ta=tb=tc=2s fest.
3. Cn sei 0.5 und Ch sei 0.65. (Der Zugriff auf einzelne Tupel ist bei HJ teurer, diese
zwei Werte sind aber dennoch fiktiv)
4. Die bucket – size |B| der hash – Tabellen sei immer 10. Dementsprechend enthält
eine hash – Tabelle Anzahl − Tupel
buckets.
10
Betrachten wir Zuerst die Kosten von Join A to B:
NLJ – Algorithmus (Formel 2):
C ( A, b) = (ra * rb * tb + 2 * rb) * Cn = (100 * 100 * 2 + 2 * 100) * 0.5 = 10100
HJ – Algorithmus (Formel 3):
C ( A, B) = (ra *
rb * tb
100 * 2
rb * tb
100 * 2
+ rb * (
+ 1)) * Ch = (100 *
+ 100 * (
+ 1)) * 0.65 = 2665
B
|B|
10
10
In diesem Fall ist also der HJ – Algorithmus um Faktor 3.8 schneller.
16
Nun betrachten wir die Kosten Für Join A to C:
NLJ – Algorithmus:
C ( A, C ) = (ra * rc * tc + 2 * rc) * Cn = (10 *100 * 2 + 2 *100) * 0.5 = 1100
HJ – Algorithmus:
C ( A, B ) = (ra *
rc * tc
rc * tc
100 * 2
100 * 2
+ rc * (
+ 1)) * Ch = (10 *
+ 100 * (
+ 1)) * 0.65 = 1495
B
|B|
10
10
In diesem Fall ist der NLJ – Algorithmus günstiger.
17
5. Grundlagen der rate – based Optimierung
5.1 Ein Überblick
Im vorigen Kapitel haben Methoden kennen gelernt, um den besten Join – Algorithmus zu
wählen. In diesem Kapitel sollen Vorgehensweißen gezeigt werden, mit denen man die
beste Join – order für eine Situation schätzt.
Wie der Name rate – based Optimierung schon vermuten lässt, sind die Grundlagen dieses
Optimierungsmodells die Durchsatzraten der Eingabeströme.
Der Ansatz des Verfahrens ist, mit Hilfe der Eingaberaten von Joins ihre Ausgaberate zu
bestimmen. Mit Hilfe der Abschätzungen für die einzelnen Joins kann dann die
Ausgaberate des gesamten Query – Plans geschätzt werden. Der Plan mit der höchsten
Ausgaberate ist natürlich der Beste.
Es sei darauf hingewiesen, dass dieses Optimierungsmodell unabhängig von den vorher
gezeigten Moving Windows arbeitet.
5.2 Die Ausgaberate als Funktion von der Eingaberate
Ziel dieses Kapitels ist es, eine Funktion herzuleiten, welche als Eingabe die Raten der
Eingabeströme eines Joins erhält, und als Ausgabe die geschätzte Ausgaberate des Joins
liefert. Es ist zu beachten, dass wir hier Joins im Allgemeinen betrachten, und nicht
spezielle Join – Methoden wie wir sie im Kapitel Moving Windows kennen gelernt haben.
Da bei den Berechnungen eine Reihe von Kostenvariablen verwendet wird sind diese
Variablen mit Bedeutung hier aufgelistet:
Kosten
Variable
Cl
Cr
T
rr
ro
Bedeutung
Kosten für die Bearbeitung der linken Eingabe für einen Join
Kosten für die Bearbeitung der rechten Eingabe für einen Join
Kosten um eine einzelne Übertragung zu machen
Rechte Eingaberate (bei Joins)
Ausgabe Rate
18
Fragestellung:
Zu irgendeinem Zeitpunkt tx in der Join – Ausführung können Eingaben aus dem linken
bzw. rechten Eingabestrom ankommen.
Die Frage ist: Was ist die Rate der Ausgabe, welch durch die zum Zeitpunkt tx
ankommenden Tupel generiert wird? Folgende Grafik soll die Fragestellung bildlich
darstellen:
t0
Eingaben zwischen t0 und tx
|Zeitspanne zwischen t0 und tx | = tx
tx
Zeit t
Durch Eingabe zum
Zeitpunkt tx
verursachte Ausgaben
Vorgehensweiße:
Um die Beantwortung dieser Frage zu vereinfachen, gehen wir vorerst nicht von einer
kontinuierlichen Zeit aus, sondern betrachten zuerst diskrete Zeiteinheiten. Im Folgenden
gehen wir in vier Schritten vor:
1. Berechnung der generierten Ausgabetupel für eine Eingabe zum Zeitpunkt tx
innerhalb einer Zeiteinheit
2. Folgerung für mehrere Zeiteinheiten
3. Übergang von diskreten Zeiteinheiten zur kontinuierlichen Zeit
4. Berechnung der Ausgaberate ro
Berechnung der Ausgaberate für eine Zeiteinheit:
Zuerst überlegen wir uns, wie viele Ausgabe – Tupel durch die im Zeitpunkt tx
ankommenden Tupel, während einer Zeiteinheit generiert werden.
Wie in der oben stehenden Tabelle aufgeführt nehmen wir für den linken Eingabestrom
eine Rate rl und für den Rechten eine Rate rr an.
In der Zeitspanne zwischen t0 und tx kommen tx*rl Tupel aus dem linken Eingabestrom,
und tx*rr aus dem Rechten in den Join. Wir wollen nun wissen, wie viele Ausgabetupel
diese Eingabe produziert.
Würden wir hier kartesische Produkte betrachten (in denen jedes Tupel vom linken Strom
mit jedem Tupel vom Rechten Strom eine Ausgabe generiert), so wäre die Anzahl
generierter Tupels während einer Zeiteinheit:
# Tupel (kartesisches Pr odukt ) = rl * tx * rr * tx = rl * rr * tx ²
19
Wie viele Ausgaben bei einem Join produziert werden hängt vom Trennschärfe – Faktor f
des verwendeten Operators (in Bezug auf die Eingabeströme) ab. Deswegen müssen wir
diesen Faktor f mit in unsere Berechnung einfließen lassen, und erhalten für die Anzahl
generierter Tupel:
# Tupel ( Eine − Zeiteinheit ) = f * rl * tx * rr * tx = f * rl * rr * tx ²
(Formel 4)
5.3 Beispiel 6:
In diesem Beispiel verwenden wir den Join A
B aus Beispiel 3. Die Eingaberaten ra
und rb betragen jeweils 100 Tupel/Sekunde. Der Trennschärfe – Faktor fab beträgt 0.1.
Den Zeitpunkt tx sei tx=3s: Die Anzahl der generierten Tupel beträgt:
# Tupel = f * rl * rr * tx ² = 0.1 *100 *100 * 9 = 9000
Der Einfachheit wegen nehmen wir an, das der Begin unserer Zeiteinheit nicht zum
Zeitpunkt tx=3 ist, sondern zum Zeitpunkt tx=t0. Das heißt, dass vor dem Zeitpunkt tx
noch keine Tupel in den Join eingeflossen sind. Wir betrachten also die Anzahl der Tupel
welche in der ersten Zeiteinheit der Join – Ausführung generiert werden:
# Tupel (1steZeiteinheit ) = f * rl * rr = 0.1 *100 *100 = 1000
Nun betrachten wir die zweite Zeiteinheit nach Begin der Join - Ausführung:
Am Ende der zweiten Zeiteinheit ist der Beitrag des linken Eingabestroms zur
Gesamtausgabe des Joins: f * rl * 2 * rr . (Der Trennschärfe – Faktor multipliziert mit den
Tupeln welche während der zweiten Zeiteinheit vom linken Strom gelesen wurden (rl)
multipliziert mit der Anzahl Tupel welche vom rechten Strom währen der ersten und der
zweiten Zeiteinheit gelesen wurden (2*rr)).
Den Beitrag vom rechten Eingabestrom berechnen wir analog: f * rr * 2 * rl .
Folglich ist die Gesamtausgabe, die durch die Eingaben vom linken bzw. rechten
Eingabestrom während der zweiten Zeiteinheit generiert wurde:
# Tupel (2te − Zeiteinheiten) = f * rl * 2 * rr + f * rr * 2 * rl − f * rr * rl = 2 * (2 * f * rr * rl ) − f * rr * rl
(Die Subtraktion verhindert das einige Tupel zweimal mitgerechnet werden) (Formel 5)
20
Wenn man von Diskreten Zeiteinheiten zur kontinuierlichen Zeit übergeht, so muss man,
um die Anzahl der generierten Ausgaben zu berechen folgendes Integral lösen:
tx
tx
t0
t0
# Tupel (kontinuirlicheZeit ) = ∫ (2 * (t * f * rl * rr ) − f * rl * rr )dt = f * rl * rr * ∫ (2 * t − 1)dt
mit t0 = 0 folgt:
tx
# Tupel (kontinuirlicheZeit ) = f * rl * rr * ∫ (2 * t − 1)dt = f * rl * rr * tx * (tx − 1)
(Formel 6)
0
Berechnung der Ausgaberate:
Bislang haben wir nur die durch Eingaben zum Zeitpunkt tx generierte Anzahl von
Ausgaben betrachtet. Als letzen Schritt soll nun noch die Ausgaberate ro berechnet
werden. Dies gestaltet sich einfach: Die Ausgaberate ist gleich der Anzahl der
ausgegebenen Tupel dividiert durch die dazu benötigte Zeit. Für die Anzahl der
ausgegebnen Tupel verwenden wir Formel 6.
Die Kosten um ein im linken Strom ankommendes Tupel zu verarbeiten betragen laut
Tabelle Cl. Die Kosten um ein Tupel aus dem Rechten Strom zu verarbeiten sind analog
Cr. Wie schon bekannt erhalten wir aus dem linken Strom tx* rl Eingabetupel und aus
dem Rechten tx*rr. Es ist zu berücksichtigen, dass die Kosten nicht konstant sind. Je mehr
Tupel bereits in den Join eingeflossen sind, desto aufwendiger wird es auch, neue Tupel
zu verarbeiten. (Da z.B. bei Nested Loop Joins jedes in einem Strom ankommende Tupel
mit jedem bereits vorhandenen Tupel der anderen Seite verglichen werden muss.). Für die
Kosten von Cl und Cr sei auf das vorige Kapitel verwiesen.
Die Gesamtkosten für beide Eingaben belaufen sich folglich auf
Kosten = Cl * tx * rl + Cr * tx * rr
(Formel 7)
Daraus resultiert die Ausgaberate ro:
ro =
Formel 6
f * rl * rr * tx * (tx − 1)
f * rl * rr * (tx − 1)
f * rl * rr * tx
≈
=
=
Formel 7 Cl * tx * rl + Cr * tx * rr
Cl * rl + Cr * rr
Cl * rl + Cr * rr
(Formel 8)
Mit Formel 8 können wir nun einen generellen Rahmen für die rate – based Optimierung
erstellen.
21
5.4 Genereller Rahmen für die rate-based Optimierung
Im vorherigen Kapitel wurde gezeigt, wie man die Ausgaberate eines Joins als eine
Funktion der Eingaberaten darstellen kann. In diesem Kapitel soll gezeigt werden, wie mit
Hilfe dieser Funktionen ein Plan bezüglich seiner Kosten evaluiert werden kann.
Betrachten wir dazu wieder Plan B aus Beispiel 3:
|A
A
|A
A
|A| = 100
A
C| = 5
C
B| = 10
|C| = 100
C
B
|B| = 100
B
Wir stellen fest, dass der Ausgabestrom von A
B als linker Eingabestrom von
A
C fungiert. Damit ist die linke Eingaberate des Joins A
C gleich der
Ausgaberate von A
B. So kombiniert ließe sich die Gesamtausgaberate von Plan B
als Funktion der Eingaberaten der Ströme A,B und C darstellen. Hat man einmal diese
Funktion (nennen wir sie ro(Plan B)) könnte man daraus wieder die Anzahl Ausgaben in
einem Zeitintervall berechnen. Der Plan mit den meisten Ausgaben in einem bestimmten
Zeitintervall ist für dieses auch der beste Plan.
Die Funktion für die von einem Plan ausgegebenen Tupel ist:
ty
# Gesammtausgabe( PlanB ) = ∫ ro( PlanB)dt (Formel 9)
0
Diese Funktion stellt den generellen Rahmen für die ratenbasierte Optimierung dar.
22
5.5 Beispiel 7:
In diesem Beispiel soll nun endlich die Frage, welche schon zu Anfang aufgeworfen
wurde, beantwortet werden: Welcher der beiden Pläne aus Beispiel 3 ist besser?
(Man beachte, dass hier noch die maximalen Ausgaberaten angegeben sind. Sobald den
Joins Kosten zugeordnet werden, sinken die Ausgaberaten unter das jeweilige Maximum!
Wir können also den unten stehenden Graphen nur die Eingaberaten von A,B und C
entnehmen.)
Plan A
|A
A
Plan B
B| = 5
B
A
|B| = 100
|A
A
|A
C| = 50
B
C
A
|C| = 100
|A| = 100
|A| = 100
A
|A
C
A
C| = 5
C
B| = 10
|C| = 100
C
B
|B| = 100
B
Die Zeitvariable tx setzen wir auf tx=1 (Sekunden). Für die Kosten Cl und Cr nehmen wir
der Einfachheit halber die konstanten Werte Cl = 0.8 und Cr=0.7 an.
Für die folgenden Berechnungen wird die Formel 8 verwendet:
Plan A:
ro( AJoinC ) =
f * rl * rr * tx
0.5 *100 *100 *1
=
≈ 33
Cl * rl + Cr * rr 0.8 *100 + 0.7 *100
Ausgaberate( PlanA) = ro( AJoinB) =
f * ro( AJoinC ) * rr * tx
0.1 * 33 *100 *1
=
≈ 3,4
Cl * ro( AJoinC ) + Cr * rr 0.8 * 33 + 0.7 *100
Plan B:
ro( AJoinB) =
f * rl * rr * tx
0.1 *100 *100 *1
=
≈ 6,6
Cl * rl + Cr * rr 0.8 *100 + 0.7 *100
Ausgaberate( PlanB) = ro( AJoinC ) =
f * ro( AJoinB) * rr * tx
0.5 * 6,6 *100 *1
=
≈ 4,3
Cl * ro( AJoinB) + Cr * rr 0.8 * 6,6 + 0.7 *100
23
Es ist ersichtlich, dass Plan B für die gegebenen Werte die bessere Lösung wäre. Da wir
für tx = 1 angenommen haben, entsprechen die Ausgaberaten der Anzahl generierter
Tupel. Die Anwendung von Formel 9 ist hier also unnötig.
Achtung:
Es sei nochmals darauf hingewiesen, dass die Kosten Cl und Cr hier nur der Einfachheit
halber auf konstante Werte gesetzt wurden. Je mehr Tupel in einen Join eingeflossen sind,
desto höher sind die Kosten um weitere Tupel zu verarbeiten. Aus diesem Grund wird
auch der Zeitfaktor im Zähler der Formel 8 kompensiert. Wäre dies nicht der Fall, blieben
die Kosten also wirklich konstant, so würde laut Formel 8 mit steigender Zeitkomponente
tx auch die Ausgaberaten steigen, und die Formel wäre zweifelsohne falsch.
Wollten wir z.B. Plan A für den Zeitpunkt tx = 2 berechnen, so müssten wir für die Kosten
Cl=1,6 und Cr=1,4 annehmen. (Vorausgesetzt die Kosten steigen linear).
ro( AJoinC ) =
f * rl * rr * tx
0.5 *100 * 100 * 2
=
≈ 33
Cl * rl + Cr * rr 1.6 *100 + 1.4 * 100
Ausgaberate( PlanA) = ro( AJoinB) =
f * ro( AJoinC ) * rr * tx
0.1 * 33 * 100 * 2
=
≈ 3,4
Cl * ro( AJoinC ) + Cr * rr 1.6 * 33 + 1.4 *100
Die Anzahl generierter Tupel kann hier mit Formel 9 berechnet werden:
(Hier benötigen wir die Mittelwerte der Kosten Cl und Cr im Intervall [0,2]. Diese
Mittelwert entsprechen der Kosten zum Zeitpunkt tx=1. Also Cl=0,8 und Cr=0,7)
2
1 f * ro( AJoinC ) * rr * tx ²
f * ro( AJoinC ) * rr * tx
= *
...[0,2]
Cl * ro( AJoinC ) + Cr * rr 2 Cl * ro( AJoinC ) + Cr * rr
0
# Ausgabe = ∫
=
1 0.1 * 33 *100 * 4
*
− 0 ≈ 6,8
2 0.8 * 33 + 0.7 *100
24
6. Fazit
Die Anforderungen an Data Stream Management Systeme werden in naher Zukunft
vermutlich stark ansteigen. Daher werden gute Optimierer unabdingbar sein. Das
Forschungsgebiet „Optimierer für DSMS“ ist noch sehr jung. Die hier vorgestellten
Konzepte befinden sich momentan in der Erprobungsfase. Es ist davon auszugehen, das
zukünftig mächtigere und deutlich komplexere Optimierungsverfahren als die hier
vorgestellten zur Anwendung kommen.
25
7. Anhang
Quellenangaben:
[1] Access path selection in an Relational Database Management System
( P. Griffits Selinger )
Zusätzlich http://www.cs.pdx.edu/~kgb/t/t.shtml
[2] Evaluating Window Joins over Unbounded Streams
(Jeffrey F. Naughton)
[3] Rate-Based Query Optimization for Streaming Information Sources
ACM SIGMOD'2002, June 4-6, Madison, Wisconsin, USA
[4] Niagara Query Engine
http://www.cs.wisc.edu/niagara/
26

Documents pareils