Vorfahrtsregeln: Ressourcenkonflikte in einer APEX Installation mit dem Ressoucen Manager lösen

Wie der öffentliche Demoserver apex.oracle.com zeigt, ist Application Express für Hosting-Umgebungen sehr gut geeignet. Unterschiedlichste Entwickler arbeiten in verschiedenen Workspaces und jeder hat den Eindruck, als hätte er die Datenbank für sich alleine. Dieser Ansatz ist durchaus auch im eigenen Unternehmen denkbar. Ein zentraler APEX-Server kann viele Fachabteilungen in die Lage versetzen, schnell und einfach eigene Anwendungen zu erstellen und im Unternehmen zu veröffentlichen.

Verschiedene Entwickler arbeiten unabhängig mit den APEX-Workspaces
Abbildung 1: Verschiedene Entwickler arbeiten unabhängig mit den APEX-Workspaces

Gerade in solchen Umgebungen kommt jedoch häufig die Anforderung nach "Vorfahrtsregeln". Wenn der Server unter Last steht, sollen bestimmte Anwendungen eben bevorzugt werden; andere müssen warten. Zu diesem Zweck gibt es in der Oracle-Datenbank schon seit einiger den Ressourcen-Manager, der in vielen IT-Landschaften auch zu diesem Zweck genutzt wird. In der DBA Community, die sich mit für Datenbankadministratoren interessanten Themen beschäftigt, erschien kürzlich ein Artikel zum Einsatz des Ressourcen-Managers. Dieser APEX-Community Tipp geht auf die Besonderheiten des Ressourcen-Managers in APEX-Umgebungen ein.

Der hier vorgestellte Ressourcen-Manager ist Teil der Enterprise Edition der Oracle-Datenbank; mit OracleXE oder der Standard Edition kann das hier vorgestellte nicht nachvollzogen werden.

Der Ressourcen-Manager bildet zunächst Consumer Groups. Eine Consumer Group bekommt dann Ressourcen zugewiesen. Ressourcen sind vornehmlich CPU, aber auch I/O, Parallelität oder Execution Time in einer Sitzung. Im Tipp der DBA-Community (und in den meisten How-To-Dokumenten) erfolgt die Zuordnung zu einer Consumer Group anhand des Datenbankusers - und hier ergibt sich für APEX scheinbar ein Problem - denn alle APEX-Sitzungen arbeiten stets mit dem gleichen Datenbankuser: APEX_PUBLIC_USER. Das ist jedoch, wie dieser Tipp zeigt, überhaupt kein Problem.

Exkurs: Eine APEX-Sitzung erkennen und identifizieren

Zunächst gehen wir in diesem Tipp also kurz darauf ein, wie man die Aktivität einer APEX-Session als solche erkennen und einer konkreten Applikation zuordnen kann. Der DBA schaut, wenn er sich einen Überblick über die Aktivität verschaffen möchte, normalerweise in die View V$SESSION. Loggen Sie sich also in einen APEX-Workspace ein und öffnen Sie parallel dazu ein SQL*Plus-Fenster als DBA und setzen Sie folgende SQL-Abfrage ab:

SQL> select username, action, module, client_info from v$session where username is not null order by 1;

USERNAME                       ACTION                           MODULE                                   CLIENT_INFO
------------------------------ -------------------------------- ---------------------------------------- ------------------------------
APEX_PUBLIC_USER                                                                                         ADMIN
GEOWS                                                           JDBC Thin Client
GEOWS                                                           JDBC Thin Client
MVDEMO                                                          JDBC Thin Client
MVDEMO                                                          JDBC Thin Client

In der Spalte CLIENT_INFO erkennt man schon eine erste Information; die Datenbanksitzung des APEX_PUBLIC_USER enthält dort den Text ADMIN; APEX trägt also den angemeldeten APEX-Nutzernamen in die Spalte CLIENT_INFO ein; das ist aber noch nicht alles. Für den nächsten Test müssen Sie schnell sein: Klicken Sie in der APEX-Entwicklungsumgebung ein wenig herum und wiederholen Sie während der Browser lädt, die SQL*Plus-Abfrage auf die View V$SESSION. Das ist nicht ganz einfach; sie müssen einfach schnell sein (ein Tipp: Wenn Sie in SQL*Plus anstelle der Abfrage ein "r" eingeben, wird die letzte Abfrage wiederholt). Sie sollten dann folgendes Abfrageergebnis sehen:

SQL> select username, action, module, client_info from v$session where username is not null order by 1;

USERNAME                       ACTION                           MODULE                                   CLIENT_INFO
------------------------------ -------------------------------- ---------------------------------------- ------------------------------
APEX_PUBLIC_USER               PAGE 4150                        APEX:APPLICATION 4000                    ADMIN
APEX_PUBLIC_USER                                                                                         ADMIN
GEOWS                                                           JDBC Thin Client
GEOWS                                                           JDBC Thin Client
MVDEMO                                                          JDBC Thin Client
MVDEMO                                                          JDBC Thin Client

Aus dieser kleinen Übung erfährt man schon recht viel: APEX nutzt eine Datenbanksitzung nur während des aktiven Page Renderings - während dieser Zeit werden die Details wie Applikations-ID und Seitennummer in die Spalten MODULE und ACTION eingetragen. Mit diesen Informationen kann man als DBA also einen recht guten Überblick über die Aktivitäten eines APEX-Servers bekommen - und wenn eine Sitzung viele Ressourcen belegt, kann man einfach feststellen, welche Applikation und welche Seite darin die Last auslöst.

Den Ressoucen-Manager für eine APEX-Umgebung einrichten

Diese Informationen sind sehr nützlich zum Einrichten des Ressourcenmanager; denn die Informationen der V$SESSION-Spalten CLIENT_INFO, MODULE und ACTION können zur Zuordnung der Sessions in Consumer Groups genutzt werden. Die folgenden Schritte beschreiben, wie Sie einer von zwei APEX-Applikationen den Vorzug bei CPU-Engpässen geben. Um den Effekt auch wirklich beobachten zu können, spielen Sie zunächst in Ihrem Workspace folgende PL/SQL-Funktion ein (mit den Fibonacci-Zahlen werden wir ein wenig Last auf der CPU erzeugen):

create or replace FUNCTION fib (n binary_integer) RETURN binary_integer IS
BEGIN
  IF (n = 1) OR (n = 2) THEN
     RETURN 1;
  ELSE
     RETURN fib(n - 1) + fib(n - 2);
  END IF;
END fib;

Erzeugen Sie nun zwei APEX-Anwendungen mit je einer Seite und einem Bericht. Das Berichts-SQL soll in beiden Fällen wie folgt aussehen.

select fib(36) from dual

Im folgenden sprechen wir von den Anwendungs-IDs 1001 und 1002. Natürlich können Sie auch andere IDs verwenden; achten Sie jedoch dann darauf, in den SQL-Kommandos stets die ID anzupassen. Setzen Sie den String #TIMING# in den Regions-Footer des Berichts - dann wird die Ausführungszeit direkt auf der Seite mit angezeigt. Sie werden feststellen, dass dieser Bericht einige Sekunden zur Ausführung benötigt. Wenn Sie in einem Browserfenster die Anwendung 1001 ausführen und einige Sekunden später Anwendung 1002, sehen Sie, dass die erste (1001) auch zuerst fertig ist. Beide Applikationen werden gleich behandelt und müssen sich die CPU-Leistung teilen.

Nun werden wir dies ändern: Das folgende SQL-Skript muss wie alle nachfolgenden als DBA eingespielt werden und legt zwei Consumer Groups namens PRIO_LOW und PRIO_HIGH an. Die hier vorgestellten SQL-Skripte können Sie als Vorlage für eigene Ressourcenpläne nehmen - daneben ist die Konfiguration, wie im Tipp der DBA-Community beschrieben, natürlich auch mit dem Oracle Enterprise Manager möglich.

begin
  dbms_resource_manager.clear_pending_area;
  dbms_resource_manager.create_pending_area;

  dbms_resource_manager.create_consumer_group(
    consumer_group => 'PRIO_HIGH',
    comment        => '', 
    cpu_mth        => 'ROUND-ROBIN'
  );
  dbms_resource_manager.create_consumer_group(
    consumer_group => 'PRIO_LOW',
    comment        => '', 
    cpu_mth        => 'ROUND-ROBIN'
  );

  dbms_resource_manager.validate_pending_area;
  dbms_resource_manager.submit_pending_area;
  dbms_resource_manager.clear_pending_area;
end;

Als nächstes werden die APEX-Applikationen den Consumer Groups zugeordnet.

begin
  dbms_resource_manager.clear_pending_area;
  dbms_resource_manager.create_pending_area;

  -- Datenbanksitzungen des APEX_PUBLIC_USER sollen in die Gruppen "PRIO_HIGH" und "PRIO_LOW" wechseln dürfen
  DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SWITCH_CONSUMER_GROUP ( 
    grantee_name   => 'APEX_PUBLIC_USER',
    consumer_group => 'PRIO_HIGH',
    grant_option   => false
  );
  DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SWITCH_CONSUMER_GROUP ( 
    grantee_name   => 'APEX_PUBLIC_USER',
    consumer_group => 'PRIO_LOW',
    grant_option   => false
  );
  -- Applikation "1001" wird der Gruppe "PRIO_HIGH" zugeordnet
  dbms_resource_manager.set_consumer_group_mapping(
    attribute      => dbms_resource_manager.MODULE_NAME,
    value          => 'APEX:APPLICATION 1001',
    consumer_group => 'PRIO_HIGH'
  );

  -- Applikation "1002" wird der Gruppe "PRIO_LOW" zugeordnet
  dbms_resource_manager.set_consumer_group_mapping(
    attribute      => dbms_resource_manager.MODULE_NAME,
    value          => 'APEX:APPLICATION 1002',
    consumer_group => 'PRIO_LOW'
  );

  dbms_resource_manager.validate_pending_area;
  dbms_resource_manager.submit_pending_area;
end;

Nun wird ein Plan erstellt - ein Plan legt fest, welche Consumer Groups welche Ressourcen belegen können. Dabei geht es vor allem um Prioritäten. Im folgenden werden wir diesen CPU-Belegungsplan ein:

Gruppe Level 1 Level 2 Level 3 Level 4
SYS_GROUP 75%      
PRIO_HIGH   95%    
PRIO_LOW   5% 100%  
OTHER_GROUPS       100%

Was bedeutet dieser Plan?

Die SYS_GROUP, also die Administratoren, genießen höchste Priorität. Wenn ein DBA sich verbindet, werden ihm 75% der CPU garantiert. Die anderen 25% werden an die nachfolgenden Ebenen weitergegeben. Wenn kein DBA eingeloggt ist, können die nachfolgenden Ebenen die gesamte CPU nutzen. Auf Ebene zwei werden der Gruppe PRIO_HIGH 95% und PRIO_LOW 5% zugewiesen; was übrig bleibt, geht an Ebene 3. Wenn keine der genannten Consumer Groups aktiv ist, gehen die Ressourcen an die Gruppe OTHER_GROUPS, also "alle anderen".

Spielen Sie das folgende SQL-Skript ein, um die Plan-Belegung einzurichten.

begin
  dbms_resource_manager.clear_pending_area;
  dbms_resource_manager.create_pending_area;

  dbms_resource_manager.create_plan(
    plan => 'APEX_TESTPLAN', 
    comment => '',
    max_iops => NULL,
    max_mbps => NULL
  );
  dbms_resource_manager.create_plan_directive(
    plan => 'APEX_TESTPLAN', 
    group_or_subplan => 'SYS_GROUP',
    comment => '',
    cpu_p1 => 75, cpu_p2 => NULL, cpu_p3 => NULL, cpu_p4 => NULL,
    cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL
  );
  dbms_resource_manager.create_plan_directive(
    plan => 'APEX_TESTPLAN', 
    group_or_subplan => 'PRIO_HIGH',
    comment => '',
    cpu_p1 => NULL, cpu_p2 => 95, cpu_p3 => NULL, cpu_p4 => NULL,
    cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL
  );
  dbms_resource_manager.create_plan_directive(
    plan => 'APEX_TESTPLAN', 
    group_or_subplan =>  'PRIO_LOW',
    comment => '',
    cpu_p1 => NULL, cpu_p2 => 5, cpu_p3 => 100, cpu_p4 => NULL,
    cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL
  );
  dbms_resource_manager.create_plan_directive(
    plan => 'APEX_TESTPLAN', 
    group_or_subplan => 'OTHER_GROUPS',
    comment => '',
    cpu_p1 => NULL, cpu_p2 => NULL, cpu_p3 => NULL, cpu_p4 => 100,
    cpu_p5 => NULL, cpu_p6 => NULL, cpu_p7 => NULL, cpu_p8 => NULL
  );

  dbms_resource_manager.validate_pending_area;
  dbms_resource_manager.submit_pending_area;
end;

Aktivieren Sie den Plan abschließed.

begin
  dbms_resource_manager.switch_plan( 
     plan_name => 'APEX_TESTPLAN', 
     allow_scheduler_plan_switches => true 
  );
end;

Nun können Sie die Ressourcenpläne testen. Öffnen Sie zwei Browserfenster (oder zwei Browser-Reiterkarten) und starten Sie zunächst Applikation 1002. Einige Sekunden später starten Sie im anderen Fenster Applikation 1001. Die Login-Seite (wenn vorhanden) ist nicht relevant; wichtig ist die Berichtsseite, die etwas länger zu Ausführung braucht. Sie werden feststellen, dass die Seite der Applikation 1001 zuerst fertig ist, obwohl sie später angefordert wurde. Das liegt daran, dass diese zu einer Consumer Group mit höherer Priorität gehört - sie wird also bevorzugt behandelt.

Während die Seiten laden, können Sie sich (als DBA) die aktuelle Situation mit einem SQL-Kommando ansehen. Setzen Sie in SQL*Plus (als DBA) folgendes SQL-Kommando ab.

SELECT 
  s.username username,
  co.name gruppe,
  se.state, 
  se.consumed_cpu_time cpu_time, 
  se.cpu_wait_time
FROM v$rsrc_session_info se, v$rsrc_consumer_group co, v$session s
WHERE 
  se.current_consumer_group_id = co.id and  
  s.sid=se.sid and se.state!='NOT MANAGED' and 
  s.username = 'APEX_PUBLIC_USER'

Sie sollten in etwa folgendes Ergebnis sehen. Wahrscheinlich sind es bei Ihnen nicht sechs Zeilen, sondern eine andere Anzahl - das ist aber egal; wichtig ist die Tatsache, dass alle Sessions der Gruppe OTHER_GROUPS zugeordnet sind.

USERNAME                       GRUPPE          STATE             CPU_TIME CPU_WAIT_TIME
------------------------------ --------------- --------------- ---------- -------------
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               8988          8266
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               8061          8013
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               9068             1
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               4035             0
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING              18297            76
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING                420             0

Starten Sie nun nochmals die Berichtsseite der beiden Anwendungen - wie eben jede Anwendung in einem separaten Browserfenster und setzen Sie dann die Abfrage in SQL*Plus nochmal ab.

USERNAME                       GRUPPE          STATE             CPU_TIME CPU_WAIT_TIME
------------------------------ --------------- --------------- ---------- -------------
APEX_PUBLIC_USER               PRIO_HIGH       RUNNING               5077             0
APEX_PUBLIC_USER               PRIO_LOW        WAITING FOR CPU       2409          4882
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               8988          8266
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               8061          8013
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               9068             1
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING               4035             0
APEX_PUBLIC_USER               OTHER_GROUPS    WAITING              18297            76

Hier erkennen Sie sehr gut, dass zwei Datenbanksessions nun in eine andere Consumer Group gewechselt sind. Die Session in der Gruppe PRIO_HIGH läuft (RUNNING), während die andere warten muss.

Aufräumen: Ressourcenpläne löschen

Bleibt die Frage, wie man einen Ressourcenplan wieder löscht. Das folgende, kurze SQL-Skript entfernt die in diesem Tipp erstellten Ressourcenpläne und Consumer Groups wieder.

BEGIN
  dbms_resource_manager.clear_pending_area;
  dbms_resource_manager.create_pending_area;

  -- Vor dem Löschen muss der Plan abgeschaltet werden
  dbms_resource_manager.switch_plan( 
     plan_name => '', 
     allow_scheduler_plan_switches => true 
  );

  -- Der Plan wird mit allem, was dranhängt, gelöscht 
  dbms_resource_manager.delete_plan_cascade( 
    plan => 'APEX_TESTPLAN'
  );

  dbms_resource_manager.validate_pending_area;
  dbms_resource_manager.submit_pending_area;
  dbms_resource_manager.clear_pending_area;
end;

Fazit und Ausblick

Der Ressourcenmanager der Datenbank ist eine sehr elegante Möglichkeit, verschiedene APEX-Anwendungen mit unterschiedlichen Prioritäten ablaufen zu lassen. Neben der CPU können, wie oben erwähnt, auch Metriken wie I/O, Ausführungszeiten und andere gesteuert werden.

Berücksichtigt man nun noch die Tatsache, dass die APEX-Entwicklungsumgebung ebenfalls eine Anwendung ist (Applikations-ID 4000), so lässt sich auch diese in die Ressourcenpläne einbeziehen. Auf der Entwicklungsmaschine könnte die APEX-Entwicklungsumgebung also eine hohe, auf der Produktionsmaschine eine niedrige Priorität bekommen.

Applikation ID
APEX-Admin-Umgebung (Workspace Internal) 4050
SQL Workshop 4500
Application Builder 4000
Data Load/Unload 4300
Workspace Administration 4350

Speziell auf einer zentralen Hosting-Umgebung, bei der nie ganz klar ist, wieviele Ressourcen die verschiedenen Entwickler verbrauchen, kann ein Ressourcen-Management eine wesentliche Verbesserung des Betriebskonzepts sein.

Weitere Informationen

Zurück zur Community-Seite