cheat sheet

GDG

Define, reference, and clean up z/OS generation data groups via IDCAMS — relative generations, rollover behaviour, model DSCBs, and the modern GDGEXTENDED mode.

GDG — Generation data groups for rolling datasets

What it is

A Generation Data Group (GDG) is a z/OS catalog construct that lets you refer to a series of dated dataset versions by a single base name and a relative index — (0) for the current generation, (-1) for the previous, (+1) for the next one being created. Behind the scenes each generation is a fully independent dataset stored as BASE.GnnnnV00 (where nnnn is the generation number); the GDG base itself is metadata only, holding the rolling list of physical generations and the policy that controls when old ones get rolled off. Reach for GDGs anytime you run the same job repeatedly against the same logical input — daily extracts, hourly backups, weekly statements, rolling audit logs — and want to keep N historical copies without renaming dataset names in JCL every run; for the modern, IDCAMS-managed variant that supports up to 999 generations and EXTENDED features, use GDGEXTENDED(YES) on the define, and for VSAM clusters use a clusters-with-EXTENDED scheme instead of a GDG.

How it works

The GDG base is created once with IDCAMS DEFINE GENERATIONDATAGROUP. The base never holds any data — it is a catalog node listing the currently active generations and the policy (LIMIT, SCRATCH/NOSCRATCH, EMPTY/NOEMPTY) for managing them.

Each new generation is allocated by JCL using a relative number (+1) and a DCB that comes either from explicit attributes on the DD card or, more commonly, from a "model DSCB" — a tiny zero-track dataset whose DCB attributes IDCAMS records and copies to each new generation. After the JCL step completes successfully, JES2 "rolls over" the new generation: it gets the next sequential GnnnnV00 number, the relative numbers of the older generations shift down (the old (0) becomes the new (-1)), and if the count exceeds LIMIT the oldest generation is scratched or uncatalogued according to the policy.

text
BASE.GDG               (* the GDG base — catalog entry only *)
 ├── BASE.GDG.G0001V00  (* generation 1 — relative (-3) today *)
 ├── BASE.GDG.G0002V00  (* generation 2 — relative (-2) today *)
 ├── BASE.GDG.G0003V00  (* generation 3 — relative (-1) today *)
 ├── BASE.GDG.G0004V00  (* generation 4 — relative (0) today *)
 └── BASE.GDG.G0005V00  (* generation 5 — relative (+1), in-flight *)

Define the GDG base

IDCAMS DEFINE GENERATIONDATAGROUP creates the base entry. The most important parameter is LIMIT(n) — the maximum number of generations to retain before the oldest is rolled off. SCRATCH (the default) physically deletes the rolled-off generation; NOSCRATCH only uncatalogues it, leaving the data on volume for manual cleanup.

text
//DEFGDG   JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE GENERATIONDATAGROUP                 -
         (NAME(ALICE.BACKUP.GDG)             -
          LIMIT(14)                          -
          SCRATCH                            -
          NOEMPTY)
/*

Define parameters

ParameterMeaning
NAME(base)Required. The base name; generations become base.GnnnnV00.
LIMIT(n)Required. Maximum live generations before rollover. 1–255 for classic, 1–999 for GDGEXTENDED.
SCRATCHRolled-off generations are physically deleted (default).
NOSCRATCHRolled-off generations are uncatalogued only — data remains on volume.
EMPTYWhen LIMIT is reached, ALL generations are rolled off, not just the oldest.
NOEMPTYWhen LIMIT is reached, only the oldest is rolled off (default — start here).
OWNER(userid)RACF owner of the GDG base.
TO(date) / FOR(days)Expiration of the base entry itself.
GDGEXTENDED(YES)Use the extended GDG features (LIMIT up to 999, additional management options).

Choosing EMPTY vs NOEMPTY

NOEMPTY is the right default for almost every workload: a daily extract that runs continuously, rolling off generation G0001V00 after the 15th run, keeping G0002V00 through G0015V00 live.

EMPTY only makes sense for jobs that batch-replace the whole window each run — for example, monthly full snapshots where each new run completely supersedes the previous month's set. When LIMIT is hit, the next allocation purges everything and starts from G0001V00 again.

Allocate the model DSCB

A model DSCB is a zero-track dummy dataset whose DCB attributes (RECFM, LRECL, BLKSIZE) IDCAMS copies to each new generation when JCL specifies the GDG by relative number without explicit DCB. One model can serve any number of GDGs.

text
//MODEL    JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1    EXEC PGM=IEFBR14
//MODELDS  DD DSN=ALICE.GDG.MODEL,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(TRK,0),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920),
//             UNIT=SYSDA,
//             VOL=SER=USR001

Reference the model in subsequent GDG-using JCL via DCB=(MODEL.NAME,...), which tells JCL "take all DCB attributes from this dataset's DSCB unless explicitly overridden":

text
//OUTPUT   DD DSN=ALICE.BACKUP.GDG(+1),
//             DISP=(NEW,CATLG,DELETE),
//             DCB=ALICE.GDG.MODEL,
//             SPACE=(CYL,(10,5))

On modern z/OS with SMS-managed datasets, the model DSCB is often optional — SMS DATACLAS supplies the same defaults — but explicit models still work and are the most portable way to template GDG generations.

Relative generation numbers in JCL

(0), (+1), (-1), (-2) ... reference generations relative to the GDG's current state at the time the job is read by JES2. The numbering is locked at job-read time, not at job-execution time — this matters when long-running batch chains overlap with parallel jobs that also roll the GDG.

text
//* Read the most recent generation
//INPUT    DD DSN=ALICE.BACKUP.GDG(0),DISP=SHR

//* Read the generation before the most recent
//PREVIN   DD DSN=ALICE.BACKUP.GDG(-1),DISP=SHR

//* Read three generations back
//OLDIN    DD DSN=ALICE.BACKUP.GDG(-3),DISP=SHR

//* Create the next generation
//OUTPUT   DD DSN=ALICE.BACKUP.GDG(+1),
//             DISP=(NEW,CATLG,DELETE),
//             DCB=ALICE.GDG.MODEL,
//             SPACE=(CYL,(10,5)),
//             UNIT=SYSDA

Within a single job, multiple DDs referencing the same (+1) always resolve to the same physical generation — JES2 records the allocation once and reuses it. A second (+1) in a different job would allocate a second new generation.

Multi-step jobs that need to read what they wrote in an earlier step use (+1) in both DDs:

text
//STEP1    EXEC PGM=GENERATE
//OUTPUT   DD DSN=ALICE.DAILY.GDG(+1),
//             DISP=(NEW,CATLG,DELETE),
//             DCB=ALICE.GDG.MODEL,
//             SPACE=(CYL,(10,5)),
//             UNIT=SYSDA
//*
//STEP2    EXEC PGM=VERIFY
//INPUT    DD DSN=ALICE.DAILY.GDG(+1),DISP=SHR

JCL knows STEP2's (+1) is the same dataset STEP1 just created and resolves it without rolling the GDG twice.

Absolute generation names

Every generation has an absolute name of the form BASE.GnnnnV00 where nnnn is the four-digit generation number (1–9999, wrapping back to 0001 after 9999) and V00 is the version — almost always 00 in practice; JCL keyword VERSION= allows up to 99 historical versions of one generation, a feature rarely used.

Reference an absolute name when:

  • The generation is no longer in the GDG (rolled off but NOSCRATCH preserved it).
  • You want to reference a specific historical generation that has shifted relative position.
  • IDCAMS commands operate on the generation as a normal dataset.
text
//INFILE   DD DSN=ALICE.BACKUP.GDG.G0042V00,DISP=SHR

IDCAMS LISTCAT shows the absolute names and which are currently in the GDG list:

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  LISTCAT ENTRIES(ALICE.BACKUP.GDG) ALL
/*

Rollover behaviour

After a job that creates (+1) completes successfully, JES2 issues the JES rollover: the new GnnnnV00 is added to the GDG's catalog list, the relative numbers shift, and if the count now exceeds LIMIT:

  • SCRATCH — the oldest generation's data and catalog entry are both deleted.
  • NOSCRATCH — only the catalog entry is removed; the physical dataset remains on volume, uncatalogued, eligible for IDCAMS DELETE cleanup or HSM migration.

If the job ABENDs before STEP3 completes, the (+1) created in STEP1 is not rolled over — it remains in "in-flight" status, catalogued under the absolute name but not yet in the GDG list. Re-running the job re-creates (+1) as the same absolute name or the next one, depending on the DISP=(NEW,CATLG,DELETE) recovery path.

The DISP=(NEW,CATLG,DELETE) on the (+1) DD is conventional: if the step ABENDs, the in-flight generation is uncatalogued and deleted, leaving the GDG in its pre-job state. DISP=(NEW,CATLG,KEEP) would preserve the in-flight generation for forensics, but pollutes the catalog.

GDGEXTENDED mode

Classic GDGs support LIMIT up to 255 and store the generation list in a single catalog cell, which becomes a contention point for very high-volume GDGs. GDGEXTENDED(YES) (also called "extended GDGs") raises the limit to 999 and uses a different catalog structure that supports parallel allocation without serializing through one catalog entry.

text
  DEFINE GENERATIONDATAGROUP                 -
         (NAME(ALICE.HIGH.VOLUME.GDG)        -
          LIMIT(500)                         -
          SCRATCH                            -
          NOEMPTY                            -
          GDGEXTENDED(YES))

You can also enable EXTENDED mode globally via the IGGCATxx SYS1.PARMLIB member or for a specific GDG via the EXTENDED keyword on ALTER:

text
  ALTER ALICE.BACKUP.GDG GDGEXTENDED(YES) LIMIT(500)

Existing classic GDGs can be migrated to EXTENDED mode without re-defining; the catalog rewrites itself on the next reference. There is no rollback to classic mode after migration short of redefining the base.

ALTER — change LIMIT or policy

IDCAMS ALTER changes the GDG base attributes without redefining (which would lose the catalog history). Useful when retention requirements shift — e.g. compliance now requires 30 days of backups instead of 14.

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  ALTER ALICE.BACKUP.GDG LIMIT(30)
  ALTER ALICE.BACKUP.GDG NOSCRATCH
  ALTER ALICE.BACKUP.GDG EMPTY
/*

After raising LIMIT, existing generations are unaffected; new generations roll off later. After lowering LIMIT, the next allocation immediately rolls off enough generations to fit the new limit — so lowering from 30 to 14 with 25 current generations would scratch 11 the next time anyone creates a (+1).

Delete generations and the base

To delete one generation, treat it as a normal dataset:

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DELETE ALICE.BACKUP.GDG.G0042V00
/*

To delete the entire GDG including every generation and the base entry, use DELETE … GENERATIONDATAGROUP FORCE:

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DELETE ALICE.BACKUP.GDG GENERATIONDATAGROUP FORCE
/*

FORCE is required when the GDG still has live generations — without it, IDCAMS rejects the delete with IDC3009I VSAM CATALOG RETURN CODE IS 56.

To delete every generation but keep the base for reuse:

text
  DELETE ALICE.BACKUP.GDG.* MASK

MASK deletes the generation datasets but leaves the GDG base intact and the catalog entries empty.

Common pitfalls

  1. (+1) not rolling over after an ABEND — the in-flight generation is deleted by DISP=(NEW,CATLG,DELETE), leaving the GDG state unchanged. Re-running the job creates a fresh (+1) — no manual cleanup needed in the normal case.
  2. Two jobs creating (+1) simultaneously — classic GDGs serialize on the catalog entry; one job waits. Under EXTENDED mode they may both succeed and the rollover order is determined by job-completion timestamp, which is fine for parallel data-pump jobs. Within a single job, every (+1) reference resolves to the same generation; this is by design.
  3. Forgetting DCB= or DCB=MODEL — JCL allocates the generation with RECFM=U,LRECL=0,BLKSIZE=0 and the next read with a real DCB fails. Always supply DCB attributes on (+1) directly or via model DSCB.
  4. Referencing (0) in a job that creates (+1) first(0) resolves to the pre-existing generation (what was current before the job ran); (+1) resolves to the new one. After rollover at end-of-job, the new (0) becomes what was (+1). Be deliberate about which you want.
  5. LIMIT reached and EMPTY set unintentionally — the next (+1) scratches every generation, not just the oldest. Always confirm NOEMPTY is set unless you genuinely want batch-replacement semantics.
  6. NOSCRATCH leaving orphaned datasets — uncatalogued generations accumulate volume space silently. Either use SCRATCH or schedule a cleanup job using LISTC and DELETE.
  7. GDG base deleted while generations exist (without FORCE) — IDCAMS rejects with RC=56. Always use GENERATIONDATAGROUP FORCE or delete generations first with the MASK syntax.
  8. Crossing the 9999 generation number — JES2 wraps to G0001V00. If an old generation G0001V00 still exists (uncatalogued under NOSCRATCH), the next G0001V00 allocation conflicts on volume. Use SCRATCH or migrate to EXTENDED mode with proactive cleanup.
  9. Catalog the model DSCB without SPACE=(TRK,0) — a model with allocated tracks consumes real disk space and may be deleted by space-reclamation jobs. Zero-track is the correct allocation.
  10. Confusing (0) (current) with (1) (which means absolute generation 0001)(1) is parsed as an absolute number, not a relative. Always include the sign for relative: (+1), (-0) (rare), (0).

Sources

References consulted while writing this article. Links open in a new tab.

  • IBM Documentation — Defining Generation Data Groups (DFSMS Using Data Sets) — Authoritative reference for GDG base definitions, model DSCBs, and relative-generation semantics used in this article.

Real-world recipes

Daily rolling backup with 14-day retention

The canonical GDG use case: one IDCAMS define, one model DSCB, then a JCL job scheduled daily that produces (+1) and reads (0) for change-detection. After 14 days, the oldest generation rolls off automatically.

Step 1 — define the GDG and model (one-time):

text
//DEFGDG   JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE GENERATIONDATAGROUP                 -
         (NAME(ALICE.DAILY.BACKUP)           -
          LIMIT(14)                          -
          SCRATCH                            -
          NOEMPTY)
/*
//STEP2    EXEC PGM=IEFBR14
//MODELDS  DD DSN=ALICE.DAILY.BACKUP.MODEL,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(TRK,0),
//             DCB=(RECFM=FB,LRECL=2000,BLKSIZE=27000),
//             UNIT=SYSDA,
//             VOL=SER=USR001

Step 2 — daily job (re-run by the scheduler):

text
//DAILYBKP JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1    EXEC PGM=BACKUPPG
//SYSPRINT DD SYSOUT=*
//INPUT    DD DSN=ALICE.LIVE.DATA,DISP=SHR
//PREVIOUS DD DSN=ALICE.DAILY.BACKUP(0),DISP=SHR
//OUTPUT   DD DSN=ALICE.DAILY.BACKUP(+1),
//             DISP=(NEW,CATLG,DELETE),
//             DCB=ALICE.DAILY.BACKUP.MODEL,
//             SPACE=(CYL,(50,10)),
//             UNIT=SYSDA

After day 15, the first day's backup is scratched automatically; the GDG always contains the most recent 14.

Restore from a specific generation by absolute name

A user reports an issue on data from 3 days ago. List the GDG, identify the absolute name of (-3), and run a restore job that explicitly references that name (so a later rollover during the long-running restore doesn't shift relative numbers under it).

text
//LISTGDG  JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  LISTCAT ENTRIES(ALICE.DAILY.BACKUP) ALL
/*

Identify the absolute name (say G0042V00), then run the restore:

text
//RESTORE  JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1    EXEC PGM=RESTOREPG
//INPUT    DD DSN=ALICE.DAILY.BACKUP.G0042V00,DISP=SHR
//OUTPUT   DD DSN=ALICE.LIVE.DATA,DISP=OLD

Migrate a classic GDG to EXTENDED with higher LIMIT

A monthly extract has been running with LIMIT(255) for years and is now at the catalog cap. Migrate to EXTENDED and raise the limit to 500, keeping every existing generation.

text
//MIGRATE  JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  ALTER ALICE.MONTHLY.EXTRACT          -
        GDGEXTENDED(YES)               -
        LIMIT(500)
  LISTCAT ENTRIES(ALICE.MONTHLY.EXTRACT) ALL
/*

The migration is in-place — no rebuild of generations is required.

Tear down a GDG and all its generations

End of life for an old reporting GDG: delete every generation and the base in one IDCAMS step.

text
//CLEANUP  JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DELETE ALICE.OLD.REPORT.GDG GENERATIONDATAGROUP FORCE
/*

If only the data is obsolete but the GDG base will be reused, delete just the generations:

text
  DELETE ALICE.OLD.REPORT.GDG.* MASK

The base survives with an empty generation list, ready for new (+1) allocations.

Recover from a NOSCRATCH leak

A GDG was running with NOSCRATCH and has accumulated 200 uncatalogued generations on a single volume. The volume is filling. Reclaim it by listing the volume's contents, identifying the orphans (uncatalogued *.GnnnnV00 matching the GDG base name pattern), and deleting them:

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  LISTCAT LEVEL(ALICE.DAILY.BACKUP) ALL
  DELETE ALICE.DAILY.BACKUP.G0001V00 NVR
  DELETE ALICE.DAILY.BACKUP.G0002V00 NVR
  /* repeat for each orphan ... or write a REXX to generate the DELETEs */
/*

NVR (non-VSAM uncatalogued) is the disposition for an uncatalogued non-VSAM dataset that has data on volume.

Then switch the GDG to SCRATCH to prevent future leaks:

text
  ALTER ALICE.DAILY.BACKUP SCRATCH

A multi-step job that reads and writes the same GDG

A daily aggregation reads the previous day's snapshot and the current day's deltas, produces a new snapshot, and verifies it — all in one job. Three DDs reference the same (+1) resolution and they all land in the same physical generation.

text
//DAILYAGG JOB (ACCT),'ALICEJ01',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1    EXEC PGM=AGGPG
//SYSPRINT DD SYSOUT=*
//YESTRDY  DD DSN=ALICE.SNAPSHOT.GDG(0),DISP=SHR
//DELTAS   DD DSN=ALICE.LIVE.DELTAS,DISP=SHR
//TODAY    DD DSN=ALICE.SNAPSHOT.GDG(+1),
//             DISP=(NEW,CATLG,DELETE),
//             DCB=ALICE.SNAPSHOT.MODEL,
//             SPACE=(CYL,(100,20)),
//             UNIT=SYSDA
//*
//STEP2    EXEC PGM=VERIFY,COND=(0,NE,STEP1)
//SYSPRINT DD SYSOUT=*
//INPUT    DD DSN=ALICE.SNAPSHOT.GDG(+1),DISP=SHR
//*
//STEP3    EXEC PGM=NOTIFYPG,COND=(0,NE,STEP2)
//INPUT    DD DSN=ALICE.SNAPSHOT.GDG(+1),DISP=SHR

If STEP2 or STEP3 fails (RC != 0), the next step is skipped via COND but the new generation has been created. To roll back on any failure, change the (+1) to DISP=(NEW,CATLG,DELETE) (the default) and add an explicit IF/THEN/ELSE cleanup step:

text
// IF (RC > 0) THEN
//CLEANUP  EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DELETE ALICE.SNAPSHOT.GDG(+1)
/*
// ENDIF

The (+1) inside the cleanup IDCAMS step resolves to the just-created generation if the rollover has not happened yet. Verify behaviour for your z/OS release before relying on this pattern.

Inspect a GDG from TSO

A REXX one-liner from TSO READY mode prints the live generation list — useful for ad-hoc checks without writing JCL:

bash
LISTC ENT('ALICE.DAILY.BACKUP') ALL

Output:

lua
GDG BASE ------- ALICE.DAILY.BACKUP
   IN-CAT --- CATALOG.USER01
   ATTRIBUTES
     LIMIT-----------------14   NOSCRATCH        NOEMPTY
     GDGEXTENDED-NO
   ASSOCIATIONS
     NONVSAM--ALICE.DAILY.BACKUP.G0042V00
     NONVSAM--ALICE.DAILY.BACKUP.G0043V00
     NONVSAM--ALICE.DAILY.BACKUP.G0044V00
     ...

For more programmatic inspection, route the LISTCAT output through IDCAMS in batch and parse the captured SYSPRINT with a REXX exec or workstation tool — both Zowe and the file-transfer recipes in the FTP cheat sheet are good fits.

Catalog-only deletion for compliance recovery

Sometimes retention policy requires keeping the data but removing it from the active GDG list (the generations stay catalogued under absolute names for legal hold purposes but no longer roll). Use NOSCRATCH plus ALTER to detach generations:

text
//STEP1    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  ALTER ALICE.MONTHLY.EXTRACT NOSCRATCH
  ALTER ALICE.MONTHLY.EXTRACT LIMIT(0)
  /* generations remain catalogued under absolute names, GDG list empty */
  ALTER ALICE.MONTHLY.EXTRACT LIMIT(12)
  /* future generations roll into the GDG normally; the legal-hold generations stay catalogued *,
   *  but outside the GDG and unaffected by future rollovers */
/*

After this sequence, new (+1) allocations start populating a fresh window without touching the preserved older generations.