inMemory mit Oracle Real Application Clusters (RAC)

In meinem letzten Blog habe ich meine ersten Erfahrungen mit inMemory von der Kunden-Seite aus beschrieben. Ich war wirklich beeindruckt vom massiven Performance-Zuwachs und der einfachen Handhabung. Wie vorher schon beschrieben, hat der Kunde eine Single-Instance-Datenbank als Testumgebung verwendet, wobei die Produktion und Vorproduktion auf Oracle RAC liefen. Dies zeigt wieder, wie wichtig es ist, die gleiche Umgebung für Produktion und Vorproduktion zu haben – oder besser sogar auch für den Test, weil dies die nicht vergleichbaren Ergebnisse früh genug gezeigt hätte und nicht erst kurz bevor es in die Produktion ging.

Wie schon im ersten Blog erklärt, hatten wir ein paar einfache und komplexe Abfragen, die ich hier leider nicht verwenden darf. Deshalb nutze ich wieder meine eigene Umgebung.
Die erste Abfrage ist ein einfaches COUNT bei einer einfachen Tabelle, während die komplexere ein JOIN ist mit vier Dimensionstabellen und einer Faktentabelle, also ein simples Stern-Schema. Beim Kunden hatten wir einige komplexere Snowflake-Schemata mit bis zu vier Faktentabellen und ca. 15 Dimensionen, die Ergebnisse waren allerdings genauso wie hier beschrieben.
Zuerst haben wir die Tabellen wieder geladen,  die Faktentabelle mit der Priorität „critical“ und die Dimensionen mit „high“.
Wie üblich habe ich nicht zuerst das komplette Dokument durchgelesen, das führte dazu, dass ich überrascht war, dass die erste Abfrage ([inlinecode]SELECT COUNT(*) FROM umsaetze[/inlinecode]) nach dem Laden mehr als 10 (!) Minuten dauerte. Man erinnere sich: Die Abfrage auf einer Single-Instance ohne inMemory dauerte ungefähr 40 Sekunden und mit inMemory ungefähr eine! Also was hatte ich falsch gemacht? Schauen wir auf die Datenverteilung (Population):

SELECT inst_id, segment_name,  inmemory_size, bytes, bytes_not_populated, populate_status
  FROM v$im_user_segments

SEGMENT_NAME         INMEMORY_SIZE      BYTES BYTES_NOT_POPULATED POPULATE_
-------------------- ------------- ---------- ------------------- ---------
REGION                     1179648     393216                   0 COMPLETED
KUNDEN                    11665408   83886080            40189952 COMPLETED
UMSAETZE                1452670976 6978273280          3163938816 COMPLETED
ZEITEN                     1179648     131072                   0 COMPLETED

Okay, kein Wunder: Während REGION und ZEITEN vollständig geladen worden sind, wurden KUNDEN und die Faktentabelle UMSAETZE nur teilweise geladen. Nachdem ich mit dem DBA gesprochen hatte (wir hatten keinen direkten Zugang auf die Datenbank, also mussten wir den DBA anrufen), fanden wir heraus, dass wir tatsächlich keinen ausreichenden Speicherplatz hatten. Also erhöhte er den Parameter inmemory_size, sodass es für alle Tabellen reichen sollte.

Zweite Abfrage – kein Unterscheid, wieder 400 Sekunden und die Abfrage auf v$im_user_segments zeigt das gleiche Ergebnis. Aber wir sind auf einer RAC Datenbank. Also ist v$ wahrscheinlich gar nicht die richtige Ansicht, sondern gv$:

SELECT inst_id, segment_name,  inmemory_size, bytes, bytes_not_populated, populate_status
  FROM gv$im_user_segments

   INST_ID SEGMENT_NAME         INMEMORY_SIZE      BYTES BYTES_NOT_POPULATED POPULATE_
---------- -------------------- ------------- ---------- ------------------- ---------
         2 REGION                     1179648     393216                   0 COMPLETED
         2 KUNDEN                    11665408   83886080            40189952 COMPLETED
         2 UMSAETZE                1452670976 6978273280          3163938816 COMPLETED
         2 ZEITEN                     1179648     131072                   0 COMPLETED
         1 KUNDEN                    12713984   83886080            39043072 COMPLETED
         1 UMSAETZE                1237254144 6978273280          3740106752 COMPLETED

Parallelism

Wie Sie sehen, sind die Tabellen KUNDEN und UMSAETZE jetzt vollständig geladen und zwischen den beiden Instanzen verteilt. Okay, wenn ich die Dokumentation gelesen hätte, dann wäre mir bewusst gewesen, dass die Tabellen zwischen den Instanzen verteilt werden, wenn man RAC benutzt. Dies bedeutet, dass man Inter-Instance-Parallelism für Anfragen benutzen muss.

Also der nächste Versuch mit dem Parameter „parallel_degree_policy=AUTO;“.

Also lassen Sie uns die Anfrage noch einmal ausführen:

SELECT count(*)
FROM
 umsaetze

SQL>
  COUNT(*)
----------
 144500000

Elapsed: 00:00:00.93


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          0          2          0           0
Execute      1      0.01       0.27          0          5          0           0
Fetch        2      0.05       0.56          0          0          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.08       0.85          0          7          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 103
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         1          1          1  SORT AGGREGATE (cr=5 pr=0 pw=0 time=784786 us)
        14         14         14   PX COORDINATOR  (cr=5 pr=0 pw=0 time=479066 us)
         0          0          0    PX SEND QC (RANDOM) :TQ10000 (cr=0 pr=0 pw=0 time=0 us)
         0          0          0     SORT AGGREGATE (cr=0 pr=0 pw=0 time=0 us)
         0          0          0      PX BLOCK ITERATOR (cr=0 pr=0 pw=0 time=0 us cost=2183 size=0 card=144500000)
         0          0          0       TABLE ACCESS INMEMORY FULL UMSAETZE (cr=0 pr=0 pw=0 time=0 us cost=2183 size=0 card=144500000)

Beeindruckend: Weniger als eine Sekunde!

Also das Resultat ist: die Parallelisierung zu benutzen, wenn man RAC verwendet (und tatsächlich steht in der Dokumentation genau das).

Komplexe Abfrage

Aber ich hatte kein gutes Gefühl. Was ist mit einer komplexeren Abfrage, die vielleicht nicht von Parallelism profitiert? Lassen Sie mich die Komplexe Abfrage durchführen (wieder mit automatischem Parallelism):

SELECT sum(u.umsatz) as gesamtumsatz,
       a.artikelgruppe,
       z.monat_des_jahres,
       z.jahreszahl,
       r.region,
       k.plz,
       k.ort
  FROM region r
       INNER JOIN umsaetze u ON (r.region_id = u.region_id)
       INNER JOIN kunden k ON (k.kunden_id = u.kunden_id)
       INNER JOIN artikel a ON (a.artikel_id = u.artikel_id)
       INNER JOIN zeiten z ON (zeiten_id = zeit_id)
 WHERE r.region = 'Nord'
   AND z.jahreszahl in (2014,2015)
   AND z.monat_des_jahres = 12
 GROUP BY a.artikelgruppe, z.monat_des_jahres, z.jahreszahl, k.plz, k.ort, r.region
 FETCH FIRST 10 PERCENT ROWS ONLY

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.20       0.52          1          5          0           0
Execute      1      0.00       0.05          0          0          0           0
Fetch       88     78.74     309.07     602615     626637          0        1304
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       90     78.94     309.64     602616     626642          0        1304

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 103
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
      1304       1304       1304  VIEW  (cr=626637 pr=602615 pw=0 time=309120365 us cost=61732 size=188525563 card=1376099)
     13031      13031      13031   WINDOW BUFFER (cr=626637 pr=602615 pw=0 time=309125958 us cost=61732 size=112840118 card=1376099)
     13031      13031      13031    HASH GROUP BY (cr=626637 pr=602615 pw=0 time=309111308 us cost=61732 size=112840118 card=1376099)
    511356     511356     511356     HASH JOIN  (cr=626637 pr=602615 pw=0 time=302369604 us cost=35455 size=112840118 card=1376099)
       374        374        374      TABLE ACCESS INMEMORY FULL ARTIKEL (cr=5 pr=3 pw=0 time=18590 us cost=1 size=5984 card=374)
    511356     511356     511356      HASH JOIN  (cr=626632 pr=602612 pw=0 time=302126752 us cost=35451 size=90822534 card=1376099)
   1000000    1000000    1000000       TABLE ACCESS INMEMORY FULL KUNDEN (cr=4770 pr=4766 pw=0 time=75903 us cost=115 size=22000000 card=1000000)
    511356     511356     511356       HASH JOIN  (cr=621862 pr=597846 pw=0 time=300198624 us cost=30075 size=60548356 card=1376099)
      1351       1351       1351        TABLE ACCESS INMEMORY FULL REGION (cr=41 pr=37 pw=0 time=9573 us cost=1 size=12159 card=1351)
   3128202    3128202    3128202        HASH JOIN  (cr=621821 pr=597809 pw=0 time=302970633 us cost=30058 size=217609035 card=6217401)
        32         32         32         JOIN FILTER CREATE :BF0000 (cr=14 pr=0 pw=0 time=626 us cost=1 size=594 card=54)
        32         32         32          TABLE ACCESS INMEMORY FULL ZEITEN (cr=14 pr=0 pw=0 time=492 us cost=1 size=594 card=54)
   3128202    3128202    3128202         JOIN FILTER USE :BF0000 (cr=621807 pr=597809 pw=0 time=301926572 us cost=29693 size=3468000000 card=144500000)
   3128202    3128202    3128202          TABLE ACCESS INMEMORY FULL UMSAETZE (cr=621807 pr=597809 pw=0 time=301826778 us cost=29693 size=3468000000 card=144500000)

Frustrierend! Obwohl sowohl inMemory als auch BloomFilter (BF) verwendet wurden, dauerte die Abfrage ungefähr 300 Sekunden (statt 70 ohne inMemory und drei Sekunden mit einer Single-Instance und inMemory). Sehr enttäuschend!

Also Abfragen, die nicht vom Parallelism profitieren können, werden viel länger dauern als auf einer Single-Instance-Datenbank. Mit einem Blick in die Dokumentation fand ich die Option „DUPLICATE“ mit dem ALTER TABLE INMEMORY Befehl (z.B. ALTER TABLE umsaetze INMEMORY DUPLICATE PRIORITY CRITICAL). Leider gibt es den Parameter nur für die Engineered Systems. Also obwohl der Parameter im v$im_user_segments korrekt angezeigt wird, funktioniert er nicht. Die Tabellen werden immer verteilt.

Um zu Überprüfen, dass inMemory auf der Plattform im Prinzip funktioniert, haben wir den DBA gebeten eine Instanz herunterzufahren. Wie erwartet läuft die Abfrage in der erwarteten Zeit:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.25       0.27          0          5          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       88      1.50       1.61          0         17          0        1304
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       90      1.76       1.89          0         22          0        1304

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 103
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
      1304       1304       1304  VIEW  (cr=17 pr=0 pw=0 time=1606149 us cost=18577 size=1785247 card=13031)
     13031      13031      13031   WINDOW BUFFER (cr=17 pr=0 pw=0 time=1611588 us cost=18577 size=1068542 card=13031)
     13031      13031      13031    HASH GROUP BY (cr=17 pr=0 pw=0 time=1600326 us cost=18577 size=1068542 card=13031)
    511356     511356     511356     HASH JOIN  (cr=17 pr=0 pw=0 time=1432922 us cost=14973 size=41931192 card=511356)
       374        374        374      TABLE ACCESS INMEMORY FULL ARTIKEL (cr=5 pr=0 pw=0 time=157 us cost=1 size=5984 card=374)
    511356     511356     511356      HASH JOIN  (cr=12 pr=0 pw=0 time=1335242 us cost=14971 size=33749496 card=511356)
    511356     511356     511356       HASH JOIN  (cr=9 pr=0 pw=0 time=827741 us cost=11851 size=22499664 card=511356)
      1351       1351       1351        TABLE ACCESS INMEMORY FULL REGION (cr=3 pr=0 pw=0 time=21736 us cost=4 size=12159 card=1351)
   3128202    3128202    3128202        HASH JOIN  (cr=6 pr=0 pw=0 time=705636 us cost=11839 size=118395025 card=3382715)
        32         32         32         JOIN FILTER CREATE :BF0000 (cr=3 pr=0 pw=0 time=29789 us cost=4 size=594 card=54)
        32         32         32          TABLE ACCESS INMEMORY FULL ZEITEN (cr=3 pr=0 pw=0 time=29723 us cost=4 size=594 card=54)
   3128202    3128202    3128202         JOIN FILTER USE :BF0000 (cr=3 pr=0 pw=0 time=189813 us cost=11471 size=3468000000 card=144500000)
   3128202    3128202    3128202          TABLE ACCESS INMEMORY FULL UMSAETZE (cr=3 pr=0 pw=0 time=180968 us cost=11471 size=3468000000 card=144500000)
   1000000    1000000    1000000       TABLE ACCESS INMEMORY FULL KUNDEN (cr=3 pr=0 pw=0 time=30677 us cost=151 size=22000000 card=1000000)

Weniger als zwei Sekunden für die Abfrage!

Fazit

 
Ich sehe keinen Vorteil im Verteilen von Tabellen zwischen Cluster-Instanzen. Ja, eine einfache Abfrage könnte Parallelism verwenden, aber ich kann mich nicht darauf verlassen und im Gegenteil könnte die Abfrage zehnmal langsamer laufen als ohne inMemory. Benutzer von Engineered Systems sollten besser die Option „DUPLICATE“ benutzen, um die gesamten Tabellen auf allen RAC-Instanzen zu laden. Das ist sicherlich der effektivste Weg um inMemory mit RAC zu verwenden, weil man dann die Verbindungen verteilen kann. Im Falle eines Instanz-Fehlers werden die Tabellen automatisch neu verteilt, sodass inMemory fehlertolerant ist. Kunden mit RAC auf einem nicht-engineered System brauchen einen Workaround!

Workaround

Tatsächlich sind zwei Workarounds verfügbar. Ich habe einen getestet, der ganz passabel funktioniert: Es gibt einen nicht-dokumentierten Parameter namens „_inmemory_auto“. Wenn man diesen Parameter auf beiden Instanzen setzt, kann man die Tabellen auf beiden Instanzen erneut laden. Es sieht also aus, als ob dies so ähnlich ist wie der DUPLICATE-Parameter – keine Ahnung, warum Oracle das nicht dokumentiert. Eine andere Lösung wäre es, einen parallelen Instance Group Service zu verwenden ([inlinecode]parallel_instance_group[inlinecode]), der an eine Instanz gekoppelt ist. Ich werde das testen und berichten, ob das sinnvoller ist oder nicht. In der Zwischenzeit freue ich mich über Feedback und Erfahrungsberichte.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen