Computing History · Mainframe Era

Job Classes, Initiators, and How a Batch Queue Gets Ordered

Why a batch scheduler separates jobs into classes consumed by independent initiators, what an initiator costs in storage, and how starvation and throughput pull against each other.

A batch queue with one consumer is a line. The consumer takes the first job, runs it to completion, and takes the next. Since a job’s life is mostly waiting for I/O, a single consumer leaves the processor idle for the majority of the wall clock, and the only fix is to have more than one job resident at once. That is multiprogramming, and it raises the question of who decides which jobs are resident.

The answer that batch systems settled on is a division of labour between two components. The scheduler, which runs as part of the job entry subsystem, decides which jobs should be allowed to become resident and in what order. The initiator, which is a separate system task, takes a job from a class it serves and actually starts it. The two are separate because they have different information and different timescales: the scheduler sees the whole queue and can weigh jobs against each other, while the initiator sees only whether its own resources are available right now.

A class is a resource claim, not a label

A job class in this scheme is not a category for reporting, and the queue it draws from is a spool data set of the kind described in spooling and the output queue. It is a statement about the resources the job needs, expressed in the terms the initiator will check: a region size, a count of tape units, a count of disk volumes, a limit on printed output, a maximum CPU time. Jobs in the same class have compatible requirements, which is what allows an initiator to be configured once for that class and to start any job in it without reconfiguration.

This is why a shop’s class structure looks the way it does. There is a class for short compiles that need a small region and no tapes. There is a class for sort steps that need several tape units and a large region. There is a class for long-running production that needs a large region and a generous time limit but no operator attention. There is a class for work that must wait for a specific tape volume to be mounted, which is a class of jobs that cannot start until an operator acts.

The classification is done by the submitter, through a control statement, and the system validates it. A job that asks for more resources than its class permits is rejected at submission rather than at start, which is the point: the rejection is immediate and the submitter learns about it while still at the terminal.

An initiator is a resident cost

An initiator is a job in its own right. It occupies a region of storage, it has a key, it has control blocks, and it consumes a share of the machine while it is idle. It waits on a queue, wakes when a job is available and its resources permit, performs the allocation, and starts the job.

The cost is therefore per initiator and it is paid continuously. Every additional initiator reduces the storage available for actual work by its own region size, and it consumes a slot from the fixed supply of protection keys and from the fixed supply of job control blocks. A machine with a few hundred kilobytes can afford a handful of initiators; a machine with a few megabytes can afford dozens.

The benefit is per initiator and it is realized only when there is work to overlap. Two initiators let one job compute while the other waits for a tape. Ten initiators let ten jobs overlap, and the processor’s utilization rises toward the point where the processor itself is the bottleneck. Past that point, more initiators do not add throughput; they add contention for the channel, for the disk arms, and for the storage frames, and the average job’s turnaround time gets worse while total throughput stays flat.

The number of initiators is therefore chosen against the workload’s mix, and the choice is a throughput-versus-latency decision. The classic shape of the curve: throughput rises steeply as initiators are added up to the point where the slowest resource saturates, then flattens, then declines as paging or channel contention adds overhead. Where on that curve a shop wants to sit depends on whether it is charging for turnaround time or for throughput, and most shops wanted a point short of the peak because the peak is fragile.

Ordering within a class: four policies and what each optimizes

The scheduler’s job is to order the queue, and the orderings available are the classic ones, each with a specific pathology.

First-in-first-out is the simplest and is what a shop uses when the queue is short and the jobs are homogeneous. It guarantees no starvation and it makes turnaround time proportional to queue length. It performs badly when a long job is ahead of many short ones, because every short job pays the long job’s duration.

Shortest-job-first minimizes average turnaround time, and it is unimplementable exactly because the scheduler does not know how long a job will take before it runs. The practical approximation is a class of jobs whose submitter declares an estimated time, with the estimate used as the ordering key. The declaration is gameable, and the counter is that the system measures actual time and charges against the estimate; a job that consistently exceeds its declared time is either cancelled or moved to a class with a larger limit. This is a scheduling policy enforced by accounting, which is the characteristic mainframe pattern.

Priority ordering is what a shop uses when jobs differ in importance rather than in size. Priority is a number in the job’s control statement, and the scheduler takes the highest-priority job available in the class. The pathology is starvation: a stream of high-priority work prevents low-priority work from running indefinitely. The defence is aging, in which a waiting job’s effective priority increases with time in the queue, so that a low-priority job eventually becomes competitive. Aging is a bounded fix, and it converts starvation into a delay whose bound depends on the aging rate.

Fair-share scheduling allocates the machine by owner rather than by job. Each submitter has a share, and a submitter who has consumed more than its share is ordered behind one who has consumed less. The effect is that a single user who submits a thousand jobs does not monopolize the machine, which is what a priority scheme with no fairness component allows. Fair share requires that the accounting identity of the submitter be part of the queue entry, which is why the identity is recorded at submission and not at execution.

None of these is better in the abstract. Throughput, average turnaround, worst-case turnaround, and fairness are four different objectives, and no policy optimizes all four. The scheduler’s configuration is a statement about which of them the installation values.

Where the initiator and the scheduler disagree

The two-level design produces a specific class of misbehaviour that a single-level scheduler would not have, and understanding it explains several operator practices.

The scheduler decides that job A should run next because it has the highest priority in the class. But the initiator for that class is busy with job B, which is in the middle of a long step. Job A waits, and the scheduler’s decision has no effect until the initiator frees up. Meanwhile job C, of lower priority but in a different class whose initiator is idle, starts immediately. The observed order is not the scheduled order.

The operator’s response is to manage the initiators rather than the queue: start another initiator for the busy class, or stop the initiator for the class whose jobs are jumping ahead. Both are configuration changes made at run time, and both are visible at the console. This is why console commands for starting and stopping initiators, and for changing the number of initiators serving a class, are part of the operator’s vocabulary. The scheduler is a policy engine, and the initiators are the actuators; tuning the machine means adjusting both.

A second disagreement is over resources that the scheduler does not model. A job may be assigned to a class whose initiator has a large enough region, but the job’s data sets may be on volumes that are all in use, so the job starts and immediately waits for a volume. The initiator has spent the allocation and the job occupies a region without computing. The fix is to make volume availability part of the class definition, so that jobs needing scarce volumes go to a class with a dedicated initiator, and the shortage becomes visible as a queue rather than as a set of stalled resident jobs.

What the class structure reveals about the workload

A shop’s class definitions are a compressed description of what it ran. The presence of a class with a large region and several tape units, ordered by priority and served by one initiator, says that the installation had a nightly batch window dominated by a few large sorts. The presence of many small classes served by many initiators says the machine was interactive-adjacent, with short compiles arriving continuously.

The structure also constrains what can be changed. Adding a class requires an initiator, which requires storage, which requires either a larger machine or a smaller region for everything else. Removing a class strands the jobs that named it. The class table is therefore one of the most conservative parts of a batch installation, changed rarely and with care, and its shape outlives the hardware it was designed for.

The accounting records produced at end-of-job are what close the loop. Each record carries the class, the declared limits, and the actual consumption, so a shop can compare the class definitions against what the jobs really needed. A class whose jobs consistently use a fraction of their declared region is over-provisioned, and a class whose jobs are frequently cancelled for exceeding their time limit is under-provisioned. The tuning is empirical, and it is done against the records rather than against intuition. The record format and its fields are described in the resident monitor loop, which is where the accounting is produced, and a job that is repeatedly cancelled for exceeding its limit is the case that checkpoint and restart is designed to make cheap.

References

  1. IBM System/360 Operating System: Job Management — IBM, GC28-6539
  2. Operating Systems: Three Easy Pieces — Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
  3. Scheduling: Theory, Algorithms, and Systems — Michael L. Pinedo, Springer