Bulkloading Indexes

Transcription

Bulkloading Indexes
Bulkloading Indexes
Algorithmen für Datenbanksysteme
Motivation
• Grosses Interesse an “Spatial”-, Bilder- und Text-Datenbanksystemen
• Verwaltung von mehr-dimensionalen Objekten => mehrdimensionale Indexstrukturen sind notwendig um NN-Suche oder
Window-Abfragen auszuführen.
• Bulk-Operationen immer wichtiger, Bulkloading zum Aufbauen von
mehr-dimensionalen Indexstrukturen
Das I/O Modell
• Standard Disk-Modell (nach Aggarwal und Vitter) zur
Komplexitätsanalyse
• Disk aufgeteilt in mehrere Blöcke mit fixer Grösse B.
• Pro Diskzugriff transfer von einem Datenblock in den Speicher
dies entspricht einem I/O.
• Performance wird an der Anzahl I/Os gemessen.
Das I/O Modell - Fortsetzung
• Parameter des Modells.
N: Anzahl Datenobjekte, die eingefügt werden
M: Anzahl Datenobjekte, die in den Speicher passen
B: Anzahl Datenobjekte pro Datenblock
• Wichtige Werte für die Analysen
N/B =: n Anzahl Diskblöcke, die eingefügt werden
M/B =: m Anzahl Diskblöcke, die in den Speicher passen
Bulkloading Methoden
• Mehrere Vorschläge für Bulkloading von R-Bäumen, die alle auf
dieselbe Weise arbeiten.
• Arbeitsweise
- Sortierung aller Daten (1-dimensionales Kriterium)
- Bottom-Up Erstellung des R-Baums, durch Clustern der Daten
- Erstellung von jeweils einer Ebene des R-Baums
• Komplexität Ο ( n log m n ) I/Os, dies entspricht der unteren Grenze vom
externen Sortieren.
• Problem: Im Normalfall (bei hohen Dimensionen) schlechte AbfragePerformance.
Komplexitätsanalyse - Externes Sortieren
• Lässt sich mittels einem multiway Mergesort erklären.
• Einladen von M Datenobjekten in den Speicher -> Sortieren
• Anschliessend alle N/M = n/m Listen mergen.
• Totale I/O kosten
Ο ( n + n log m −1 n / m ) = Ο ( n + n log m n / m )
= Ο ( n + n log m n / m ) = Ο ( n + n log m n − n log m m )
= Ο ( n + n log m n − n ) = Ο ( n log m n )
Ziele des Bulkloading
• Folgende Ziele werden für eine neue Bulkloading Methode gesetzt.
• Erreichung der unteren Grenze, die durch das externe Sortieren
gegeben ist.
• Keine Beeinflussung der Abfrage-Performance
• Generische Methode für verschiedene baumbasierte
Indexstrukturen.
Baumbasierte Indexstrukturen
• Knoten entsprechen einem Diskblock
• Index-Knoten: Enthalten Routing-Informationen und entsprechen
einem Teilgebiet des d-dimensionalen Baums.
• Daten-Knoten: Enthalten die Datenbojekte
• Indexstrukturen unterstützen folgende Funktionen.
InsertIntoNode: Datenobjekt oder Knoten einfügen
Split: Teilt Knoten mit zu vielen Kindern
ChooseSubTree: Unterbaum für Datenobjekt oder Gebiet
Buffer-Baum: Idee
• Zur Entwicklung von I/O effizienten Algorithmen
• Ausnutzung des grossen Hauptspeichers
• Einfügen in R-Baum: Jeder Knoten B Unterbäume (1 I/O zum Laden)
für jedes Rechteck => Ineffizient.
• Buffer-Baum Idee: Buffern von Operationen im Knoten, damit die
I/Os verteilt werden. Anzahl Unterbäume maximal =>
Speichernutzung.
Buffer-Baum: Idee
• Knotentypen im Buffer-Baum
Interne-Knoten
IndexKnoten
Leaf-Knoten
Daten-Knoten
Buffer
• Jeder Index-Knoten besitzt Buffer.
Routing Tabelle
• Parameter:
C: Maximale Einträge in Index-Knoten => Unterbäume
p: Maximale Anzahl belegter Blöcke im Buffer (p belegt => Overflow)
Buffer-Baum: Einfügen
• Datenobjekt in den Buffer einfügen. Prozess => blockiert
• Buffer Overflow => Leeren des Buffers => Raktivieren der Prozesse
• Es werden die Routing-Informationen nur einmal für mehrere
Datenobjekte geladen.
• Datenobjekt in Daten-Knoten. Prozess => terminiert.
Bulkloading mit Hilfe eines Buffer-Baums
• Es werden alle Datenobjekte in den Buffer-Baum eingefügt.
• Erzeugung der Daten-Knoten
• Normalfall entsprechen die Index-Knoten des Buffer-Baums nicht
denen der Indexstruktur. (Grösse der Knoten nicht korrekt)
• Erstellung der Index-Knoten mittels einem rekursiven Verfahren.
Beispiel Bulkloading
• Bulkloading eines R-Baums mittels einem Buffer-Baum.
• Im Beispiel sind B=3, C=4, p=2
r19
r22
r20
r23
r21
I1
e6
• Datenmenge bestehend aus 25
Rechtecken {r1, r2, ... ,r25}.
e7
r17
r13
r16
r14
r18
r15
• 23 Einfüge-Operationen bereits
ausgeführt.
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
Einfügen von r24
r19
r22
r20
r23
r21
I1
e6
e7
r17
r13
r16
r14
r18
r15
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
Einfügen von r24
r19
r22
r20
r23
r21
r24
I1
e6
e7
r17
r13
r16
r14
r18
r15
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
Einfügen von r25
r19
r22
r20
r23
r21
r24
I1
e6
e7
r17
r13
r16
r14
r18
r15
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
Einfügen von r25 - Leerung Root-Buffer
I1
e6
e7
r17
r22
r13
r16
r19
r23
r14
r18
r15
r21
r20
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
r24
Einfügen von r25 - Leerung Root-Buffer
I1
e6
e7
r17
r22
r13
r16
r19
r23
r14
r18
r15
r21
r20
I2
I3
e1
e2
e3
e4
e5
r1
r3
r5
r7
r9
r2
r4
r6
r8
r10
r11
D1
D2
D3
r12
D4
D5
r24
Overflow in I3
• Auflösung durch Leeren des Buffers von I3
• Rechtecke {r13, ... , r16, r18, r21, r24} verschieben sich hinunter auf die
Daten-Ebene
• Es kann auch ein Überlauf in einem Daten-Knoten auftreten. Diese
werden wie im R-Baum gehandhabt (mittels Split Fkt.)
• Der Daten-Knoten wird aufgeteilt
• Neue Routing-Information wird zum Vater propagiert. (Kann
wiederum einen Split auslösen)
Knoten I3 nach Teilleerung des Buffers
r24
r21
I3
e4
e5
e8
e9
r7
r10
r9
r14
r8
r13
r12
r16
r18
D4
r15
D5
D6
D7
Knoten I3 nach Teilleerung des Buffers
r24
r21
I3
e4
e5
e8
e9
r7
r10
r9
r14
r8
r13
r12
r16
r18
D4
r15
D5
D6
D7
Knoten I3 nach Teilleerung des Buffers
r24
r21
I3
e4
Splitten von D4 veranlasst
einen neuen Routing-Eintrag e10,
der in I3 eingefügt werden muss.
e5
e8
e9
r7
r10
r9
r14
r8
r13
r12
r16
r18
D4
r15
D5
D6
D7
Splitten von I3
• E10 kann nicht in I3 vermerkt werden, es muss ein Split ausgeführt
werden => I4
• Die Buffer-Einträge werden neuverteilt auf I3 und I4.
Der Grössere der beiden Buffer geleert.
Splitten von I3
• E10 kann nicht in I3 vermerkt werden, es muss ein Split ausgeführt
werden => I4
• Die Buffer-Einträge werden neuverteilt auf I3 und I4.
Der Grössere der beiden Buffer geleert.
r24
I3
I4
e4
e5
e10
e8
e9
r8
r10
r7
r9
r14
r18
r13
r21
r12
r16
r15
D4
D5
D8
D6
D7
Fertigstellung des Einfügen von r24
• Ein neuer Routing-Eintrag e12 für I4 wird zum Vater (hier I1)
propagiert.
• Eintrag e12 wird in I1 vermerkt. Anschliessend ist das Einfügen von
r24 beendet und r25 kann eingefügt werden.
• Mittels Tiefensuche alle nicht leeren Buffer leeren.
Buffer-Baum nach Leerung aller Buffer
I1
I2
e6
e7
e12
I3
e1
e2
e3
I4
e13
e4
e5
e10
e8
e9
e11
r1
r3
r5
r6
r8
r10
r7
r9
r14
r12
r2
r4
r11
r20
r18
r13
r21
r24
r16
r15
r19
r17
r22
r23
D1
D2
D3
D10
r25
D4
D5
D8
D6
D7
D9
Algorithmus im Detail
• Generischer Algorithmus, Vorraussetzungen an die Indexstruktur:
InsertIntoNode, Split, ChooseSubTree
• Einfache Methode: Buffer-Baum mittels Multi-Threading zu
implementieren => grosser Management-Overhead
• Idee ähnlich zur normalen Einfüge-Operation.
Kein nach unten Traversieren für jedes Einfügen, sondern
gesammelt.
Einfügen führt zu Restrukturierungs-Operationen.
• Restrukturierungs ebenfalls sammeln.
Bulkloading Methode
function BulkLoading(Root, Record){
if (Root.BufferLoad = B*p) {
new_childs = ClearBuffer(Root);
new_siblings = InsertChilds(Root, new_childs);
if (!new_siblings.isEmtpy())
}
InsertIntoBuffer(Root, R);
create a new root from new_siblings;
}
• ClearBuffer: Liste von neuen Kindern zum Einfügen
• InsertChilds: Liste von neuen Siblings
InsertChilds Methode - 1
function InsertChilds(Node, new_childs){
new_siblings = {};
foreach entry E in new_childs {
all_sibs = new_siblings
Parent = ChooseSubTree(all_sibs, E);
InsertIntoNode(Parent, E);
if (Parent.HasOverflow()) {
Split(Parent);
insert new entry into new_siblings;
}
}
...
{entry of Node};
• Jedes Kind in den aktuellen Knoten einfügen
• Bei Overflows Splitten => neue Siblings
InsertChilds Methode - 2
...
if (!new_siblings.isEmtpy()) {
foreach record R in Node.Buffer() {
Target = ChooseSubTree(all_sibs, R);
move R from Node.Buffer() to Target.Buffer();
}
}
return new_siblings;
}
• Bei neuen Siblings => aufteilen des Buffers
• Neue Siblings zurückgeben
ClearBuffer
function ClearBuffer(Node){
if (Node.isLeaf)
else
return ClearLeafBuffer(Node);
return ClearIndexBuffer(Node);
}
• Beim Leeren des Buffers muss unterschieden
• Interne Index-Knoten
• Leaf-Knoten
ClearIndexBuffer
function ClearIndexBuffer(Node){
overflow_list = {};
foreach R in first B*p records in Node.Buffer() {
Child = ChooseSubTree(Node,R);
InsertIntoBuffer(Child,R);
if (Child.BufferLoad > B*p)
}
new_childs = {};
foreach Child in overflow_list
return InsertChilds(Node, new_childs);
insert Child into overflow_list;
add ClearBuffer(Child) into new_childs;
}
• Leert den Buffer eines internen Index-Knotens
• Ersten B*p Einträge Leeren => Buffer maximal 2*B*p Einträge
ClearLeafBuffer - 1
function ClearLeafBuffer(Node){
foreach record R in Node.Buffer() {
DataNode = ChooseSubTree(Node,R);
InsertIntoNode(DataNode,R);
if (DataNode.HasOverflow()) {
Split(DataNode);
insert new entry into Node;
if (Node.HasOverflow()) {
SplitWithBuffer(Node);
new_siblings = {};
insert new entry into new_siblings;
...
• Jeden Buffer-Eintrag in einen Daten-Knoten schreiben.
• Eventuelles Splitten von Daten-Knoten.
ClearLeafBuffer - 2
...
//N is the new node
if (N.BufferLoad > Node.BufferLoad){
}else{
}
return new_siblings;
}
}
}
return {};
add ClearLearBuffer(N) to new_siblings;
add ClearLearBuffer(Node) to new_siblings;
}
• Grösseren Buffer leeren und Siblings zurückgeben.
Abschliessende Aufgaben
• Tiefensuche => Leerung aller nicht leeren Buffer
• Mittels der ClearBuffer und InsertChilds Methode.
• Wie erwähnt, entsprechen die Index-Knoten des Buffer-Baums nicht
denen der gewünschten Indexstruktur.
• Verschiedene Grössen der Index-Knoten.
• Buffer-Baum: maximal C Kinder
R-Baum: maximal B Kinder
Erstellung der Index-Knoten
• Ebene für Ebene rekursiv erstellen.
• Routing-Einträge der Daten-Knoten in neuen Buffer-Baum einfügen.
• Solange bis Buffer-Baum nur noch ein Daten-Knoten besitzt.
• Resultat => gewünschte Index-Struktur
• Abfrage-Performance?
Komplexitätsanalyse - 1
• Bulkloading eines R-Baums => Ziel Ο ( n log m n ) I/Os
• Akurater Wert für C, damit die Kosten fürs Leeren eines Buffers klein
sind.
⎡C ⎤
⎢⎢ B ⎥⎥ + C + 1 ≤ m
C ≈ Ο (m)
• Annahme: p=1/2*C
• Theorem 1: I/O Kosten zum Einfügen von N Rechtecken in einen RBuffer-Baum sind Ο ( n log m n )
Komplexitätsanalyse - Beweis Theorem 1
• Kosten Leerung eines Buffers mit maximal 2*B*p = B*C Datenobjekte
• Laden der gesamten Routing-Informationen Ο ( m ) I/Os
• Laden aller Datenobjekte Ο ( m ) I/Os
• Amortisiert jedes Datenobjekt bezahlt Ο (1 / B ) I/Os
• Total: Ο ( N / B * log m n ) = Ο ( n log m n )
• Zu beweisen: Buffer-Leerung immer Ο ( m ) I/Os
Splittoperationen Ο ( n ) I/Os
Komplexitätsanalyse - Theorem 2
• Theorem 2: I/O Kosten für das Leeren aller Buffer Ο ( n )
• Totale Anzahl Buffer: Ο ( n / m )
• Leeren eines Buffers Ο ( m ) I/Os
• Splittoperationen Ο ( n ) I/Os
• => Total: Ο ( n ) I/Os
Komplexitätsanalyse - 2
• Totale Kosten fürs Bulkloading
h
⎛
N
N
N
N⎞
⎛
⎞
∑ Ο ⎜⎝ Bi log m Bi ⎟⎠ ≤ Ο ⎜⎝ ∑ Bi log m Bi ⎟⎠
i=0
0 ≤i
h
⎛
⎛ h N
⎞
⎛
N ⎞⎞
= Ο ⎜ ∑ i log m n ⎟ = Ο ⎜ n log m n ⎜ ∑ i ⎟ ⎟ =
⎝ 0 ≤i B
⎠
⎝ 0 ≤1 B ⎠ ⎠
⎝
Ο ( n log m n )
• Optimalität
•Untere Grenze des externen Sortierens wird erreicht.
•1-dimensionaler R-Buffer-Baum kann fürs Sortieren verwendet
werden
Fazit
• Markant schneller als Eintrag für Eintrag einzufügen.
• Aufgezeigte Methode
• Generisch
• Optimal in den I/O Kosten
• Abfrage-Performance
Noch Fragen...
Danke für die Aufmerksamkeit

Documents pareils