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