Dienstag, 16. Februar 2010

Tabellen partitionieren - Performance steigern

Es gibt einen neuen Power-Hinweis von SAP, den Hinweis 1333328: Er kommt – allen Rivalitäten der Firmenchefs von Oracle und SAP zum Trotz [1] – exklusiv allen SAP-Kunden zugute, die sich für Oracle als darunterliegendes DBMS entschieden haben.

Oracle bietet seit Version 8 das Feature der Partitionierung an: Man kann eine grosse Datenbanktabelle (der Hinweis empfiehlt als sinnvolle Mindestgrösse 10 Gigabytes) in viele kleine Blöcke unterteilen, die sich jeweils wie eine Mini-Tabelle verhalten: Sie können zum Beispiel eigene Indices haben. In welchen Block ein Satz kommt, kann anhand seiner Daten entschieden werden. So kann man ein Feld, das ein Datum enthält, nutzen, um Monats- oder Wochenblöcke zu definieren. Selektionen, die dieses Feld als Selektionskriterium enthalten, können dann wesentlich schneller ausgeführt werden, da sie i.a. nur auf wenige Partitionen zugreifen müssen. Noch eleganter kann man alte Partitionen löschen: Mit dem Befehl DROP PARTITION ... können die Daten einer ganzen Kalenderwoche im Handumdrehen gelöscht werden. Aber auch die gewöhnliche Archivierung mit DELETE-Statements (und nur hierzu traut sich der Hinweis 1333328, damit die bestehenden Archivierungsprogramme unverändert weiterbenutzt werden können) wird um Grössenordnungen schneller.

Um die Partitionierung durchzuführen, muss die Tabelle einmalig neu umorganisiert (eben "partitioniert") werden. Das wäre für die Riesentabellen mit Hunderten von Millionen Einträgen, die wir dabei im Auge haben, ein kaum machbares Unterfangen, wenn es nicht möglich wäre, diese Arbeit online durchzuführen, also während die Tabelle produktiv genutzt wird. Das ist tatsächlich möglich, und der Hinweis definiert ein neues Kommando für das Backup- und Restore-Tool BRSPACE, mit dem diese Redefinition online durchgeführt werden kann.

Für uns ist es leider noch etwas zu früh für den Hinweis - den nötigen Support Package Level werden wir in den relevanten Systemen vermutlich erst im kommanden Jahr haben - wir freuen uns aber schon darauf! Der Hinweis führt die Tabellen MSEG, BDCP, CDHDR & co., LIPS, VBRP usw. auf. Das zeigt, dass die Autoren ihre Pappenheimer kennen. Diese Tabellen gehören auch in unseren Retailsystemen zu den grössten.

Da wir nicht mehr warten wollten, bis der Hinweis auf unseren Systemen verfügbar ist, haben wir mit den Tabellen des Retail Information Systems (RIS) eigenständig den Anfang gemacht, insbesondere unserer grössten Infostruktur, der Tabelle S520. Sie hat 420 Millionen Einträge. Der Primärschlüssel besteht im wesentlichen aus der Kalenderwoche, der Artikelnummer und der Filiale, wobei die Tabelle zwei Jahre in die Vergangenheit aufbewahrt werden soll. Allein die Archivierung der (unpartitionierten) Tabelle sowie die periodische Verdichtung der Daten nach Warengruppen kostete das System pro Woche rund 103'000 Sekunden, also etwa 28 Stunden. Dabei ist noch zu bedenken, dass die S520 nur eine Tabelle des RIS ist (wenn auch die grösste) – es gibt noch mehr von ihrer Sorte...

Nach der Partitionierung benötigt die "Archivierung" [2] mit dem neuen Programm Z_RIS_PARTITION_REORG nur noch drei Sekunden! In diesen drei Sekunden werden die 4.5 Millionen Sätze der ältesten Kalenderwoche gelöscht, und für die aktuelle Kalenderwoche wird eine neue Partition angelegt. Der Performancefaktor – fairerweise nur mit dem bisherigen Löschprogramm verglichen, also ohne die Archiv-Schreibprogramme und den Warengruppen-Verdichter mitzurechnen – beläuft sich damit auf 1:10'000.

Dazu kommen noch wohltuende Sekundäreffekte: Für die Ausführung eines DELETE Statements mussten nämlich Blöcke in den Datenbankpuffer gelesen werden, um die Where-Bedingung zu evaluieren. Das entfällt bei der Anweisung DROP PARTITION: Der Puffer steht für andere Statements zur Verfügung. Auch das Warengruppen-Verdichtungsprogramm konnte immerhin noch um einen Faktor 200 beschleunigt werden, da es nur auf der aktuellen Kalenderwoche operiert und damit nur eine Partition durchsuchen muss.

Natürlich war es ein besonderer Glücksfall, mit der S520 anzufangen: Sie hat in unserem System auf Datenbankebene keine partitionsübergreifenden Sekundärindices: Alle existierenden Sekundärindices enthalten die für die Partitionierung verwendete Kalenderwoche (SPWOC), so dass beim Entfernen von Partitions kein Index-Update nötig wird. Es gibt andere Infostrukturen, für die wir partitionsübergreifende Indices verwenden. Dann dauert es nicht mehr zwei Sekunden, sondern zwei Minuten, um eine Partition zu löschen. Aber das ist immer noch um Grössenordnungen schneller als die aktuellen Archivierungs- und Löschlaufzeiten.

Wie haben wir die Partitionierungsprogramme organisiert? Herzstück ist die SAP-Klasse CL_SQL_STATEMENT. Um Partitionen zu löschen oder anzulegen, müssen wir die Methode execute_ddl( statement ) dieser Klasse verwenden. Wir haben für diesen Zweck eine lokale abstrakte Oberklasse lcl_stmnt eingeführt, die ein Objekt des Typs CL_SQL_STATEMENT als Delegationsobjekt enthält. Von der Oberklasse sind für jeden Statementtyp eigene Klassen abgeleitet, die die Aufgabe haben, in "ihrem" Statement die aktuellen Parameter des Aufrufs zu substituieren und dann das konkrete Statement als String durch Aufruf einer Methode der Oberklasse auszuführen:
class lcl_stmt definition abstract.
public section.
methods:
constructor importing iv_tablename type tabname.
protected section.
methods:
_execute importing iv_statement type string optional
raising cx_sql_exception.
data:
gv_statement type string,
gv_tabname type tabname,
go_sql_stmt type ref to cl_sql_statement.
endclass.

class lcl_split_partition_stmt definition inheriting from lcl_stmt.
public section.
methods:
constructor importing iv_tablename type tabname,
execute importing iv_next type zora_partition_name
iv_lower_than type zora_partition_name
raising cx_sql_exception.
endclass.

class lcl_drop_partition_stmt definition inheriting from lcl_stmt.
public section.
methods:
constructor importing iv_tablename type tabname,
execute importing iv_part
type zora_partition_name
raising cx_sql_exception.
endclass.


Hier zum Beispiel die Implementierung der Klasse zum Löschen einer Partition:
class lcl_drop_partition_stmt implementation.
method constructor.
super->constructor( iv_tablename = iv_tablename ).
gv_statement =
'alter table &TABLE_NAME ' &
'drop partition "&PARTITION" ' &
'update indexes'.
endmethod. "constructor
method execute.
data: lv_statement type string.
lv_statement = gv_statement.
replace:
'&PARTITION' in lv_statement with iv_part.
_execute( lv_statement ).
endmethod. "execute
endclass. "lcl_drop_partition_stmt IMPLEMENTATION

Zur Laufzeit erhält die execute()-Methode den Partitionsnamen als Parameter und ersetzt diesen im drop partition Statement. Danach wird die _execute()-Methode in der Oberklasse ausgeführt, die das Statement mit Hilfe der Klasse CL_SQL_STATEMENT an die Datenbank sendet.

Diese kleinen Statement-Klassen sind in einer globalen Klasse ZCL_SQL_PARTITIONER beheimatet, die alle im Zusammenhang mit Partitionen stehenden Datenbankoperationen anbietet:



Diese Klasse will aber nichts von dem Verfahren zur Benennung der Partitions wissen. Das Wissen darüber soll in der Clientklasse implementiert werden, die die Partitionen einer konkreten Tabelle oder eine Familie von ähnlichen Tabellen verwaltet. Wir haben eine solche Clientklasse namens ZCL_RIS_PARTITIONER für die Familie der Infostrukturen geschrieben. Sie alle haben den Tag, die Woche oder den Monat (Oberbegriff: die Periode) als Zeitschlüssel. Die wesentliche Methode reorg() dieser Clientklasse berechnet je nach verwendetem Periodentyp die Namen aller Perioden, die aus der Residenzzeit herausfallen und ruft dann Methoden der Dienstklasse ZCL_SQL_PARTITIONER auf, um diese Partitionen zu löschen. Ausserdem wird der nächste Partitionsname in die Zukunft berechnet, um dann wieder mit Hilfe des ZCL_SQL_PARTITIONERs eine neue Partition anzulegen.

Fazit: Zwar können wir das in Hinweis 1333328 veröffentlichte Partitionierungs-Tool noch nicht nutzen, aber unsere eigenen Vorstösse in diese Richtung sind sehr vielversprechend. Alle SAP-Nutzer mit Oracle-Datenbank sollten erwägen, ihre grössten Datenbanktabellen zu partitionieren. Man realisiert nicht nur traumhafte Performanceverbesserungen; wenn es gut läuft, dürfte ein Aufatmen durchs ganze System spürbar werden.



[1] Wenigstens in einer Fussnote soll hier der mittlerweile 14 Jahre zurückliegende Kenwood Cup nicht unerwähnt bleiben, eine Regatta, bei der Hasso Plattners Yacht havarierte. Oracle-Chef Larry Ellison unterliess es, mit seiner Yacht die von Plattner angeforderte Hilfe zu leisten und zog es vor, die Unglücksyacht genüsslich zu filmen. In seiner Wut liess Plattner darauf seine Hose herunter, um ihm wenigstens diesen Spass zu verderben: "If you have to have this on your video, when you go home you should feel s--tty about what you did." (siehe den Artikel in der Sailing World).

[2] Wir haben bei der Gelegenheit festgestellt, dass es für eine physische Archivierung der RIS-Strukturen weder Bedarf noch etwa gesetzliche Notwendigkeit gibt, so dass wir die Daten auch gleich löschen dürfen.

Keine Kommentare :