Products 31 Daniel Rey, OPITZ CONSULTING Schweiz GmbH Oracle Exadata Storage Server Performance erklärt SmartScan Im Herbst 2008 präsentierte Oracle an der OpenWorld den Exadata Storage Server und die Oracle Database Machine. Es handelte sich dabei um eine, in Partnerschaft mit HP entwickelte, Appliance. Für die Entwicklung dieser Appliance hat Oracle einige der besten Oracle Spezialisten damit beauftragt ein perfekt ausbalanciertes System aufzubauen. Oracle behauptet damit bis zu hundertfache Performance zu erreichen, wie soll das aber möglich sein? Wenn wir die Hardware betrachten, so können wir einige Schmankerl entdecken. Mindestens drei Storage Server mit je zwölf Disks und zwei Quad Cores sind über InfiniBand mit mindestens zwei Datenbankservern verbunden, auf welchen ein Oracle RAC betrieben wird. Aber lässt sich damit wirklich eine solche Steigerung in der Performance erreichen? Wohl kaum, das kann die Konkurrenz schliesslich auch. Wo liegt denn nun das Geheimnis? Die zwei Quad Cores, welche in jedem einzelnen Storage Server stecken, werden für höhere Zwecke als für reines Auslesen und Transferieren von Blöcken verwendet. Anstatt dessen führen die Storage Cell getauften Storage Server sogenannte SmartScans durch. SmartScan Heutige Software Produkte sind meist über mehrere Layer verteilt, die klassische 3-Layer Architektur mit Datenbank, Middle-Tier und Client gilt inzwischen schon als relativ einfach. Diese Layer haben aber auch ihre Nachteile, mit jedem höheren Layer vermindert sich die Performance. Den meisten Leuten ist bewusst, dass es sinnvoller ist eine WHERE- Clause auf der Datenbank zu verwenden, als alle Daten ins Middle-Tier zu laden, dort zu Filtern und dann einen grossen Teil zu verwerfen. Mit konventionellem Storage muss die Oracle Datenbank aber gegenüber dem Storage genau dieses Konzept anwenden. Dies hatte mit lokalem Storage noch seine Berechtigung, dieser ist inzwischen aber, aus verschiedensten Gründen, nur noch selten anzutreffen. Um die Performance zu verbessern, hat sich Oracle Gedanken gemacht, wie sie die Daten schon auf dem Storage einschränken könnten, sodass sie nicht erst im Datenbank Layer einen grossen Teil der gelesenen Blöcke wieder verwerfen müssen. Um aber schon auf Storage Ebene die korrekten Blöcke auszuwählen, müssen diese Oracle Blöcke gelesen und ausgewertet Dafür musste der Storage intelligenter gemacht Der Oracle eigene Storage Server war geboren. Oracle hat dabei alle Features, welche von dieser Intelligenz profitieren, unter dem Begriff SmartScan zusammengefasst. Mit Hilfe von Exadata kann Oracle folgende Operationen einer SQL Abfrage auf den Storage auslagern: Filtern von Prädikaten Filtern von Prädikaten mittels einfachen Funktionen Filten der selektierten Spalten einfache Joins mehrerer Tabellen Diese Features können die Performance so stark beeinflussen, dass teilweise sogar der von Oracle versprochene hundertfache Geschwindigkeitsanstieg erreicht werden kann. SmartScan: Filtern von Prädikaten Eine der effizientesten Möglichkeiten die Datenmenge zu verringern, ist das Filtern mittels Prädikatenlogik. Angenommen wir haben eine weltweite Kundendatenbank eines internationalen Unternehmens und möchten uns nun nur die Daten unserer Schweizer Kunden anzeigen lassen. Mit einer einfachen WHERE Bedingung können wir die Datenmenge drastisch einschränken. Wenn der Storage Server für uns diese Einschränkung vornimmt, so haben wir mehrere Vorteile: der DB-Server benötigt weniger Ressourcen, um die Daten zu lesen das Netzwerk zwischen Storage und DB-Server wird weniger stark belastet der DB-Server benötigt keine Ressourcen, um die Daten zu filtern die kleine Datenmenge lässt sich in kürzerer Zeit vom Storage auf den DB-Server übertragen das Resultat steht dem Enduser schneller zur Verfügung Doch was muss nun alles gemacht werden, um diese Daten zu übertragen? Ist das wirklich so einfach, wie man sich das vorstellt? Schauen wir uns doch mal einen einfachen Oracle Block, welcher Daten einer Heap Table speichert, im Detail an.
32 Products schreiben verwendet werden? Was bringen uns speziell für eine Abfrage zusammengesetzte Blöcke im Cache? Wir haben jetzt zwei entscheidende Überlegungen gemacht. Speziell für eine Abfrage zusammengestellte Blöcke im Buffer Cache aufzubewahren bringt uns nichts und der Buffer Cache kann per se mit Blöcken, welche nicht den Original DB Blöcken entsprechen, nichts anfangen. der PGA der Session gespeichert. Und genau diese Lösung hat Oracle für Exadata gewählt. Das bedeutet, Resultate von SmartScans werden nie in der SGA aufbewahrt, die Daten müssen jedes Mal neu gelesen werden und falls kein Direct Path Read stattfindet, findet auch kein SmartScan statt. Soviel zur Theorie, schauen wir uns doch einmal einen Execution Plan einer solchen Abfrage an. Wie wir sehen, sind mehrere Rows in einem Block enthalten. Einige davon werden unserer WHERE Clause genügen, andere nicht. Was passiert nun bei einem normalen Physical Block Read? Der Block wird von der Festplatte gelesen (meist mehrere Blöcke auf einmal entsprechend dem Wert von DB_ FILE_MULTIBLOCK_READ_COUNT). Der gelesene Block wird komplett im Buffer cache in der SGA zwischengespeichert. Wenn Änderungen der Daten gemacht werden, so wird dieser Block im Memory kopiert und nach einem Checkpoint wieder zurück auf die Festplatte geschrieben. Wenn wir nun aber nur die Daten an den DB-Server senden wollen, welche zur WHERE Clause passen, können wir keine kompletten Blöcke mehr hochsenden. Was bleibt denn als Alternative? Wir senden nur den benötigten Teil der Blöcke zur DB, den Rest kann sie selbst mit NULL auffüllen: Das würde zum einen zu viel Platz im Buffer Cache belegen und zum anderen könnten die Blöcke wieder nicht für andere Zwecke verwendet Es müsste verhindert werden, dass diese Blöcke jemals wieder zurückgeschrieben Vielleicht findet sich eine bessere Lösung? Wir setzen einfach neue Blöcke aus unseren Daten zusammen und senden diese hoch: Wie sollen die Daten aber sonst zur DB zurück transferiert werden? Einmal mehr profitiert Oracle von einer älteren Errungenschaft. Dieses Mal ist es der Direct Read, dabei werden die gelesenen Daten nicht im Buffer Cache abgelegt, sondern als Resultset direkt in Die Abfrage war: SELECT * FROM opitz.customers WHERE is_a_class = Y ; Als erstes der Execution Plan ohne Smart Scan. Wir sehen hier schon als zweiten Schritt «TABLE ACCESS STO- RAGE FULL», dies weil die Daten von einem Exadata Storage Server gelesen Id Operation Name Rows Bytes Cost (%CPU) Time 0 SELECT STATEMENT 500K 36M 3100 (1) 00:00:38 * 1 TABLE ACCESS STORAGE FULL CUSTOMERS 500K 36M 3100 (1) 00:00:38 Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 / CUSTOMERS@SEL$1 Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter( IS_A_CLASS = Y ) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - CUSTOMERS. ID [NUMBER,22], CUSTOMERS. FIRST_NAME [VARCHAR2,200], CUSTOMERS. LAST_NAME [VARCHAR2,200], CUSTOMERS. COUNTRY [VARCHAR2,8], IS_A_CLASS [VARCHAR2,1] Wird aber ein SmartScan verwendet, so ändert sich der Execution Plan nochmals. Der grosse Unterschied liegt bei der Predicate Information. Hier kommt neu die Information storage( IS_A_ CLASS = Y ) hinzu. Dies bedeutet, dass die Daten schon auf Storage Seite gefiltert werden können. Ob die Daten am Ende auch auf dem Storage gefiltert werden, bleibt bis hier aber noch offen. Gerade bei kleinen Tabellen wird, nach meiner Erfahrung, trotzdem ein Full Table Scan durchgeführt. Der Optimizer scheint in diesem Fall davon auszugehen, dass es sinnvoller ist, kleine Tabelle komplett in der SGA zur Verfügung zu haben. Das wird leider nicht funktionieren. Wie sollen diese Blöcke denn zum
Products 33 Id Operation Name Rows Bytes Cost (%CPU) Time 0 SELECT STATEMENT 500K 36M 3100 (1) 00:00:38 * 1 TABLE ACCESS STORAGE FULL CUSTOMERS 500K 36M 3100 (1) 00:00:38 Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$1 / CUSTOMERS@SEL$1 Predicate Information (identified by operation id): --------------------------------------------------- 1 - storage( IS_A_CLASS = Y ) filter( IS_A_CLASS = Y ) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - CUSTOMERS. ID [NUMBER,22], CUSTOMERS. FIRST_NAME [VARCHAR2,200], CUSTOMERS. LAST_NAME [VARCHAR2,200], CUSTOMERS. COUNTRY [VARCHAR2,8], IS_A_CLASS [VARCHAR2,1] 26 rows selected. SELECT * FROM v$sqlfn_metadata WHERE offloadable = YES ; Die Funktionen können übrigens nicht nur mit =, sondern mit allen folgenden Bedingungsoperatoren kombiniert!=, <, >, <=, >=, IS [NOT] NULL, LIKE, [NOT] BETWEEN, [NOT] IN, EXISTS, IS OF type, NOT, AND, OR. Wie können wir nun aber feststellen, ob tatsächlich ein SmartScan durchgeführt wurde? Dafür müssen wir uns die WAIT Events betrachten. SmartScan: Filtern von selektierten Spalten Schnell entdecken wir neue WAIT Events wie cell multiblock physical read oder cell single block physical read (schönes Detail am Rande), Oracle macht beim WAIT Event auch sichtbar, auf welche Cell und welche Disk gewartet wurde und für wie viele Bytes. WAIT #33: nam= cell multiblock physical read ela= 7552 cellhash#=398250101 diskhash#=786031317 bytes=40960 obj#=16639 tim=1286372833389835 Für den SmartScan existiert aber ein eigener WAIT Event cell smart table scan. Nur dieser WAIT Event zeigt die Durchführung eines SmartScan an. WAIT #2: nam= cell smart table scan ela= 154 cellhash#=2520626383 p2=0 p3=0 obj#=15043 tim=1289240139259306 SmartScan: Filtern von Predikaten mittels einfacher Funktionen Meistens werden aber Prädikate nicht fix festgelegt, sondern über Funktionen evaluiert. Glücklicherweise kann Oracle aber nicht nur über Konstanten filtern, sondern ganze Funktionen auf dem Storage ausführen. Dies ist leider nicht für alle, aber immerhin für die meisten Funktionen möglich. Für welche Funktionen ein Offloading möglich ist, kann auf jeder 11gR2 Datenbank abgefragt Wie könnte nun aber die zwischen Storage und DB-Server übertragene Datenmenge weiter eingeschränkt werden? Ursprünglich zielte Oracle mit Exadata nur auf Data Warehouse Anwendungen ab. Dort werden oftmals grosse Fact Tabellen mit vielen Spalten verwendet. Eine einzelne Abfrage interessiert sich aber meistens nur für einen Bruchteil dieser Spalten. Deshalb hat Oracle sich dafür entschieden in diesem Fall nur die benötigten Spalten zurück zu senden. Diese Technologie nennen sie column projection. Wie gut sie funktioniert, lässt sich schon an einer einfachen Tabelle zeigen. Ich habe dafür die Werte aus v$sesstat ausgelesen und das Delta berechnet. So können wir gut sehen, wie sich die Werte verändert haben. Die Tabelle ist wie folgt aufgebaut: SQL> desc opitz.huge_customers Name Null? Type ------------------- -------- -------------------- 1 ID NOT NULL NUMBER 2 FIRST_NAME VARCHAR2(50 CHAR) 3 LAST_NAME VARCHAR2(50 CHAR) 4 COUNTRY VARCHAR2(2 CHAR) 5 IS_A_CLASS VARCHAR2(1)
34 Products Als erstes selektiere ich alle Spalten der Tabelle. Dabei kann Exadata natürlich noch keine Daten über Column Projection einschränken. SELECT * FROM opitz.huge_customers WHERE is_a_class = Y ; 4259648 rows selected. NAME VALUE WAIT_CLASS -------------------------------------------------------------------------------- ---------- -------------- cell IO uncompressed bytes 5894037504 SQL cell blocks helped by minscn optimization 738261 SQL cell blocks processed by cache layer 738261 Debug cell blocks processed by data layer 719487 Debug cell blocks processed by txn layer 738261 Debug cell physical IO bytes eligible for predicate offload 5891588096 SQL cell physical IO interconnect bytes 357090752 SQL Jetzt selektiere ich mal nur die Spalte last_name. Neu taucht noch cell physical IO interconnect bytes returned by smart scan in der Statistik auf. Zusätzlich werden weniger Daten über den Interconnect übertragen, das sehen wir am Wert von cell physical IO interconnect bytes (returned by smart scan). Anstatt 340MB werden noch 176MB übertragen. SELECT last_name FROM opitz.huge_customers WHERE is_a_class = Y ; 4259648 rows selected. NAME VALUE WAIT_CLASS -------------------------------------------------------------------------------- ---------- -------------- cell IO uncompressed bytes 5892751360 SQL cell blocks helped by minscn optimization 727561 SQL cell blocks processed by cache layer 727561 Debug cell blocks processed by data layer 719330 Debug cell blocks processed by txn layer 727561 Debug cell physical IO bytes eligible for predicate offload 5891588096 SQL cell physical IO interconnect bytes 185055792 SQL cell physical IO interconnect bytes returned by smart scan 185055792 SQL cell scans 1 SQL cell session smart scan efficiency 5 SQL cell simulated session smart scan efficiency 5892751360 SQL + Debug Jetzt stellt sich nur noch die Frage, ob die übertragene Menge nur durch die Anzahl Spalten oder auch durch deren Grösse beeinflusst wird. SELECT is_a_class FROM opitz.huge_customers WHERE is_a_class = Y ; 4259648 rows selected. NAME VALUE WAIT_CLASS -------------------------------------------------------------------------------- ---------- ------------ cell IO uncompressed bytes 5891801088 SQL cell blocks helped by minscn optimization 720567 SQL cell blocks processed by cache layer 720567 Debug cell blocks processed by data layer 719214 Debug cell blocks processed by txn layer 720567 Debug cell physical IO bytes eligible for predicate offload 5891588096 SQL cell physical IO interconnect bytes 53693936 SQL cell physical IO interconnect bytes returned by smart scan 53693936 SQL cell scans 1 SQL cell session smart scan efficiency 8 SQL cell simulated session smart scan efficiency 5891801088 SQL + Debug
Products 35 Das Feld is_a_class ist ein VAR- CHAR2(1 BYTE), also immer genau 1 Byte gross, während last_name ein VARCHAR2(50 CHAR) ist, von welchem in unserem Fall immer 32 Byte belegt sind. Tatsächlich werden jetzt nur noch 51MB übertragen, vorher waren es noch 176MB. Rechnen wir die Anzahl Rows * 1 Byte so sind unsere Nutzdaten aber nur 4MB. Etwas Overhead wird somit für das Record Set und die Übertragung benötigt. Wie sieht das Verhältnis zwischen Nutzdaten und Overhead denn bei der last_name Spalte aus? Nutzdaten sind 129MB und übertragen wurden 176MB. Der Overhead scheint also pro Row zu sein. Was ja auch verständlich ist. Mit einfacher Mathematik kommen wir auf einen Overhead von etwa 11 Byte pro Row. Übrigens scheint auch ein SELECT * einen ähnlichen Overhead mit sich zu bringen. Rechnen wir für das Number Feld mal 4 Byte, so erhalten wir pro Row 71 Byte (die beiden VARCHAR2(50 CHAR) Felder sind in unserem Fall konstant mit je 32 Byte gefüllt), so müssten theoretisch maximal 288MB übertragen Der SELECT * hat aber 340MB übertragen. Der Overhead bewegt sich also auch hier um 50MB. A n z e i g e globâle Services Individuelle Softwarelösungen Business Intelligence Application Engineering Seit 10 Jahren local, in Ihrer weltweiten Nähe. The local player for global solutions info@irix.ch www.irix.ch Dornacherstrasse 192 CH 4053 Basel T 061 367 93 33 SmartScan: Einfache Joins mehrerer Tabellen Oracle hat sich aber noch eine weitere Optimierung ausgedacht. Tabellen können mit Exadata sogar schon auf Storage Ebene gejoined Dabei hat sich Oracle für eine Technologie entschieden, welche noch eine Nachkontrolle auf dem DB Server benötigt, nämlich den Bloom Filter. Doch weshalb sollte Oracle nicht direkt einen sauberen Join auf der Storage Ebene machen? Da gibt es ein paar grosse Probleme zu bewältigen. Zum einen sind die Daten physisch über mehrere Maschinen verteilt, zum anderen kann die Menge der Datensätze riesig sein. Das heisst, es wäre mit einem klassischen Vorgehen sehr viel Kommunikation zwischen den Storage Servern notwendig, zum anderen müsste auf mindestens einem der Storage Server ein möglicherweise riesiges Zwischenergebnis aufbewahrt Diese Probleme eliminiert der Bloom Filter auf elegante Weise. Kurz gefasst funktioniert er wie folgt: Für jeden Datensatz in der Tabelle T1, in der Spalte, über die der Join gemacht wird, wird ein Hash Wert H1n berechnet. Alle Hashwerte werden über OR Verknüpfungen zu einem gesamt Hash Hg verrechnet. Danach kann für die andere Tabelle T2 auch für jeden Datensatz in der Join Spalte der Hash Wert H2n berechnet Dieser wird nun mit einer AND Verknüpfung mit dem gesamt Hash Hg verrechnet. Entspricht das Resultat nicht H2n, so kann kein äquivalenter Datensatz in T1 vorhanden sein. Entspricht das Resultat H2n, so ist es sehr wahrscheinlich, dass in T1 ein äquivalenter Datensatz vorhanden ist. Das heisst false positive ist möglich, false negative nicht. Die Wahrscheinlichkeit eines false positive kann mit einem grösseren Hash reduziert werden, damit steigt aber Zeitaufwand und Platzbedarf beim Bilden des Hashes. Eine herausragende Erklärung von Bloom Filtern in Oracle findet man unter http://antognini.ch/papers/ BloomFilters20080620.pdf. Da der Bloom Filter zu viele Werte zurückgeben kann, führt die Datenbank den Join nochmals auf der hoffentlich schon stark verringerten Menge durch.
36 Products Leider hat Oracle bei meinen verschiedenen Tests den Bloom Filter auf Storage Ebene nicht angewendet. Grundsätzlich kann man aber das Verhalten des Optimizers auch auf einer normalen Oracle Datenbank testen. Dafür setzt man einfach den Parameter cell_offload_plan_display auf ALWAYS. Der Optimizer zeigt einem dann im Execution Plan an, wo er ein Offloading auf die Storage Cell anwenden könnte. Dabei hat sich herausgestellt, dass Oracle es einmal mehr verschlafen hat die ANSI SQL Syntax vollumfänglich zu unterstützen. Ähnliche Probleme kennt Oracle unter anderem bei komplexen Materialized Views. Das folgende Query hat also nie zu einem Offloading geführt: Fazit Nicht alles spricht für die Oracle Database Machine. Zum einen ist sie wegen der Lizenzkosten relativ teuer, zum anderen wird das System durch den Einsatz von intelligentem Storage noch komplexer. Der Optimizer hat nun eine weitere Möglichkeit die Statements zu tunen und die Datenbankspezialisten müssen noch mehr beachten, wenn sie ein Performanceproblem analysieren. Auf der anderen Seite wird die Hardwareevaluation damit deutlich einfacher. Entscheidet man sich für eine Database Machine, so muss man Storage, Netzwerk und Datenbankserver Hardware nicht mehr aufeinander abstimmen, die Appliance wird schon perfekt ausbalan- SELECT sf.*, c.* FROM opitz.salesfacts sf INNER JOIN opitz.huge_customers2 c ON sf.customer_id = c.id WHERE sf.sale_date < sysdate 700; Schreibt man das Query aber wie folgt um SELECT sf.*, c.* FROM opitz.salesfacts sf, opitz.huge_customers2 c WHERE sf.customer_id = c.id AND sf.sale_date < sysdate 700; So sehen wir neu folgende Informationen in der Predicate Information Predicate Information (identified by operation id): --------------------------------------------------- 3 - access( SF. CUSTOMER_ID = C. ID ) 9 - storage( SF. SALE_DATE <SYSDATE@!-700 AND SYS_OP_BLOOM_FILTER(:BF0000, SF. CUSTOMER_ID )) filter( SF. SALE_DATE <SYSDATE@!-700 AND SYS_OP_BLOOM_FILTER(:BF0000, SF. CUSTOMER_ID )) Wie wir diesem Execution Plan entnehmen können, kann Oracle den Join mit einem Bloom Filter auf Storage Ebene vorbereiten. ciert zur Verfügung gestellt. Auch der Support kommt dabei aus einer Hand. Die Hauptmotivation, sich eine Oracle Database Machine anzuschaffen, bleibt aber die Performance. Mit SmartScan und der in diesem Artikel nicht untersuchten Hybrid Columnar Compression, bietet Exadata zwei starke Funktionen zur Performance Verbesserung. Von ersterem kann man sogar profitieren, ohne dass man den eigenen Code anpassen muss. Die einzige Ausnahme scheint dabei momentan noch der Join-Filter zu sein. Contact OPITZ CONSULTING Schweiz GmbH Daniel Rey E-Mail: daniel.rey@opitz-consulting.com