Zeitreisen
Geschrieben von Uwe M. Küchler am 4. Juli 2011
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:
- Verändern von SYSDATE, nur in der DB
- 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:
- Auf dem Server und dessen Datenbank(en) cron- und Scheduler-Jobs bearbeiten (s.u.)
- Alle Datenbanken auf dem Server herunterfahren
- Systemzeit des Servers umstellen
- Datenbanken auf dem Server wieder hochfahren
- 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