Combining Schedules and Creating Event-Driven Jobs
Synopsis. Oracle 10g Release 1 (10gR1) added a new and powerful scheduling tool - aptly named the Scheduler - that significantly augments an Oracle DBA's abilities to schedule and control just about any type of frequently repeating task from within an Oracle 10g database. This article - the first in this series -- illustrates how to utilize several enhancements added to the Scheduler in Oracle 10g Release 2 (10gR2) to create even more complex schedules with the new schedule combination features, and how to trigger a job based on an event in an event queue.
As my responsibilities as an Oracle DBA have increased over the years, I have noticed a commensurate increase in ever-more complex requirements for scheduling tasks to occur at frequent - and sometimes less-than frequent! - intervals. Here are some of the typical scheduling requirements I have faced:
Special Handling for Holidays. Many business processes need to run at a particular time on a daily basis. However, when a holiday interrupts the normal business schedule, it may make sense to suspend those processes until the next regular business day. Scheduling around the November and December holidays for U.S. businesses can get complex, especially in years like 2005 when Christmas and New Year's Day both fell on a weekend.
Complex Scheduling For Period-Ending Processes. One of my clients needed to schedule their three-day month-end processing to commence no later than the second business day of the month that is, unless a national holiday preceded that day, in which case the processing schedule shifted ahead by one day unless, of course, the shifting of the schedule would cause the jobs to start on a Friday evening, because no users would be available over the weekend to verify issues that might arise after processing had completed. (I am only slightly exaggerating this scenario.)
External File Batch Processing. A vendor was supposed to supply an external file every day that contained paid invoices, rejected credit card transactions, and other accounting information. Unfortunately, this file tended to arrive on an infrequent basis, so I needed to first verify if the file had been transferred in the last 24 hours; if no file was present, I needed to inform the accounting support team that our vendor has once again forgotten to send the file in a timely fashion. However, if a new file was indeed present, I needed to validate the contents of the new file for any errors; if no errors are found, the data was to be loaded into several production database tables. Once the file had been successfully processed, I needed to notify an end user that the data is ready for review.
Handling these types of scheduling requests usually meant assembling a carefully constructed set of DBMS_JOB tasks, then insuring that the on-call DBA fired off the appropriate tasks in the appropriate order. In the case of complex scheduling changes because of holidays, this usually meant a careful disassemble / reassemble of the previous DBMS_JOB schedule. Fortunately, Oracle 10gR2 has made these types of scheduling nightmares a thing of the past with some nifty new enhancements to the Oracle 10g Scheduler. (For a complete picture of the types of scheduling tasks the Oracle Scheduler can handle as part of Oracle 10g Release 1, please see my previous article series that discusses those features.)
Upgrades to Scheduler Calendaring Expressions
In Oracle 10gR1, the Oracle Scheduler already boasted a robust set of calendaring expression features that could handle most complex scheduling tasks. For example, the Scheduler made it relatively simple to create a schedule that runs every six hours starting at 06:00 on March 1, 2006, and that terminates on March 31, 2006:
BEGIN DBMS_SCHEDULER.CREATE_SCHEDULE ( schedule_name => 'SpecialSchedule_032006' ,start_date => '03/01/2006 06:00' ,end_date => '03/31/2006 18:00' ,repeat_interval => 'FREQ=HOURLY; INTERVAL=6' ,comments => 'Special March 2006 4x daily schedule' ); END; /
This schedule can then be invoked by multiple Scheduler jobs so that they all run on the same schedule; each job can call two different Scheduler programs to accomplish their goals simultaneously:
BEGIN DBMS_SCHEDULER.CREATE_JOB ( job_name => 'SpecialEndofMth' ,job_type => 'STORED_PROCEDURE' ,program_name => 'sp_EndOfMonth' ,schedule_name => 'SpecialSchedule_032006' ,comments => 'Special March 2006 End-of-Month processing' ); DBMS_SCHEDULER.CREATE_JOB ( job_name => 'SpecialEndofQtr' ,job_type => 'STORED_PROCEDURE' ,program_name => 'sp_EndOfQuarter' ,schedule_name => 'SpecialSchedule_032006' ,comments => 'Special March 2006 End-of-Quarter processing' ); END; /
Building Highly-Selective Schedules: BYDATE, OFFSET and SPAN
While this type of scheduling is usually more than sufficient for most processing requirements, I now have the capability to create extremely selective schedules with the new BYDATE parameter. BYDATE allows me to include into a schedule a series of dates based on a date mask, or even specific calendar dates. For example, to build a schedule that is triggered on the tenth day of January, April, July, and October for all current and future years, I could specify the following calendaring expression for a schedule's repeat_interval parameter:
BEGIN DBMS_SCHEDULER.CREATE_SCHEDULE ( schedule_name => 'SampleAnnualSchedule' ,repeat_interval => FREQ=YEARLY; BYDATE='0110,0410,0710,1015' ,comments => 'Example of an annual calendar using date masks' ); END; /
The BYDATE parameter also offers some interesting possibilities with its two optional qualifiers, SPAN and OFFSET. SPAN allows me to choose a range of dates that begins on the specific date or date mask and extends into the future for the number of days specified. On the other hand, OFFSET, SPAN's counterpart, lets me choose a range of dates that begins the specified number of days prior to the specified date and then ends on the specified date itself.
Listing 1.1 shows yet another interesting possibility that the BYDATE parameter offers: the creation of a standard annual holiday schedule for specific dates. In this example, I have created a Scheduler object named StandardHolidays that describes the five-year holiday schedule spanning 2006 through 2010. I've included a rather typical example of U.S. holidays (New Year's Day, Martin Luther King's Birthday, Memorial Day, Independence Day, Labor Day, Thanksgiving and the following Friday, Christmas Eve, Christmas Day, and New Year's Day).
Listing 1.2 illustrates how to use the SPAN and OFFSET directives to create two new schedules, FifthDayCycle and March35thBilling, that will start and end based on specific dates or date masks.
Combining Schedules: INCLUDE, EXCLUDE, and INTERSECT
Oracle 10gR2 offers three new Scheduler directives specific to Schedule objects that allow two schedules to be combined into one:
- The INCLUDE directive tells the Scheduler to combine all occurrences found in one schedule with all occurrences found in a second schedule (essentially the same as the SQL UNION operation).
- The EXCLUDE directive, on the other hand, tells the Scheduler to subtract all occurrences in the second schedule from the occurrences in the first schedule (like a SQL MINUS operation).
- Finally, the INTERSECT directive tells the Scheduler to gather only the occurrences that two schedules have in common.
Since I have already established a standard holiday calendar, I can use it to my advantage to accomplish my first requirement: running a job only on standard business days, but excluding holidays. Listing 1.3 shows two variations of this concept with the creation of the ThursdaysOnlyBatchProcessing and SpecialEndOfMonth schedule objects, both of which use the EXCLUDE directive to remove any occurrence of a standard holiday from each schedule.
BYSETPOS, PERIODS, and BYPERIOD
Three other intriguing Scheduler parameters give me extreme flexibility in choosing one or more specific values from a list of occurrences based on the specified relative position in the resulting set of occurrences:
- The BYSETPOS directive lets me pick the nth occurrence from either end of the result set. For example, if a positive value like +3 is specified for BYSETPOS, then the Scheduler picks the 3rd occurrence starting from the beginning of the result set. However, if a negative value is specified for BYSETPOS, then the Scheduler starts at the end of the result set and works its way backwards until it finds the nth to the last occurrence.
- PERIODS and BYPERIOD work in concert to classify and then select from a resulting set of occurrences. The PERIODS directive is used to define a specific number of elements in a one-dimensional array - for example, twelve occurrences that represent the month-ending dates for each month in a year - and then the BYPERIOD directive can be applied against that array to choose only certain elements.
Listing 1.4 shows three examples of how to utilize these directives in concert with the other new Oracle 10gR2 Scheduler features:
- The first example creates a new Schedule object named SpecialClosingPeriod that will choose the second-to-last date of the month, providing that the occurrence is either a Tuesday, Thursday, or Saturday.
- The second example takes advantage of the PERIODS directive to create a new Schedule object, StandardEndOfMonth, that will include just the last day of each month (excluding standard holidays, of course).
- The last example creates a new Schedule object, PeriodicalEndOfMonth, that uses the BYPERIOD directive to select just the third, sixth, ninth, and twelfth occurrences of the periods defined in the StandardEndOfMonth Schedule object.
Confirming A Schedule Using EVALUATE_CALENDAR_STRING
Oracle 10g provides an excellent tool for validating the expected dates and times at which I expect my Schedule object to start: the EVALUATE_CALENDAR_STRING function in the DBMS_SCHEDULER package. I have provided a sample implementation of this function in Listing 1.5, as well as a unit-testing script that displays a sufficient number of iterations of each Schedule to prove that it is firing at the expected dates and times. I have also provided a sample of the output from this unit testing script.
Scheduling Event-Based Jobs
As interesting as these new scheduling features and methods are, Oracle 10gR2 has added an even more powerful feature to control when a job will start: event-based scheduling. I can now trigger a job to start whenever an event is raised inside a public or private event queue.
The concept of event queues has been around for some time in Oracle, but many Oracle DBAs have not worked with them extensively, so a brief introduction is in order. The Oracle database server utilizes a public event queue, SYS.ALERT_QUE, to publish information about various database events so that special agents called subscribers can be notified of the events. For example, whenever either a warning or critical threshold that was previously set up via the DBMS_SERVER_ALERT package is violated, Oracle publishes information about that violation event to SYS.ALERT_QUE. Through the public agent named SYS.AQ$AGENT, a subscriber can read information from that queue and may either leave the information intact in the queue for other subscribers to view, or remove the information from the queue once it has been read (a process known as dequeuing that information from the queue).
Scheduling a Simple Event-Triggered Job
To illustrate how easy it is to set up an event-triggered Scheduler job, I will first register two Scheduler objects - a Scheduler job object named AUX_EVENT_MONITOR_JOB and a Scheduler schedule object named AUX_EVENT_MONITOR_SCH -- so that they can subscribe to the SYS.ALERT_QUE queue via the SYS.AQ$AGENT agent. I will also set up a new table, SYS.AUX_EVENT_LOGGING, and a new stored procedure, SYS.AUX_EVENT_MONITOR, that will be used to dequeue the event message and then store it in the new table. See Listing 1.6 for the code that sets up the subscriptions to the queue and these new objects.
Next, I will create three new Scheduler objects: schedule AUX_EVENT_MONITOR_SCH, program AUX_EVENT_MONITOR_PGM, and job AUX_EVENT_MONITOR_JOB. The new Scheduler schedule object uses the new 10gR2 CREATE_EVENT_SCHEDULE procedure of DBMS_SCHEDULER so that the schedule will be triggered whenever any new message is queued in the SYS.ALERT_QUE event queue. Note that I can also set up a specific event condition with this procedure so that only specific events trigger the schedule. (More about this in the next article in this series.) The new program object simply calls the SYS.AUX_EVENT_MONITOR stored procedure, and the new job object ties the program and schedule objects together so that whenever a new event is queued, that stored procedure will execute. Listing 1.7 shows the code to create these three new Scheduler objects.
To demonstrate how a newly-queued event triggers this job, I will use Oracle 10g's built-in capabilities to capture threshold violations for two different situations: excessive user commits per second, and excessive blocking / blocked user sessions. In Listing 1.8 I have set rather low warning and critical thresholds for user commits (2 and 4 commits per second respectively) and blocked/blocking users (1 and 2 blocked users, respectively).
To prove out this example, I have built a small sample table as the target of an anonymous PL/SQL block that performs numerous UPDATE operations over a brief time span. I will also open two other user sessions, update one row in the HR.EMPLOYEES table without committing the change in the first session, and then attempt to update the same row in the second session. Between these two sets of DML operations, Oracle will eventually detect the violation and fire the event-driven schedule, causing information to be dequeued from SYS.ALERT_QUE and written into SYS.AUX_EVENT_LOGGING. Listing 1.9 shows these unit test-code snippets and the resulting rows stored in that table.
In the final article in this series, I will explore how to utilize an even more powerful new Oracle 10gR2 Scheduler object - the job chain - and demonstrate how event-triggered jobs can be used to fire complex sets of rules for performing complex tasks via the Scheduler.
References and Additional Reading
While there is no substitute for direct experience, reading the manual is not a bad idea, either. I have drawn upon the following Oracle 10gR2 documentation for the deeper technical details of this article:
B14214-01 Oracle Database New Features Guide
B14229-01 Oracle Streams Concepts and Administration
B14231-01 Oracle Database Administrator's Guide
B14257-01 Oracle Streams Advanced Queuing User's Guide and Reference
B14258-01 PL/SQL Packages and Types Reference