Setup einer Testumgebung für Oracle In-Memory (Base Level)

Performance, Performance, Performance

Das immerwährende und immerwiederkehrende Problem

Die Oracle Datenbank besitzt historisch schon einige Features, die zur Verbesserung der Performance als Analysetool oder Advisor verwendet werden können. Neu mit 20c (beta), bzw. rückwirkend mit 19.8, kam nun ein altbekanntes Feature, das es bisher nur als Enterprise Edition Option gab, mit einigen Einschränkungen als kostenlose Lizenz zur Enterprise Edition hinzu: das Oracle Database In-Memory Base Level, das wir im Technology Channel hier bereits vorgestellt haben. 

Performance ist für jede Applikation und für jeden Endbenutzer eigentlich immer zu wenig vorhanden - und insbesondere für den Datenbankadministrator sorgt das für ständige, wiederkehrende, sich auch weiter steigernde Herausforderungen ("schnell ist eine Gewöhnungssache"). Wer einmal eine Suche bei Google nach den Punkten "Application Performance Problem" macht, sieht das alleine schon an der Anzahl der Suchergebnisse.

So hat also der moderne Datenbankadministrator (DBA) gleich eine Vielzahl an Strömungen zu berücksichtigen, die die Performance der Systeme schlussendlich in immer kürzeren Abständen beeinflussen, wie folgende Punkte zeigen:

  • Schnelle Businesstrends und Prozessänderungen
  • Rapid Application Development (Kurze Sprints, ...)
  • Konfigurierbare Systeme und Prozesse („Dynamische Toolkits“ ersetzen „vordefinierte Anwendungsprozesse“)
  • Exponentielles Datenwachstum
  • Neue Datentypen (JSON, etc.) und unstrukturierte Daten
  • Entwicklung ausserhalb der DB (Java, Web, ...)
  • Kurze Testzeiten
  • Kein Zugriff auf den Sourcecode

Für den DBA bedeutet dies auch, sich schnell fortzubilden, neue Features zu testen und deren Einsatz zu planen, grossteils noch, bevor die entsprechenden Datenbank- oder Applikationsversionen überhaupt im produktiven Einsatz befindlich sind. Aber wie kann man sich "mal eben schnell" eine Umgebung aufsetzen, um so etwas wie das In-Memory Base Feature zu testen? 

Testdatenbank erstellen

Release 19.8/19.9

Zuerst einmal muss eine neue Datenbank erstellt werden. Hierbei ist zu beachten, dass das Feature eben erst mit Release 19.8 bzw. 19.9 on-premises verfügbar ist. Wichtig ist also, die richtige Softwareversion für sein Betriebssystem herauszufinden und zu installieren. Dabei spielt es erst einmal keine Rolle, ob man die Datenbank in der Non-CDB Architektur oder in der Container-Architektur erstellt, aber wenn man sowieso schon eine Datenbank extra für Tests einrichtet, macht es natürlich Sinn, eine Containerdatenbank dafür zu nutzen. Wichtig ist, dass man für die SGA etwas Platz "on top" rechnet, denn der Column Store des In-Memory benötigt ja schliesslich Platz, wie alle anderen Caches, die Teil der SGA sind, natürlich auch. Für meine persönliche Testumgebung habe ich 400MB hinzugerechnet, damit liessen sich die erstellten Testdaten komplett im In-Memory Column Store unterbringen. 

Oracle Datenbank Beispielschemata erstellen

Am einfachsten bedient man sich den Oracle Beispielschematas, ich habe meine Testszenarien auf dem HR Schema aufgebaut. Herunterladen kann man die entsprechenden Skripte als zip oder tar.gz von Github.
Danach müssen sie in ein Arbeitsverzeichnis kopiert und dort entpackt werden, von dem aus mit SQL auf die Datenbank zugegriffen werden kann.  
Leider sind in den Skripten Unterverzeichnisse als "__SUB_CWD__" abgelegt, so dass die Skripte angepasst werden müssen. Unter Linux geht so etwas z.B., in dem man Perl verwendet und das richtige Unterverzeichnis setzt (hier der Befehl, wenn man schon im richtigen Verzeichnis steht):
 

perl -p -i.bak -e 's#__SUB__CWD__#'$(pwd)'#g' *.sql */*.sql */*.dat

 

Wer mit Windows arbeitet, kann entweder das Perl aus %ORACLE_HOME%/perl/bin verwenden (habe ich persönlich allerdings nicht getestet) oder Tools wie Notepad++ benutzen. Hier gibt es ein "Find in Files" mit der Möglichkeit, ein Search & Replace in allen Dateien in allen Unterverzeichnissen durchzuführen. 

Ich persönlich lege für Testumgebungen immer meine eigenen Tablespaces an, auch wenn die Skripte hier z.B. einen fehlenden Tablespace automatisch anlegen würden (wer OMF nutzt und db_create_file_dest gesetzt hat, kann das z.B. tun). 

Nachdem die Vorbereitungen hier soweit abgeschlossen sind, können die Beispielschemata erstellt werden. Ein Blick in die Readme.txt zeigt, welche Parameter dem mksample Skript übergeben werden müssen. Da ich alle Passwörter aus Einfachheitsgründen auf oracle gesetzt habe, sieht das Erstellen der Schemata in meiner Datenbank pdb1 dann so aus:

SQL> @/home/oracle/Downloads/db-sample-schemas-19c/mksample oracle oracle oracle oracle oracle oracle oracle oracle example temp $ORACLE_HOME/demo/schema/log pdb1

Das HR Schema beinhaltet nach der Erstellung dann folgende Daten, natürlich viel zu wenig, um damit In-Memory Funktionalitäten testen zu können:

 

Beispielschemata mit Massendaten füllen

Verwendung von DBMS_RANDOM

Um die verschiedenen Performanceverbesserungen testen zu können, die z.B. bei IN, <, >, Like oder beim Abfragen von wenigen Spalten einer Tabelle, die viele Spalten besitzt, in Erscheinung treten sollen, werden in diesem Beispiel hier nun verschiedene Tabellen des HR Schemas mit zusätzlichen Massendaten angereichert. Wichtig dabei ist, dass keine zusätzlichen Indexe erstellt werden, dass aber z.B. Foreign-Key-Constraints und Not Null Spalten natürlich erst einmal bleiben sollen. 

Um die Daten einfacher testen und später wieder getrennt löschen zu können, werden zwei neue Einträge in der Tabelle JOBS hinzugefügt. 

SQL> insert into jobs values ('IM_TEST','In-Memory-Tester',14000,240000);

1 row created.

SQL> insert into jobs values ('GG','Good Guys',14,2400);

1 row created.

SQL> commit;

Commit complete.

Als nächstes wird die LOCATIONS Tabelle angepasst und mit Massendaten gefüllt. Dazu müssen die Sequence und die Spalte LOCATION_ID geändert werden. Danach können die Daten mit Hilfe der dbms_random Funktion eingefügt werden. Wichtig hierbei ist, dass die STREET_ADDRESS nun mehrfach mit den gleichen Werten vorkommt. 

declare

i number;

begin

for i in 1..200000 loop

begin

insert into locations (location_id, street_address, postal_code, city, state_province, country_id)

values (locations_seq.nextval,

(select street_address from (select * from locations order by dbms_random.random) where rownum=1),

trunc(dbms_random.value(1000,999999)),

(select city from (select * from locations order by dbms_random.random) where rownum=1),

'',

(select country_id from (select * from countries order by dbms_random.random) where rownum=1)

);

exception when dup_val_on_index then null;

end;

end loop;

end;

/

commit;

Durch die DBMS_RANDOM Funktion ist gewährleistet, dass es keine Gleichverteilung der Daten gibt, aber jede STREET_ADDRESS mit unterschiedlicher Häufigkeit mehrfach in der Tabelle vorkommt.

 

Nun kann als nächstens die Anzahl DEPARTMENTS erhöht werden. Auch hier muss zuerst die Sequence angepasst werden, sowie die Spalten mit den IDs. Ausserdem soll in meinem Test der DEPARTMENT_NAME verwendet werden für die LIKE Abfragen. Deshalb wird die Spalte deutlich erweitert, damit dann beim Einfügen der Massendaten einfach die DEPARTMENT_ID an die bereits vorhandenen Werte angefügt werden kann. 

SQL> alter sequence departments_seq maxvalue 1000000000 increment by 1;

SQL> alter table departments modify department_id number(9);

SQL> alter table departments modify location_id number(9);

SQL> alter table departments modify department_name varchar(100);

Im Anschluss daran können auch hier Daten hinzugefügt werden. Ich persönlich habe es mir einfach gemacht und beim DEPARTMENT_NAME einen Substring auf 20 Zeichen verwendet. Wer das richtig schön machen möchte, kann natürlich auch mit Instring arbeiten und dann wirklich auf "-" prüfen (in meinem Beispiel erhält man bei kürzeren Texten Doppelzahlen am Ende des Textes). 

declare

i number;

begin

for i in 1..15000 loop

insert into departments

select departments_seq.nextval,

substr(department_name,1,20)||'-'||(to_char(trunc(departments_seq.currval))),

(select manager_id from (select * from departments order by dbms_random.random) where rownum=1),

(select location_id from (select * from locations order by dbms_random.random) where rownum=1)

from departments

where rownum < 30;

end loop;

end;

/

In meiner Testdatenbank habe ich mir auf diese Art und Weise in etwa 560.000 verschiedene Departments erzeugt. 

Als letztes müssen dann pro Department noch eine unbestimmte Menge an EMPLOYEES hinzugefügt werden. Durch die Verwendung der DBMS_RANDOM Funktion bei den Namen und E-Mail-Adressen kommen hier sehr sinnlose Kombinationen aus Buchstaben heraus. Aber auch das hat im Falle des Testes der In-Memory Option einen Nutzen. So kann nämlich der Unterschied getestet werden, was die Komprimierung bzw. den Komprimierungsgrad der Daten anbetrifft. Während bei LOCATIONS die STREET_ADDRESS nur aus 23 verschiedenen Werten über alle Datensätze hinweg besteht, sind es bei EMPLOYEES dann nur Werte ohne Duplikate. 

SQL> alter table employees modify department_id number(9);

SQL> alter table employees modify email varchar2(50);

 

declare

cursor c1 is select department_id from departments where rownum < 20000;

begin

for c1_rec in c1 loop

begin

insert into employees (employee_id, first_name, last_name, email, hire_date, job_id, salary, department_id)

values (employees_seq.nextval,

dbms_random.string('a',TRUNC(DBMS_RANDOM.value(5,20))),

dbms_random.string('a',TRUNC(DBMS_RANDOM.value(5,20))),

dbms_random.string('a',TRUNC(DBMS_RANDOM.value(5,20)))||'@mycompany.com',

TRUNC(SYSDATE + DBMS_RANDOM.value(0,366)),

'IN-MEM',

TRUNC(DBMS_RANDOM.value(1000,100000)),

c1_rec.department_id);

exception when dup_val_on_index then null;

end;

end loop;

end;

/

In diesem Beispiel sieht man übrigens auch relativ schön, wie man ein Datum dank DBMS_RANDOM-Funktionalität mit unterschiedlichen Werten hinzufügen kann.

Wie bereits gesagt, logisch gesehen machen die Daten in der Employees Tabelle keinen Sinn:

 

 

Nachdem wir nun dieses Textkauderwelsch in der EMPLOYEES Tabelle hinzugefügt haben, muss die Tabelle nur noch um X Spalten erweitert werden (ohne Indexe). Ich habe es mir hier leicht gemacht und einfach die Spalten mehrfach kopiert und mit den Werten aus den schon vorhandenen Spalten upgedated. 

SQL> alter table employees add (employee_id_1 number(6), first_name_1 varchar2(20),

last_name_1 varchar2(25), email_1 varchar2(50), phone_number_1 varchar2(20),

hire_date_1 date, job_id_1 varchar2(10), salary_1 number(8,2), commission_pct_1 number(2,2),

manager_id_1 number(6), department_id_1 number(9),

employee_id_2 number(6), first_name_2 varchar2(20), last_name_2 varchar2(25),

email_2 varchar2(50), phone_number_2 varchar2(20), hire_date_2 date,

job_id_2 varchar2(10), salary_2 number(8,2), commission_pct_2 number(2,2),

manager_id_2 number(6), department_id_2 number(9),

employee_id_3 number(6), first_name_3 varchar2(20), last_name_3 varchar2(25),

email_3 varchar2(50), phone_number_3 varchar2(20), hire_date_3 date,

job_id_3 varchar2(10), salary_3 number(8,2), commission_pct_3 number(2,2),

manager_id_3 number(6), department_id_3 number(9),

employee_id_4 number(6), first_name_4 varchar2(20), last_name_4 varchar2(25),

email_4 varchar2(50), phone_number_4 varchar2(20), hire_date_4 date,

job_id_4 varchar2(10), salary_4 number(8,2), commission_pct_4 number(2,2),

manager_id_4 number(6), department_id_4 number(9));

 

SQL> update employees set

employee_id_1=employee_id, employee_id_2=employee_id, employee_id_3=employee_id, employee_id_4=employee_id,

first_name_1=first_name, first_name_2=first_name, first_name_3=first_name, first_name_4=first_name,

last_name_1=last_name, last_name_2=last_name, last_name_3=last_name, last_name_4=last_name,

email_1=email, email_2=email, email_3=email, email_4=email,

hire_date_1=hire_date, hire_date_2=hire_date, hire_date_3=hire_date, hire_date_4=hire_date,

job_id_1=job_id, job_id_2=job_id, job_id_3=job_id, job_id_4=job_id,

salary_1=salary, salary_2=salary, salary_3=salary, salary_4=salary,

department_id_1=department_id, department_id_2=department_id, department_id_3=department_id, department_id_4=department_id;

 

SQL> commit;

Um die In-Memory Funktionalität richtig testen zu können, sollten man als Mindestanzahl für LOCATIONS ruhig über 200.000, für DEPARMENTS über 500.000 und für EMPLOYEES über 1 Million Datensätze anlegen. 

Als Endergebnis besitzt man eine Datenbank mit einem Minimum an Daten, die benötigt werden, um In-Memory testen zu können. Ab hier kann man mit Join-Groups, Selects, und diversen Aggregationsverfahren testen und die Erkenntnisse auf die eigenen Datenbanken und Applikationen übertragen.


Über den Autor

Jörg Sobottka

Weitere Beiträge dieses Autors

Kommentare

Keine Kommentare

Kommentar schreiben

* Diese Felder sind erforderlich