Zeitreisen

Zeitreisen sind möglich! Wer hätte das gedacht? Leider beschränkt sich diese Möglichkeit für diesen Artikel auf Datenbank-Server, es wird also vorläufig noch nichts mit dem Nobelpreis… ;-)

Szenario

Geschäftskritische Softwaresysteme müssen oftmals auf ihr Verhalten zu bestimmten Zeiten in der Zukunft geprüft werden. Darunter fallen z.B. Jahreswechsel (wer erinnert sich noch an den Hype um das Jahr 2000?) oder auch Kalenderdaten, zu denen bestimmte Funktionen in der Anwendung aktiv oder inaktiv werden sollen. Normalerweise wird dann das Serverdatum kurz vor oder kurz hinter das zu untersuchende Datum gesetzt und das Systemverhalten beobachtet. Das ist bei Oracle-Datenbanken aber mit einigen Nebenwirkungen behaftet. Vorgehen und Fallstricke sollen hier beleuchtet werden.

Zwei Varianten der Zeitumstellung will ich hier betrachten:

  1. Verändern von SYSDATE, nur in der DB
  2. Verändern der Systemzeit auf dem Server

SYSDATE fest einstellen

Zunächst einmal gibt es die Möglichkeit, das Systemdatum einer Datenbank umzustellen, ohne dabei die Zeit auf dem Server umstellen zu müssen. Das geht als DBA-User mit folgendem Befehl

alter system set fixed_date='2011-07-04';

Dabei gelten folgende Randbedingungen:

  • Das Datum bleibt fest auf dem eingestellten Wert, bis es mit
    alter system set fixed_date=NULL;
    wieder an die Systemzeit gekoppelt wird.
  • Die Zeitkomponente bleibt ebenfalls fest auf 0 Uhr.

Damit ist diese Methode nicht brauchbar für Testszenarien, die das Durchschreiten einer Zeitspanne < 1 d erfordern.

Datum auf dem Server ändern

Hierfür wird auf dem Server die Zeit direkt umgestellt. Unter Windows wie auch unter diversen Unixen geht das mit dem Befehl „date“, wobei in diesem Artikel speziell auf Unix-Umgebungen eingegangen wird.

Allgemeine Vorgehensweise bei der Zeitumstellung:

  1. Auf dem Server und dessen Datenbank(en) cron- und Scheduler-Jobs bearbeiten (s.u.)
  2. Alle Datenbanken auf dem Server herunterfahren
  3. Systemzeit des Servers umstellen
  4. Datenbanken auf dem Server wieder hochfahren
  5. cron- und Scheduler-Jobs ggf. wieder umstellen (s.u.).

Es gelten folgende Randbedingungen:

  • Vorstellen der Zeit ist unproblematischer als Zurückstellen.
    • Beim Vorstellen beachten, daß cron-Jobs und Scheduler-Jobs losfeuern können, wenn ihr geplantes Ausführungsdatum plötzlich in der Vergangenheit liegt. Es empfiehlt sich also,
      • die crontab auszukommentieren
      • Scheduler-Jobs mit DBMS_SCHEDULER.disable() zu deaktivieren
      • Jobs mit DBMS_JOB.broken() zu deaktivieren.
  • Zurückstellen hat mehrere Implikationen:
    • DB-Prozesse (z.B. MMON) können abstürzen, wenn die Zeit „rückwärts läuft“. Dagegen hilft ein Shutdown der DB vor der Zeitumstellung.
    • Die Datensammlung des AWR bleibt stehen – auch dann, wenn der Zeitpunkt der Zeitrückstellung wieder überschritten wird.
    • Wenn die Systemzeit wieder in der Gegenwart ist und AWR-Snapshots immer noch nicht gemacht werden –> Metalink
  • Daneben müssen auch noch Seiteneffekte im Betriebssystem beachtet werden, die aber den Rahmen dieses Artikels sprengen würden.

Beispielcode

Anbei ein paar Beispiele, die zum Zurücksetzen von Jobs eingesetzt werden können. Das SQL ist unter einem DBA-Account auszuführen.

AWR zurücksetzen

BEGIN
  -- Alte AWR-Snapshots loeschen:
  DBMS_WORKLOAD_REPOSITORY.drop_snapshot_range( 1, 9999999 );
  -- MMON anstossen, um die Snapshots wieder zu machen:
  DBMS_WORKLOAD_REPOSITORY.modify_snapshot_settings();
END;
/

Jobs zurücksetzen

Der nachfolgende Code setzt die standardmäßig bei Oracle 10g vorhandenen Jobs zurück.

-- ToDo nach Zeitreise
-- 1. Sched. Windows: Aktuelles Window schliessen
-- 2. Skript ausführen
-- 3. Sched. Windows: Das vorher geschlossene Window wieder öffnen
-- 4. Gather_Stats manuell starten

BEGIN
 DBMS_WORKLOAD_REPOSITORY.drop_snapshot_range( 1, 9999999 );
 DBMS_WORKLOAD_REPOSITORY.modify_snapshot_settings();
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.close_window
 (window_name  => 'SYS.WEEKNIGHT_WINDOW');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.DROP_JOB
 (job_name  => 'PURGE_LOG');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.DROP_SCHEDULE
 (schedule_name  => 'DAILY_PURGE_SCHEDULE');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.CREATE_SCHEDULE
 (
 schedule_name    => 'DAILY_PURGE_SCHEDULE'
 ,start_date       => TO_TIMESTAMP_TZ('','yyyy/mm/dd hh24:mi:ss.ff tzh:tzm')
 ,repeat_interval  => 'freq=daily;byhour=3;byminute=0;bysecond=0'
 ,end_date         => NULL
 ,comments         => NULL
 );
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.CREATE_JOB
 (
 job_name        => 'PURGE_LOG'
 ,schedule_name   => 'SYS.DAILY_PURGE_SCHEDULE'
 ,program_name    => 'SYS.PURGE_LOG_PROG'
 ,comments        => 'purge log job'
 );
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'PURGE_LOG'
 ,attribute => 'RESTARTABLE'
 ,value     => FALSE);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'PURGE_LOG'
 ,attribute => 'LOGGING_LEVEL'
 ,value     => SYS.DBMS_SCHEDULER.LOGGING_RUNS);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'PURGE_LOG'
 ,attribute => 'MAX_FAILURES');
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'PURGE_LOG'
 ,attribute => 'MAX_RUNS');
 BEGIN
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'PURGE_LOG'
 ,attribute => 'STOP_ON_WINDOW_CLOSE'
 ,value     => FALSE);
 EXCEPTION
 -- could fail if program is of type EXECUTABLE...
 WHEN OTHERS THEN
 NULL;
 END;
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'PURGE_LOG'
 ,attribute => 'JOB_PRIORITY'
 ,value     => 3);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'PURGE_LOG'
 ,attribute => 'SCHEDULE_LIMIT');
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'PURGE_LOG'
 ,attribute => 'AUTO_DROP'
 ,value     => FALSE);
 SYS.DBMS_SCHEDULER.ENABLE
 (name                  => 'PURGE_LOG');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.DROP_JOB
 (job_name  => 'GATHER_STATS_JOB');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.DROP_WINDOW_GROUP
 ( group_name => 'SYS.MAINTENANCE_WINDOW_GROUP'
 ,force      => TRUE);
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.CREATE_WINDOW_GROUP
 (
 group_name     => 'MAINTENANCE_WINDOW_GROUP'
 ,window_list    => NULL
 ,comments       => NULL
 );
END;
/
BEGIN
 DBMS_SCHEDULER.ADD_WINDOW_GROUP_MEMBER
 (group_name  => 'SYS.MAINTENANCE_WINDOW_GROUP',
 window_list => 'WEEKNIGHT_WINDOW');
END;
/
BEGIN
 DBMS_SCHEDULER.ADD_WINDOW_GROUP_MEMBER
 (group_name  => 'SYS.MAINTENANCE_WINDOW_GROUP',
 window_list => 'WEEKEND_WINDOW');
END;
/

/********************************************************************/

BEGIN
 SYS.DBMS_SCHEDULER.DROP_WINDOW
 (window_name  => 'SYS.WEEKNIGHT_WINDOW');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.CREATE_WINDOW
 (
 window_name     => 'WEEKNIGHT_WINDOW'
 ,start_date      => NULL
 ,repeat_interval => 'freq=daily;byday=MON,TUE,WED,THU,FRI;byhour=22;byminute=0; bysecond=0'
 ,end_date        => NULL
 ,resource_plan   => NULL
 ,duration        => to_dsInterval('+000 08:00:00')
 ,window_priority => 'LOW'
 ,comments        => 'Weeknight window for maintenance task'
 );
 SYS.DBMS_SCHEDULER.ENABLE
 (name                  => 'SYS.WEEKNIGHT_WINDOW');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.CREATE_JOB
 (
 job_name        => 'GATHER_STATS_JOB'
 ,schedule_name   => 'SYS.MAINTENANCE_WINDOW_GROUP'
 ,program_name    => 'SYS.GATHER_STATS_PROG'
 ,comments        => 'Oracle defined automatic optimizer statistics collection job'
 );
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'RESTARTABLE'
 ,value     => TRUE);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'LOGGING_LEVEL'
 ,value     => SYS.DBMS_SCHEDULER.LOGGING_RUNS);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'MAX_FAILURES');
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'MAX_RUNS');
 BEGIN
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'STOP_ON_WINDOW_CLOSE'
 ,value     => TRUE);
 EXCEPTION
 -- could fail if program is of type EXECUTABLE...
 WHEN OTHERS THEN
 NULL;
 END;
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'JOB_PRIORITY'
 ,value     => 3);
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE_NULL
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'SCHEDULE_LIMIT');
 SYS.DBMS_SCHEDULER.SET_ATTRIBUTE
 ( name      => 'GATHER_STATS_JOB'
 ,attribute => 'AUTO_DROP'
 ,value     => FALSE);
 SYS.DBMS_SCHEDULER.ENABLE
 (name                  => 'GATHER_STATS_JOB');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.open_window
 (window_name  => 'SYS.WEEKNIGHT_WINDOW'
 , duration => '+000 08:00:00');
END;
/

BEGIN
 SYS.DBMS_SCHEDULER.run_job
 (job_name  => 'GATHER_STATS_JOB');
END;
/

asdf

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s