Tag: 19c

  • Oracle EBS 12.2.4 Upgrade to Oracle Database 19c: Oracle Restart Upgrade, Non-CDB to PDB Conversion, and Lessons Learned

    Alhamdulillah, this post brings together one of the more involved upgrade journeys I have worked through recently — taking an Oracle E-Business Suite 12.2 database stack from 12.1.0.2 all the way to 19c, on a single standalone host running Oracle Restart.

    What made this engagement worth writing up is that it was not a clean, textbook upgrade. The Grid Infrastructure layer presented several unexpected challenges — a chain of CLSRSC failures that had to be cleared before it would move, and once Grid was finally on 19c, the database upgrade and the non-CDB to PDB conversion had their own set of gotchas around ASM registration, password files, and UTL_FILE_DIR.

    I have combined both phases — the Grid Infrastructure upgrade and the database upgrade plus multitenant conversion — into one walkthrough, because in the real world they are not separate stories. The GI upgrade forms the foundation for every subsequent phase. If you only read GI upgrade blogs in isolation, you miss how the pieces connect.

    All hostnames, database names, ASM diskgroup names, and paths below are genericized. The technical substance is exactly as it played out.

    Published: June 2026

    Environment:
    Oracle EBS 12.2.4 | AD/TXK Delta 13 | Oracle Database 19c (19.30)
    (Versions shown are from the environment used in this engagement, not a minimum certified baseline — always confirm your own certified levels.)


    Executive Summary

    This upgrade involved moving an Oracle E-Business Suite 12.2 database environment from Oracle Database 12.1.0.2 to 19c on a standalone Oracle Restart host. The journey consisted of four major phases:

    • Oracle Restart (Grid Infrastructure) upgrade from 12.1.0.2 to 19c
    • Database upgrade from 12.1.0.2 to 19c using DBUA
    • Non-CDB to PDB conversion using Oracle EBS TXK utilities
    • EBS application-tier reconfiguration and validation

    The most time-consuming portions were not the upgrades themselves, but resolving legacy Oracle Restart configuration issues, rebuilding HAS components, handling ASM registration after the GI upgrade, addressing password-file requirements during the PDB conversion, and resolving UTL_FILE_DIR migration challenges.

    This guide covers an end-to-end Oracle EBS 12.2.4 upgrade to Oracle Database 19c, including Oracle Restart upgrade, DBUA upgrade, non-CDB to PDB conversion, AutoConfig remediation, and post-upgrade validation.


    Environment overview

    ComponentDetail
    PlatformSingle standalone Linux host (dbhost) running Oracle Restart / standalone HASnot full RAC Clusterware
    Apps tierSeparate node (appshost) under the applmgr-style owner
    EBS release12.2.4
    AD levelAD.C.Delta.13
    TXK levelTXK.C.Delta.13
    Source GI12.1.0.2
    Target GI19c (19.30)
    Source DBNon-CDB EBS database, SID ebsdb, on 12.1.0.2
    Target DB19c (19.30), ebsdb plugged in as a lowercase PDB inside CDB EBSCDB
    StorageASM diskgroups +DATA and +RECO
    Old GI home/u01/app/12.1.0.2/grid
    New GI home/u01/app/19.0.0/grid
    Old DB home/u01/app/oracle/product/12.1.0.2/dbhome_1
    New DB home/u01/app/oracle/product/19.0.0/dbhome_1

    The overall sequence was four phases:

    1. Grid Infrastructure 12.1.0.2 → 19c (Oracle Restart upgrade)
    2. Database 12.1.0.2 → 19c via DBUA (-keepEvents)
    3. Non-CDB → PDB conversion (plug ebsdb into EBSCDB)
    4. EBS application-tier cutover (AutoConfig, adop)

    The transformation at a glance

            BEFORE                                AFTER
    
        EBS Apps Tier                         EBS Apps Tier
             |                                     |
    12.1.0.2 Non-CDB (ebsdb)        ===>      PDB ebsdb
             |                                     |
    Oracle Restart 12.1.0.2                  CDB EBSCDB (19c)
             |                                     |
    ASM (+DATA / +RECO)                      Oracle Restart 19c
                                                   |
                                             ASM (+DATA / +RECO)

    The apps tier and ASM storage stay in place; everything between them moves up to 19c, and the standalone non-CDB becomes a pluggable database inside a container.

    A note on certification before you begin

    Do not assume any upgrade path is valid. Before starting, confirm that your EBS code level, AD/TXK delta level, database RU, and interoperability patches are certified per the Oracle EBS 12.2 and Oracle Database 19c certification and interoperability documentation. Skipping this check is the most common reason an upgrade that “worked in the lab” falls apart in the field. Database RU levels and EBS interoperability certifications change frequently, so always verify the latest certification matrix in My Oracle Support before implementation — and treat the 19.30 RU referenced here as the level used in this engagement, not the only supported target.

    Rough timeline (for maintenance-window planning)

    PhaseTypical duration
    GI (Oracle Restart) upgrade30–60 min
    DBUA database upgrade2–4 hrs
    Non-CDB → PDB conversion~15 min
    AutoConfig & validation30–60 min

    These are working estimates from this engagement, not guarantees — database size, invalid-object count, and hardware all move the numbers. Treat the GI and DBUA phases as the long poles when you size the window.


    Phase 1 — Grid Infrastructure 12.1.0.2 → 19c

    The 19c Grid software was already laid down (gold image extracted into the new home), and the OUI installer had paused — as it always does — waiting for rootupgrade.sh to be run as root. That is where everything went sideways.

    The first wall: CLSRSC-324 — Could not open old init.cssd

    Running the upgrade script:

    /u01/app/19.0.0/grid/rootupgrade.sh

    failed almost immediately:

    CLSRSC-324: Could not open old init.cssd
    Died at /u01/app/19.0.0/grid/crs/install/s_crsutils.pm line 1569

    The 19c rootupgrade.sh reads the old configuration from init.cssd to learn where the previous CRS home lived. On this host, /etc/init.d/init.cssd was simply missing — it had been stripped out during earlier deconfig attempts before I picked the task up.

    First I confirmed what the upgrade actually expected as the old home:

    grep -iE "old_crs_home|asm_upgrade|oracle_home" /u01/app/19.0.0/grid/crs/install/crsconfig_params
    OLD_CRS_HOME=/u01/app/12.1.0.2/grid
    ASM_UPGRADE=true

    Then recreated the missing init.cssd pointing at the old home:

    cat > /etc/init.d/init.cssd << 'EOF'
    #!/bin/bash
    ORA_CRS_HOME=/u01/app/12.1.0.2/grid
    export ORA_CRS_HOME
    EOF
    chmod 755 /etc/init.d/init.cssd

    The second wall: a missing OLR pointer

    With init.cssd back, the next thing the script needs is the Oracle Local Registry pointer file, /etc/oracle/olr.loc — also missing.

    A common trap here is to assume the OLR is named <hostname>.olr. It was not. The actual file was local.ocr:

    ls -lrt /u01/app/12.1.0.2/grid/cdata/localhost/
    -rw-r----- 1 oracle dba 503484416 Feb 10 2017 local.ocr

    Always verify OLR integrity before trusting it:

    /u01/app/12.1.0.2/grid/bin/ocrcheck -local
    Device/File integrity check succeeded
    Local registry integrity check succeeded
    Logical corruption check succeeded

    Then recreate the pointer with the correct path:

    mkdir -p /etc/oracle
    cat > /etc/oracle/olr.loc << 'EOF'
    olrconfig_loc=/u01/app/12.1.0.2/grid/cdata/localhost/local.ocr
    crs_home=/u01/app/12.1.0.2/grid
    EOF
    chmod 644 /etc/oracle/olr.loc

    The third major blocker: CLSRSC-348 / CLSRSC-318 — old HAS stack will not stop or start

    With the bootstrap files back, rootupgrade.sh progressed to step 2 (GetOldConfig) and then stopped again:

    CLSRSC-192: Unable to stop Oracle Restart
    CLSRSC-348: The Oracle Restart stack failed to stop

    The upgrade was trying to cleanly stop the old 12.1 HAS stack — but the old stack was not even contactable:

    /u01/app/12.1.0.2/grid/bin/crsctl check has
    CRS-4639: Could not contact Oracle High Availability Services

    So I tried to start it, and hit the classic library-mismatch error:

    /u01/app/12.1.0.2/grid/bin/crsctl start has
    CRS-4652: Failure 3 in clsvswversion for the local node

    Root Cause

    The original 12.1 Oracle Restart stack was not in a healthy state. Previous deconfiguration attempts had removed critical bootstrap files and left HAS only partially configured.

    Because Oracle only upgrades an existing, functioning stack, rootupgrade.sh could not proceed until the old stack was cleanly rebuilt and verified. The fix is to deconfigure it fully, rebuild it on 12.1, confirm it is online — and only then let the 19c upgrade take over.

    Deconfigure the broken old stack:

    cd /u01/app/12.1.0.2/grid/crs/install
    perl roothas.pl -deconfig -force
    CLSRSC-337: Successfully deconfigured Oracle Restart stack

    Set the environment correctly (the LD_LIBRARY_PATH is what fixes the CRS-4652 library failure):

    export ORACLE_HOME=/u01/app/12.1.0.2/grid
    export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/lib:/usr/lib

    Rebuild the old 12.1 HAS stack using the 12.1 perl binary (do not use the system perl — use the one shipped with the old Grid home):

    /u01/app/12.1.0.2/grid/perl/bin/perl \
    -I/u01/app/12.1.0.2/grid/perl/lib \
    -I/u01/app/12.1.0.2/grid/crs/install \
    /u01/app/12.1.0.2/grid/crs/install/roothas.pl

    This reconfigured Oracle Restart successfully on 12.1 — note this is the script rebuilding the old stack, confirmed by:

    CRS-4123: Oracle High Availability Services has been started.
    CLSRSC-327: Successfully configured Oracle Restart for a standalone server

    Verify HAS is genuinely online before going further:

    /u01/app/12.1.0.2/grid/bin/crsctl check has
    CRS-4638: Oracle High Availability Services is online

    Clearing stale checkpoints and finally running the upgrade

    The earlier failed rootupgrade.sh runs leave behind checkpoint files that block any retry — the script thinks it has already partly completed and refuses to start fresh. Clear them:

    rm -f /u01/app/19.0.0/grid/checkpoints/ROOTHAS_STACK
    rm -f /u01/app/19.0.0/grid/checkpoints/ROOTCRS_STACK

    Now re-run the 19c upgrade:

    /u01/app/19.0.0/grid/rootupgrade.sh

    This time it walked cleanly through all 12 steps (10–20 minutes), pinned the node, backed up the OLR at both the 12.1 and 19c homes, and finished with:

    CRS-4123: Oracle High Availability Services has been started.
    CLSRSC-327: Successfully configured Oracle Restart for a standalone server

    Back in the OUI GUI, clicking OK finalized the installer. ما شاء الله — Grid was now on 19c.

    One of the most common post-GI issues: ASM was never registered

    This one is worth its own warning, because it does not surface during the GI upgrade — it surfaces later, when you go to create the CDB and DBCA fails with something like DBT-06604: insufficient free space. The real reason is that ASM was not registered with Oracle Restart, so the tools silently fell back to filesystem storage, which did not have the room.

    A point worth being precise about: depending on the configuration, ASM may already exist and simply not be running — in which case srvctl start asm (or mounting the diskgroups) is all that is needed. Check before you add — running srvctl add asm when ASM is already configured will error, so confirm the current state with srvctl status asm / srvctl config asm first. In this environment ASM was not registered correctly, so it had to be added back:

    # verify first — does Oracle Restart already know about ASM?
    srvctl status asm
    srvctl config asm
    # in this environment it was not registered, so:
    srvctl add asm
    srvctl start asm

    And if the diskgroups are not mounted, mount them explicitly from the ASM instance:

    ALTER DISKGROUP DATA MOUNT;
    ALTER DISKGROUP RECO MOUNT;

    Lesson: after any Oracle Restart GI upgrade, always confirm srvctl status asm before you trust any DBCA/DBUA storage decision.

    Lessons Learned

    • Verify Oracle Restart is healthy before attempting an upgrade.
    • Confirm init.cssd and olr.loc exist and point to valid locations.
    • Clear stale checkpoint files before retrying rootupgrade.sh.
    • Validate ASM registration immediately after the upgrade.

    Phase 2 — Database 12.1.0.2 → 19c via DBUA

    With 19c Grid and ASM healthy, and the 19c CDB (EBSCDB) already created and patched (datapatch + catmgd.sql for the MGDSYS schema), the next phase was upgrading the non-CDB ebsdb in place from 12.1.0.2 to 19c. It stays a non-CDB for now — the PDB conversion is a deliberately separate Phase 3.

    For EBS environments, launch DBUA using the -keepEvents option so that the EBS-required events and hidden (underscore) parameters are preserved during the upgrade. Dropping those silently breaks the apps tier later.

    X11 forwarding across a user switch

    A very common, very annoying blocker: you SSH in as one OS user, then sudo to oracle, and X11 forwarding breaks because DISPLAY and the Xauthority cookie do not carry across the user switch. DBUA is a GUI tool, so this stops you cold.

    The fix is to capture the cookie as your login user and add it under oracle:

    # as the login user
    xauth list # note the cookie line for your display, e.g. dbhost/unix:11 MIT-MAGIC-COOKIE-1 <cookie>
    # as oracle
    xauth add dbhost/unix:11 MIT-MAGIC-COOKIE-1 <cookie>
    export DISPLAY=localhost:11.0
    xdpyinfo | head # must return display info, not "unable to open display"

    Tip: do not reuse the runbook author’s DISPLAY value (their own workstation name). It will never work for you — set your own.

    DBUA wizard choices that matter for EBS

    ScreenChoice
    CDB / PDB conversionLeave unchecked — conversion is a separate later phase
    Recovery option“I have my own backup/restore strategy” (RMAN backup already taken) — never leave this blank
    Recompile invalid objectsEnabled (parallel)
    Upgrade timezone dataEnabled
    compatibleLeave at 12.1.0.2 during the upgrade — preserves rollback. Raise it later, deliberately.
    Listener migrationKeep the CDB listener migration checked

    A couple of things that look like errors but are not:

    • The DBUA summary may show db_unique_name where you expect db_name. If your unique name differs from the SID, this is just DBUA displaying the unique name — verify against the live DB and move on.
    • The CDB listener (here on port 1523) must be up before DBUA can pass the network screen. If it is down, start it from the 19c home first: lsnrctl start <listener_name>.

    Wrap the whole thing in screen (or tmux) so an SSH drop does not kill the upgrade mid-flight. If screen is not installed, at minimum use nohup-style protection — a dropped DBUA mid-upgrade is a bad day.

    DBUA for an EBS 12.1.0.2 → 19c database typically runs 2–4 hours, plus 30–60 minutes of recompile/datapatch. Expect some ORA- noise in the logs (recompile warnings); watch for new error patterns, not the routine ones.

    Post-DBUA housekeeping

    Once the upgrade completes and all post-upgrade validation is complete (still as a non-CDB), raise COMPATIBLE to 19.0.0. Note that after COMPATIBLE is increased, rollback to the previous release is no longer possible — so this is a deliberate post-validation step, not an automatic next one:

    -- raise compatibility — this is the point of no return, do it deliberately
    ALTER SYSTEM SET compatible='19.0.0' SCOPE=SPFILE;
    -- compatible only takes effect after a restart
    SHUTDOWN IMMEDIATE;
    STARTUP;

    The exact value depends on your site standards and Oracle’s recommendation for the release — some shops set 19.0.0, others use the fully-qualified 19.0.0.0.0. Both forms are accepted; confirm which one your organization standardizes on before setting it.

    Then:

    • Snapshot a pfile from the spfile.
    • Relocate the spfile into the 19c home’s dbs/.
    • Remove obsolete parameters that 19c rejects — notably sec_case_sensitive_logon and utl_file_dir.
    • Run the XDB migration scripts: dbmsxdbschmig.sql then prvtxdbschmig.plb.
    • Run utlrp.sql (twice) to recompile. In this environment the invalid count dropped from over 12,000 to under 30 across the two passes — normal for an EBS database.

    At this point ebsdb is a healthy 19c non-CDB, ready to be plugged in.

    Lessons Learned

    • Always launch DBUA with -keepEvents for EBS — it preserves the required underscore parameters and event settings.
    • Fix X11 forwarding across the user switch before launching; never reuse the runbook author’s DISPLAY value.
    • Leave compatible at 12.1.0.2 during the upgrade and raise it deliberately afterward to preserve rollback.
    • Treat the db_unique_name-vs-db_name display in the summary as cosmetic, and start the CDB listener before the network screen.
    • Wrap the run in screen/tmux so an SSH drop cannot kill the upgrade mid-flight.

    Phase 3 — Non-CDB to PDB conversion

    This is where ebsdb becomes a lowercase PDB inside EBSCDB. EBS ships a set of txk driver scripts for exactly this path; the lowercase, case-sensitive PDB name is a detail that bites you in every subsequent command (ALTER SESSION SET CONTAINER="ebsdb" — with the quotes).

    Step 1 — Pre-creation tasks

    perl $AD_TOP/patch/115/bin/txkOnPremPrePDBCreationTasks.pl

    This generates the PDB descriptor XML (ebsdb_PDBDesc.xml, the output of DBMS_PDB.DESCRIBE, carrying the <ncdb2pdb>1</ncdb2pdb> flag) and shuts the non-CDB ebsdb down permanently — once you are on this path, there is no casual restart of the old non-CDB.

    Step 2 — Compatibility check

    perl $AD_TOP/patch/115/bin/txkChkPDBCompatability.pl

    This reported 8 violations — and per the runbook rules, all were ignorable: standard SQL-patch warnings and the usual benign messages, no character-set mismatch (a character-set violation is the one you must not ignore). Always read each violation; do not blanket-ignore.

    Step 3 — Create the PDB (plug in)

    perl $AD_TOP/patch/115/bin/txkCreatePDB.pl

    This performs a NOCOPY plug-in, allowing the existing ASM datafiles to remain in place while the database is converted into a PDB (the exact ASM file layout varies by environment and Oracle version), and runs noncdb_to_pdb.sql to finish the conversion. It completed in roughly 14 minutes, the PDB state was saved, and ebsdb showed up as CON_ID 3, READ WRITE. ما شاء الله.

    Step 4 — Post-creation tasks (the messy part)

    perl $AD_TOP/patch/115/bin/txkPostPDBCreationTasks.pl

    This is where the real fights happened. Three blockers, all around password files:

    a) ASM password file pre-staging. The script needs the CDB password file in ASM. Pre-stage it with orapwd from the DB home (not the Grid home) into the ASM path for the CDB, then register it:

    orapwd file='+DATA/EBSCDB/orapwEBSCDB' dbuniquename='EBSCDB' format=12
    srvctl modify database -d EBSCDB -pwfile '+DATA/EBSCDB/orapwEBSCDB'

    b) ORA-65066 on the SYSTEM password. Setting the SYSTEM password inside the new PDB threw ORA-65066 until it was applied across all containers:

    ALTER USER SYSTEM IDENTIFIED BY "<StrongPwd#1>" CONTAINER=ALL;

    c) OPW-00029 complexity failure. orapwd rejected the chosen SYSTEM password with OPW-00029 until it satisfied 19c’s special-character complexity rules. Use a value that genuinely meets the 19c verifier (uppercase + lowercase + digit + special character), e.g. <StrongPwd#1>.

    Step 5 — Post-PDB AutoConfig and the UTL_FILE_DIR trap

    Running AutoConfig on the DB tier after the conversion initially failed with a UTL_FILE_DIR error. The trace pointed at /usr/tmp being a symlink rather than a real directory, which the new UTL_FILE_DIR-replacement mechanism in 19c does not tolerate.

    The fix is to drive the directory configuration explicitly with txkCfgUtlfileDir.pl. First, manually create the directory-list file with real (non-symlink) paths:

    # e.g. /u01/app/oracle/product/19.0.0/dbhome_1/dbs/ebsdb_utlfiledir.txt
    # containing two real paths, e.g.:
    # /u01/app/oracle/ebsdb/temp
    # /u01/app/oracle/ebsdb/plsql

    Then run the script in its two modes:

    perl $ORACLE_HOME/appsutil/bin/txkCfgUtlfileDir.pl -contextfile=<DB_CONTEXT> -mode=setUtlFileDir
    perl $ORACLE_HOME/appsutil/bin/txkCfgUtlfileDir.pl -contextfile=<DB_CONTEXT> -mode=syncUtlFileDir

    After that, AutoConfig on the DB tier completed cleanly.

    Worth flagging for the longer term: UTL_FILE_DIR is deprecated, and Oracle’s recommended direction is to use database directory objects (CREATE DIRECTORY) for PL/SQL file I/O wherever your EBS code level supports it. The txkCfgUtlfileDir.pl mechanism above is the EBS-supported bridge for this release; treat directory objects as the strategic target.

    Lessons Learned

    • The lowercase, case-sensitive PDB name ("ebsdb" with quotes) propagates into every subsequent ALTER SESSION SET CONTAINER — get it right once and stay consistent.
    • Read every compatibility violation; ignore only the benign ones, and never ignore a character-set mismatch.
    • Budget real time for password files: pre-stage the ASM password file from the DB home, expect ORA-65066 (apply CONTAINER=ALL) and OPW-00029 (19c complexity rules).
    • A symlinked /usr/tmp breaks post-PDB AutoConfig — drive UTL_FILE_DIR explicitly via txkCfgUtlfileDir.pl with real paths.

    Phase 4 — Application-tier cutover (overview)

    With the database tier healthy at 19c as a PDB, the apps-tier work on appshost is the final phase:

    • Run AutoConfig on both filesystems (fs1 and fs2) to point the apps tier at the new 19c PDB connection details.
    • Clean up any stale adop sessions before starting fresh — an abandoned session from days earlier will block a new cycle. Run adop cleanup (cleanup_mode=full) and then a fresh adop phase=prepare.
    • Validate HugePages, the env files, and Configuration Manager.

    I will cover the full apps-tier cutover in detail in a follow-up post, since it deserves its own walkthrough.


    Final Validation Checklist

    After the upgrade and conversion were complete, the following validations were performed:

    ✓ Oracle Restart resources online and managed by srvctl
    ✓ ASM instance and diskgroups mounted
    ✓ CDB and PDB open in READ WRITE mode
    ✓ Database listener services registered correctly
    ✓ AutoConfig completed successfully on database and application tiers
    ✓ Concurrent Managers started successfully
    ✓ FNDSM service validated
    ✓ Internal Monitor running normally
    ✓ Forms and WebLogic services accessible
    adop phase=prepare completed without errors
    ✓ Invalid objects reviewed and reduced to an acceptable count
    ✓ Application smoke testing completed
    ✓ All EBS services registered in the listener
    ✓ Concurrent Manager processing validated
    ✓ Test concurrent request submitted and completed
    ✓ Forms login verified
    ✓ Workflow Mailer status verified
    ✓ OACORE, FORMS, and OAFM managed servers healthy
    ✓ No critical errors in AutoConfig logs
    ✓ adopscanlog reviewed with no blocking errors
    ✓ Database and ASM resources managed successfully through srvctl

    Quick post-upgrade SQL validation

    A handful of SQL checks confirm the database came back healthy — the component registry, the container/PDB state, and the invalid-object count. Set column formatting first so the output stays readable:

    1. Component registry — every component should report VALID (or UPGRADED) at 19.0.0:

    SET LINESIZE 200 PAGESIZE 100
    COL comp_name FORMAT A45
    COL version FORMAT A18
    COL status FORMAT A12
    SELECT comp_name, version, status
    FROM dba_registry
    ORDER BY comp_name;

    2. Container and PDB state — for a 19c multitenant environment, V$PDBS is the most useful check because it shows OPEN_MODE directly (the EBS PDB should be READ WRITE):

    COL name FORMAT A20
    SELECT con_id, name, open_mode
    FROM v$pdbs
    ORDER BY con_id;

    DBA_PDBS complements it by showing the persisted state — NORMAL for a healthy plugged-in PDB:

    COL pdb_name FORMAT A20
    COL status FORMAT A12
    SELECT pdb_name, status
    FROM dba_pdbs
    ORDER BY pdb_name;

    3. Invalid objects — review the breakdown by owner, then the overall total (an EBS database normally settles to a small count after two utlrp.sql passes):

    COL owner FORMAT A22
    COL object_type FORMAT A22
    SELECT NVL(owner,'(none)') AS owner,
    object_type,
    COUNT(*) AS invalid_count
    FROM dba_objects
    WHERE status = 'INVALID'
    GROUP BY owner, object_type
    ORDER BY invalid_count DESC;
    -- overall total
    SELECT COUNT(*) AS total_invalid
    FROM dba_objects
    WHERE status = 'INVALID';

    Although every environment is different, the upgrade itself proved relatively straightforward once the underlying Oracle Restart configuration issues were corrected. The majority of effort was spent validating and repairing legacy infrastructure assumptions rather than executing the upgrade utilities themselves.

    Key takeaways

    1. A GI upgrade is only as clean as the old stack underneath it. Most CLSRSC-318 / CLSRSC-324 / CLSRSC-348 failures trace back to an old HAS stack that was never properly configured or was half-deconfigured. Rebuild it cleanly on the old version first, then upgrade.
    2. Verify the bootstrap files yourself. Do not assume the OLR is <hostname>.olr — check cdata/localhost/ and verify with ocrcheck -local.
    3. Stale checkpoints block retries silently. Clear ROOTHAS_STACK / ROOTCRS_STACK before re-running rootupgrade.sh.
    4. After an Oracle Restart GI upgrade, register ASM (srvctl add asm / srvctl start asm) before you trust any storage-related tool.
    5. Use dbua -keepEvents for EBS, and fix X11 forwarding across the user switch before you launch.
    6. The PDB conversion’s hardest part is password files — ORA-65066, OPW-00029, and ASM password-file placement. Budget time for it.
    7. Post-PDB AutoConfig + UTL_FILE_DIR: a symlinked /usr/tmp will fail it. Drive the directory list explicitly with txkCfgUtlfileDir.pl using real paths.

    Looking back, the actual upgrade steps were straightforward. The real effort was understanding and repairing the assumptions the upgrade process makes about the existing environment. Missing bootstrap files, partially deconfigured Oracle Restart components, ASM registration gaps, password-file placement, and UTL_FILE_DIR migration issues consumed far more time than the upgrade binaries themselves. Those lessons are often absent from official documentation — which is exactly why I wanted to document the journey end to end.


    References

    • Oracle E-Business Suite Release 12.2 — Interoperability Notes for Oracle Database 19c (My Oracle Support)
    • Oracle Database 19c Upgrade Guide
    • Using UTL_FILE_DIR or Database Directories for PL/SQL File I/O in Oracle E-Business Suite (My Oracle Support)
    • Oracle E-Business Suite Multitenant / Pluggable Database (PDB) conversion documentation
    • Oracle Grid Infrastructure Installation and Upgrade Guide (Oracle Restart)

    (Look up the current My Oracle Support note IDs for your exact code and database levels — they are revised regularly.)

    If this write-up helped you during your own Oracle E-Business Suite upgrade, you’re welcome to follow the blog for more real-world Oracle Database, EBS, ADOP, cloning, upgrade, and troubleshooting guides drawn from production experience.


    Disclaimer: This post reflects my own hands-on experience and is shared for educational purposes only. All hostnames, database names, diskgroup names, and paths have been genericized. Always test thoroughly in a non-production environment and follow Oracle’s official documentation and your organization’s change-control process before applying any of these steps. The views expressed here are my own and do not represent those of any employer or client.

  • Applying Oracle 19c Apr 2026 CPU / RU 19.31 on a Single Instance CDB – Complete Patching Guide

    In this article, we are going to demonstrate the steps to apply the Oracle April 2026 Critical Patch Update (CPU) on a Single Instance Oracle 19c Container Database (CDB). Here we will apply combo patch 39062931 which contains 39034528 (Database Apr 2026 Release Update 19.31.0.0.260421) and 38906621 (OJVM Component Release Update 19.31.0.0.260421). We also cover an important real-world scenario where a previously applied overlay patch 34672698 (ORA-00800 fix) conflicted with the Apr 2026 RU — confirming that the fix is now natively included in the RU itself.

    Official References: This walkthrough is based on MOS Note CPU58Critical Patch Update (CPU) Apr 2026 for Oracle Database Products — and the Oracle April 2026 CPU Advisory (481 new security patches across all Oracle product families, released 21-Apr-2026).
    Patch Availability Note: The Linux x86-64 DB RU 39034528 was delayed post the standard Apr 21 release. Per MOS Note CPU58, the Linux x86-64 ETA was 15-May-2026. Always check CPU58 for the latest availability before scheduling your patching window.
    Downtime Notice: Applying a DB Release Update on a Single Instance (non-RAC) database requires a full database shutdown. Plan for a maintenance window accordingly and notify application teams in advance.
    Terminology Note: Oracle’s quarterly security bundle is commonly referred to as the Critical Patch Update (CPU). However, Oracle now delivers database fixes as Release Updates (RU). The term CPU refers to the overall security advisory, while RU is the actual patch artifact you apply. Both terms are used interchangeably in the field — this post uses CPU to align with the official advisory title.

    Environment Details

    Parameter Value
    Database Type Single Instance Oracle 19c CDB
    OS Oracle Linux x86-64
    Starting Version 19.30.0.0.0 (Jan 2026 RU)
    Target Version 19.31.0.0.0 (Apr 2026 RU)
    Combo Patch 39062931
    DB RU Patch 39034528
    OJVM RU Patch 38906621
    Overlay Patch (conflict) 34672698 (ORA-00800 fix — rolled back, natively included in 19.31)
    OPatch Version 12.2.0.1.51
    ORACLE_HOME /u03/app/oracle/product/19.0.0.0/db_1

    Section 1: Pre-Patch Checks

    1.1 Verify current database version

    sqlplus / as sysdba
    SQL> select version_full from v$instance;
    
    VERSION_FULL
    -----------------
    19.30.0.0.0

    1.2 Check existing SQL patch history (dba_registry_sqlpatch)

    SQL> set lines 200
    SQL> col description format a50
    SQL> col status format a10
    SQL> col action_time format a30
    SQL> select patch_id, action, status, action_time, description
      2  from dba_registry_sqlpatch
      3  order by action_time;
    PATCH_ID ACTION STATUS ACTION_TIME DESCRIPTION
    29517242 APPLY SUCCESS 2022-10-18 02:14:37 Database Release Update 19.17
    38632161 APPLY SUCCESS 2026-01-21 04:45:06 Database Release Update 19.30.0.0.260120
    38523609 APPLY SUCCESS 2026-01-21 04:49:43 OJVM Release Update 19.30.0.0.260120

    1.3 Check component registry (dba_registry)

    SQL> col comp_name format a45
    SQL> select comp_id, comp_name, version, status from dba_registry order by comp_id;
    COMP_ID COMP_NAME VERSION STATUS
    APS OLAP Analytic Workspace 19.0.0.0.0 VALID
    CATALOG Oracle Database Catalog Views 19.0.0.0.0 VALID
    CATJAVA Oracle Database Java Packages 19.0.0.0.0 VALID
    CATPROC Oracle Database Packages and Types 19.0.0.0.0 VALID
    CONTEXT Oracle Text 19.0.0.0.0 VALID
    DV Oracle Database Vault 19.0.0.0.0 VALID
    JAVAVM JServer JAVA Virtual Machine 19.0.0.0.0 VALID
    OLS Oracle Label Security 19.0.0.0.0 VALID
    ORDIM Oracle Multimedia 19.0.0.0.0 VALID
    OWM Oracle Workspace Manager 19.0.0.0.0 VALID
    SDO Spatial 19.0.0.0.0 VALID
    XDB Oracle XML Database 19.0.0.0.0 VALID
    XML Oracle XDK 19.0.0.0.0 VALID
    XOQ Oracle OLAP API 19.0.0.0.0 VALID

    1.4 Check invalid objects (baseline)

    SQL> select count(*) from dba_objects where status = 'INVALID';
    
      COUNT(*)
    ----------
             8
    Note: Record the baseline invalid object count. Any new invalids introduced by patching should be investigated. After datapatch, Oracle-supplied objects marked temporarily INVALID will recompile automatically. Only non-Oracle invalids or objects still INVALID after 24 hours need attention.

    1.5 Check current OPatch inventory

    $ORACLE_HOME/OPatch/opatch lspatches
    Patch ID Description
    34672698 ORA-00800 SOFT EXTERNAL ERROR, ARGUMENTS [SET PRIORITY FAILED], [VKTM], DISM(16)
    38523609 OJVM RELEASE UPDATE: 19.30.0.0.260120
    38632161 Database Release Update: 19.30.0.0.260120
    29585399 OCW RELEASE UPDATE 19.3.0.0.0
    Note on OCW patch 29585399: This OCW (Oracle Clusterware) patch appears in the OPatch inventory even on Single Instance (non-RAC) environments. This is expected behaviour — it is included in the base Oracle Home installation and is not a sign of misconfiguration.

    Section 2: Upgrade OPatch

    Before applying any patch, always upgrade OPatch to the latest version. Download the latest OPatch (p6880880) from MOS and apply.

    # As oracle user
    cd $ORACLE_HOME
    mv OPatch OPatch_backup_$(date +%Y%m%d)
    unzip /<patch_staging_dir>/p6880880_190000_Linux-x86-64.zip -d $ORACLE_HOME
    
    # Verify new version
    $ORACLE_HOME/OPatch/opatch version
    # Expected: OPatch Version: 12.2.0.1.51

    Section 3: Stage the Patches

    # Create staging directory
    mkdir -p /<patch_staging_dir>/APR_2026
    
    # Unzip combo patch (contains both DB RU and OJVM RU)
    cd /<patch_staging_dir>/APR_2026
    unzip p39062931_190000_Linux-x86-64.zip
    
    # Verify extraction
    ls -la
    # Should show: 38906621/  39034528/

    Section 4: Conflict Check

    # Check DB RU for conflicts
    cd /<patch_staging_dir>/APR_2026/39034528
    $ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./
    
    # Check OJVM RU for conflicts
    cd /<patch_staging_dir>/APR_2026/38906621
    $ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./
    Real-World Conflict Scenario (ZOP-47): In this environment, the conflict check returned a conflict between overlay patch 34672698 and the new DB RU 39034528. This is because the ORA-00800 fix originally delivered by 34672698 is now natively included in the 19.31 RU. The resolution is to roll back the overlay patch first before applying the RU. This is a standard ZOP-47 scenario — the overlay is superseded, not incompatible.

    Section 5: System Space Check

    # Check available space before patching
    $ORACLE_HOME/OPatch/opatch prereq CheckSystemSpace -ph /<patch_staging_dir>/APR_2026/39034528
    $ORACLE_HOME/OPatch/opatch prereq CheckSystemSpace -ph /<patch_staging_dir>/APR_2026/38906621
    
    # Also verify OS-level space
    df -h $ORACLE_HOME
    df -h /tmp

    Section 6: Take Oracle Home TAR Backup

    # As oracle user — use relative path approach to avoid absolute path issues
    cd $(dirname $ORACLE_HOME)
    nohup tar -czf /<backup_dir>/ORACLE_HOME_backup_$(date +%Y%m%d).tar.gz \
      $(basename $ORACLE_HOME) > /tmp/oh_tar_backup.log 2>&1 &
    
    echo "Backup PID: $!"
    tail -f /tmp/oh_tar_backup.log
    Note: Using $(dirname $ORACLE_HOME) and $(basename $ORACLE_HOME) avoids embedding absolute paths in the tar archive, making restoration cleaner. The nohup & pattern allows the backup to run in background without being killed if the session disconnects.

    Section 7: RMAN Backup (Verify)

    # Verify a recent RMAN backup exists before proceeding
    rman target /
    RMAN> list backup summary;
    RMAN> exit;
    Important: Never apply a patch without confirming a valid RMAN backup. If the latest backup is stale, take a fresh full backup before proceeding. The Oracle Home TAR covers binaries — RMAN covers your data.

    Section 8: Shutdown Database and Listener

    sqlplus / as sysdba
    SQL> shutdown immediate;
    SQL> exit;
    
    lsnrctl stop LISTENER

    Section 9: Roll Back Overlay Patch 34672698

    Because patch 34672698 conflicts with the Apr 2026 RU (its fix is now natively included), we must roll it back first.

    export PATH=$ORACLE_HOME/OPatch:$PATH
    cd $ORACLE_HOME/OPatch
    opatch rollback -id 34672698
    
    # Verify rollback
    opatch lspatches
    # 34672698 should no longer appear

    Section 10: Apply DB RU 39034528

    cd /<patch_staging_dir>/APR_2026/39034528
    
    # Apply (interactive)
    $ORACLE_HOME/OPatch/opatch apply
    
    # OR apply silently (for automation/scripts)
    $ORACLE_HOME/OPatch/opatch apply -silent
    Note on oradism permissions: In some environments the DB RU apply (opatch apply) itself automatically changes oradism ownership from root:oinstall to oracle:oinstall with permissions 755. Always check after applying the DB RU before attempting any manual root steps:

    ls -la $ORACLE_HOME/bin/oradism

    If oradism is already showing oracle:755 post-apply, the manual chown/chmod steps are not required. Only apply root permission changes if oradism remains root-owned after the patch apply.

    Section 11: Apply OJVM RU 38906621

    cd /<patch_staging_dir>/APR_2026/38906621
    $ORACLE_HOME/OPatch/opatch apply

    11.1 Verify OPatch inventory after apply

    $ORACLE_HOME/OPatch/opatch lspatches
    Patch ID Description
    38906621 OJVM RELEASE UPDATE: 19.31.0.0.260421
    39034528 Database Release Update: 19.31.0.0.260421
    29585399 OCW RELEASE UPDATE 19.3.0.0.0

    Note: Patch 34672698 no longer appears — successfully rolled back. Patch 34672698’s fix is now natively bundled into the 19.31 RU.

    Section 12: Start Database and Listener

    sqlplus / as sysdba
    SQL> startup;
    SQL> alter pluggable database all open;
    SQL> show pdbs;
    SQL> exit;
    
    lsnrctl start LISTENER
    lsnrctl status LISTENER

    Section 13: Run Datapatch

    Datapatch applies the SQL-level changes for the new patches into the CDB and all open PDBs. This step is mandatory — OPatch alone does not apply SQL changes.

    cd $ORACLE_HOME/OPatch
    
    # Run datapatch and capture output to log
    ./datapatch -verbose | tee /tmp/datapatch_19_31.log
    Note on tee and exit codes: Using tee in a pipeline means the shell reports the exit code of tee, not datapatch. If you need to capture datapatch’s actual exit code for scripted validation, use: ./datapatch -verbose > /tmp/datapatch_19_31.log 2>&1; echo "Exit code: $?"

    Section 14: Post-Patch Validation

    14.1 Verify dba_registry_sqlpatch (post-patch)

    SQL> select patch_id, action, status, action_time, description
      2  from dba_registry_sqlpatch
      3  order by action_time;
    PATCH_ID ACTION STATUS ACTION_TIME DESCRIPTION
    34672698 ROLLBACK SUCCESS 17-MAY-26 09.12.43 AM ORA-00800 fix (overlay)
    38523609 ROLLBACK SUCCESS 17-MAY-26 09.15.02 AM OJVM Release Update 19.30
    39034528 APPLY SUCCESS 17-MAY-26 09.21.18 AM Database Release Update 19.31.0.0.260421
    38906621 APPLY SUCCESS 17-MAY-26 09.25.47 AM OJVM Release Update 19.31.0.0.260421

    14.2 Verify dba_registry (post-patch)

    SQL> select comp_id, comp_name, version, status from dba_registry order by comp_id;
    COMP_ID COMP_NAME VERSION STATUS
    APS OLAP Analytic Workspace 19.0.0.0.0 VALID
    CATALOG Oracle Database Catalog Views 19.0.0.0.0 VALID
    CATJAVA Oracle Database Java Packages 19.0.0.0.0 VALID
    CATPROC Oracle Database Packages and Types 19.0.0.0.0 VALID
    CONTEXT Oracle Text 19.0.0.0.0 VALID
    DV Oracle Database Vault 19.0.0.0.0 VALID
    JAVAVM JServer JAVA Virtual Machine 19.0.0.0.0 VALID
    OLS Oracle Label Security 19.0.0.0.0 VALID
    ORDIM Oracle Multimedia 19.0.0.0.0 VALID
    OWM Oracle Workspace Manager 19.0.0.0.0 VALID
    SDO Spatial 19.0.0.0.0 VALID
    XDB Oracle XML Database 19.0.0.0.0 VALID
    XML Oracle XDK 19.0.0.0.0 VALID
    XOQ Oracle OLAP API 19.0.0.0.0 VALID

    14.3 Verify version

    SQL> select version_full from v$instance;
    
    VERSION_FULL
    -----------------
    19.31.0.0.0

    14.4 Check invalid objects (post-patch)

    SQL> select count(*) from dba_objects where status = 'INVALID';
    
      COUNT(*)
    ----------
             8

    Invalid count unchanged from baseline — no new invalids introduced by patching.

    14.5 Run utlrp.sql (conditional)

    Note: Running utlrp.sql is conditional — only required if you see new invalid objects that were not in the baseline count, or if Oracle-supplied objects remain INVALID after 24 hours. It is not mandatory after every patch apply.
    -- Only run if new invalids are found
    sqlplus / as sysdba
    SQL> @?/rdbms/admin/utlrp.sql

    14.6 CDB-aware validation (cdb_registry_sqlpatch)

    -- Run from CDB$ROOT to validate across all containers
    SQL> col con_id format 99
    SQL> col patch_id format 9999999999
    SQL> col status format a10
    SQL> col action format a10
    SQL> select con_id, patch_id, action, status, description
      2  from cdb_registry_sqlpatch
      3  order by con_id, action_time;
    Note: cdb_registry_sqlpatch shows the patch apply status for each container (CDB$ROOT and all PDBs). Use this view to confirm datapatch applied successfully in all PDBs, not just the root container. All rows should show SUCCESS.

    Section 15: Rollback Procedure (If Required)

    If the patch causes critical issues post-apply, roll back in reverse order: OJVM first, then DB RU. This requires full database downtime.

    15.1 Shutdown the database and listener

    sqlplus / as sysdba
    SQL> shutdown immediate;
    SQL> exit;
    
    lsnrctl stop LISTENER

    15.2 Roll back the OJVM patch first

    export PATH=$ORACLE_HOME/OPatch:$PATH
    opatch rollback -id 38906621
    
    OPatch succeeded.

    15.3 Roll back the DB RU

    opatch rollback -id 39034528
    
    OPatch succeeded.

    15.4 Verify Jan 2026 RU is restored

    opatch lspatches
    -- Expect to see 38632161 and 38523609 back as active

    15.5 Start database, open PDBs, run datapatch

    sqlplus / as sysdba
    SQL> startup;
    SQL> alter pluggable database all open;
    SQL> show pdbs;
    SQL> exit;
    
    cd $ORACLE_HOME/OPatch
    ./datapatch -verbose
    Important: If the Oracle Home is severely corrupted, restore from the Oracle Home TAR backup taken in Section 6, then run datapatch. If database files are also affected, restore and recover from the RMAN backup first, then run datapatch. Never skip datapatch after a rollback — the SQL layer must be consistent with the binary layer.

    Section 16: Common Failures and Troubleshooting

    ZOP-47: Conflict with existing overlay patch

    Symptom: opatch prereq CheckConflictAgainstOHWithDetail reports a conflict between an installed overlay patch and the new RU.

    Cause: The overlay patch fix has been natively absorbed into the new RU.

    Resolution: Roll back the overlay patch using opatch rollback -id <patch_id>, then proceed with the RU apply. Confirm with Oracle Support SR if unsure whether the conflict is a ZOP-47 (self-resolving) or a genuine incompatibility.

    ZOP-40: Patch already installed

    Symptom: OPatch reports the patch is already present in the inventory.

    Cause: Patch was applied in a previous cycle or the inventory is stale.

    Resolution: Verify with opatch lspatches. If genuinely already installed, no action needed. If inventory is stale, run opatch lsinventory -detail to diagnose.

    Datapatch skipped PDB

    Symptom: Datapatch log shows one or more PDBs were skipped.

    Cause: PDB was not open during datapatch execution.

    Resolution: Open the skipped PDB (alter pluggable database <pdb_name> open;) and re-run datapatch. Confirm via cdb_registry_sqlpatch that all containers show SUCCESS.

    OPatch heap space error

    Symptom: OPatch fails with a Java heap space error during apply.

    Cause: Default JVM heap is insufficient for large patch sets.

    Resolution: Set JAVA_TOOL_OPTIONS="-Xmx512m" before running opatch. This is environment-specific and not universally required — only apply if the error is observed.

    OPatch inventory lock

    Symptom: OPatch fails reporting the inventory is locked by another process.

    Cause: A previous OPatch run did not exit cleanly, leaving a lock file.

    Resolution: Check for and remove the lock file: $ORACLE_HOME/.patch_storage/<lock_file>. Confirm no other OPatch process is running before removing.

    Key MOS References

    Document Description
    Doc ID CPU58 Critical Patch Update (CPU) Apr 2026 for Oracle Database Products — patch availability, known issues, delayed RU tracking
    Doc ID KB106822 Primary Note for Database Quarterly Release Updates — recommended reading before any RU apply
    Doc ID KB869205 Oracle Database 19c Apr 2026 RU Known Issues
    Doc ID KB137197 OJVM Conditional Rolling Install Details
    Doc ID 244241.1 OPatch Support for RAC Rolling Patches
    Doc ID 293369.1 OPatch Documentation List

    Disclaimer: All server names, hostnames, database names, and environment-specific paths in this post have been anonymized. Steps and outputs are based on real production experience adapted for general use. Always test in a non-production environment before applying patches to production systems.

    If you found this useful, connect with me on LinkedIn or explore more Oracle DBA scripts on my GitHub. More patching walkthroughs at syedanwarahmedoracle.blog.

    Share Your Experience

    Have you applied Oracle 19c CPU patches and hit a similar overlay patch conflict? Or found that a fix you applied months ago is now natively bundled into the next RU? Your real-world experiences help fellow DBAs tackle the same challenges. Drop a comment below — questions, observations, and feedback are always welcome.

  • P1 Incident: /dbbackup Filesystem 100% Full — How We Traced, Fixed, and Recovered Two Failed RMAN Backups in One Night

    🔴 Incident Overview

    Severity P1 — Two production database backups failed
    Environment Oracle 19c (19.30) on Linux x86-64
    Backup Tool RMAN with Recovery Catalog
    Backup Volume /dbbackup — 1TB LVM filesystem
    Databases Affected DBPRO01 (12:30 failure), DBPRO02 (22:30 failure)
    Total Space Recovered ~492G (disk went from 100% → 52%)

    1. The Alerts — Two Failures, Same Root Cause

    It started with an RMAN failure at 22:30. The backup script for DBPRO02 fired on schedule and died within 2 minutes. The RMAN log told the story clearly:

    RMAN-03009: failure of backup command on c4 channel at 22:32:13
    ORA-19502: write error on file "/dbbackup/DBPRC02/rman/DiffInc_DBPRC02_4u4mi8bf"
    ORA-27072: File I/O error
    Additional information: 4
    

    Three more channels followed — c1, c2, c3 — all crashing at exactly 22:32:48. When multiple channels fail simultaneously at the same timestamp, it almost always means one thing: the destination filesystem just hit 100%.

    A quick check confirmed it:

    $ df -hP /dbbackup
    Filesystem                        Size  Used Avail Use%
    /dev/mapper/orabkupvg-orabkuplv1 1023G 1020G  3.3G 100%
    

    1TB volume. 3.3G free. Completely full.

    What we did not know yet — digging into backup history would reveal that DBPRO01 had already failed at 12:30 that same day for the same reason, 10 hours earlier. Two databases unprotected on the same night.


    2. The Investigation — Folder by Folder

    The first step was understanding what was consuming the disk. One command gave us the top-level picture:

    $ du -sh /dbbackup/*
    744G    DBPRC01
    152G    ColdBackup_11April2026
     41G    DBPRC02
     26G    DBPRC03
     15G    DBPRO01
    7.7G    JAN2026_CPU
    7.7G    OCT2025_CPU
    5.3G    infra_arch
    

    744G inside DBPRC01 alone — 73% of the entire disk. That was our primary suspect.

    Drilling into DBPRC01

    $ du -sh /dbbackup/DBPRC01/rman/* | sort -rh | head -10
    7.5G    DiffInc_DBPRC01_fl4lumij
    7.5G    DiffInc_DBPRC01_eg4lrnh5
    7.5G    DiffInc_DBPRC01_b44lc7uh
    ...
    

    Every single file was a DiffInc_ or ArchivelogAll_ backup piece. No variety. No cleanup. Just backup after backup piling up.

    $ ls /dbbackup/DBPRC01/rman/ | wc -l
    1522
    

    1,522 backup pieces. We checked the oldest and newest:

    Oldest file on disk:  2022-05-07
    Newest file on disk:  2026-04-25
    

    Four years of backup files on disk — or so we thought.


    3. The RMAN Investigation — Where Things Got Interesting

    We connected RMAN to the database and ran the retention check:

    RMAN> SHOW RETENTION POLICY;
    CONFIGURE RETENTION POLICY TO REDUNDANCY 30;
    

    REDUNDANCY 30. This tells RMAN to keep the last 30 complete backup copies of every datafile before considering anything obsolete.

    Next logical step — check what RMAN considers obsolete:

    RMAN> REPORT OBSOLETE;
    no obsolete backups found
    

    Nothing? With 1,522 files on disk?

    We ran CROSSCHECK BACKUP — all 1,693 objects came back AVAILABLE. Then we checked the actual date range RMAN was tracking from the database control file:

    SELECT TO_CHAR(MIN(completion_time),'DD-MON-YYYY') oldest,
           TO_CHAR(MAX(completion_time),'DD-MON-YYYY') newest,
           COUNT(*) total_pieces
    FROM v$backup_piece_details
    WHERE status = 'A';
    
    OLDEST          NEWEST          TOTAL_PIECES
    03-DEC-2025     25-APR-2026     1541
    

    The control file only tracks pieces from December 2025 onwards — about 5 months. The 2022/2023 files seen on disk were old directories and scripts, not backup pieces. All 1,541 current pieces were legitimate and RMAN considered every one of them necessary under REDUNDANCY 30.

    This was the key insight: RMAN was not broken. The retention policy itself was the problem.


    4. Root Cause — The Architecture Trap

    The deeper investigation revealed something unexpected. Looking at the actual RMAN backup script:

    connect target rman/password@DBPRO01
    ...
    format '/dbbackup/DBPRC01/rman/DiffInc_%d_%u'
    (database);
    ...
    delete obsolete;
    

    DBPRO01 (the production database) was backing up INTO the DBPRC01 directory. The directory names suggested one database but contained another database’s backups entirely. The naming convention was PRO to PRC — production database backups stored in the production-copy directory.

    This pattern existed for all three database pairs on the server. Each production database backed up into its corresponding copy directory.

    The delete obsolete command was in the script — but with REDUNDANCY 30 and weekly Level 0 backups, obsolete only kicks in after 30 complete Level 0 cycles. That is 30 weeks = 7.5 months of retention. Since the current tracking window was only 5 months, delete obsolete ran every night and found absolutely nothing to delete.

    The math:

    Retention policy REDUNDANCY 30
    Level 0 frequency Weekly (Sundays)
    Effective retention period ~30 weeks / 7.5 months
    Backup tracking since December 2025 (~5 months)
    Result delete obsolete finds nothing — ever
    Daily backup size ~7–7.5G per run
    Total accumulated 744G

    Adding fuel to the fire — the patching activity on April 18 triggered an extra Level 0 backup, followed by the regular Sunday Level 0 on April 19. Two large Level 0 runs (~27G each) within 24 hours wrote the final ~54G that pushed the disk over the edge.


    5. Secondary Findings During Investigation

    OCT2025 CPU Patch Artifacts (7.7G)

    The October 2025 CPU patch files (zip archives + extracted directories) were still sitting in /dbbackup/OCT2025_CPU/. A quick OPatch check confirmed the database had since been patched to 19.30 (January 2026 RU) — the October 2025 patches were fully superseded and rolled back from inventory. Safe to delete immediately.

    $ $ORACLE_HOME/OPatch/opatch lsinventory | grep -E "38291812|38194382"
    # Empty — neither Oct 2025 patch in inventory anymore
    

    5-Year-Old Pre-Migration Export Dumps

    Three directories contained Oracle 11.2.0.4 export dumps from January–March 2021 — taken before the migration to 19c. With the database now running 19.30, these had zero recovery value but occupied ~14G collectively. Flagged for manager approval before deletion.

    Recovery Catalog Version Mismatch

    The original RMAN log flagged this warning:

    PL/SQL package RMAN.DBMS_RCVCAT version 19.11.00.00 in RCVCAT database is not current
    PL/SQL package RMAN.DBMS_RCVMAN version 19.11.00.00 in RCVCAT database is not current
    

    The recovery catalog is running 19.11 packages while the RMAN client is now 19.30. Non-critical tonight but requires UPGRADE CATALOG in the next maintenance window.


    6. The Fix — Emergency Space Recovery

    With management approval obtained, we executed a time-based delete — keeping the last 30 days of backups and removing everything older:

    RMAN> DELETE NOPROMPT BACKUP COMPLETED BEFORE 'SYSDATE-30';
    

    This command does three things atomically:

    1. Queries catalog/controlfile for all pieces completed before the cutoff date
    2. Deletes the physical files from disk
    3. Removes the records from RMAN catalog — no orphaned entries, no catalog drift

    The output scrolled for several minutes:

    deleted backup piece
    backup piece handle=/dbbackup/DBPRC01/rman/DiffInc1_DBPRC01_6u4jaaih ...
    deleted backup piece
    backup piece handle=/dbbackup/DBPRC01/rman/ArchivelogAll_DBPRC01_784jab6o ...
    ...
    Deleted 1072 objects
    

    1,072 backup pieces deleted. Catalog updated. Disk checked:

    BEFORE:  Used 1020G  Avail 3.3G  (100%)
    AFTER:   Used  536G  Avail 488G   (53%)
    

    Then the OCT2025_CPU directory was removed:

    $ rm -rf /dbbackup/OCT2025_CPU/
    $ df -hP /dbbackup
    Used 528G  Avail 495G  (52%)
    

    Final result: 495G free. Disk at 52%.

    Both failed backups were re-submitted immediately and ran successfully in parallel:

    $ nohup sh /opt/oracle/scripts/rman/rman_backup_DBPRO01.sh &
    $ nohup sh /opt/oracle/scripts/rman/rman_backup_DBPRO02.sh &
    
    $ jobs -l
    [1] Running   nohup sh ...rman_backup_DBPRO01.sh &
    [2] Running   nohup sh ...rman_backup_DBPRO02.sh &
    

    7. Incident Timeline

    12:30 DBPRO01 Level 0 backup fails — ORA-19502/ORA-27072 (disk full)
    22:30 DBPRO02 Level 0 backup fails — same errors, all 4 channels
    23:08 Investigation begins — df -hP /dbbackup confirms 100% full
    23:15 DBPRC01 directory identified as 744G consumer
    23:25 RMAN connected — REDUNDANCY 30 discovered
    23:35 Architecture confirmed — PRO databases backing up into PRC directories
    23:45 Root cause confirmed — 7.5-month retention, delete obsolete finds nothing
    23:50 DELETE BACKUP COMPLETED BEFORE SYSDATE-30 executed
    23:51 1,072 pieces deleted — disk drops to 53%
    23:55 OCT2025_CPU removed — disk at 52%, 495G free
    00:00 Both backup jobs re-submitted and running successfully

    8. Permanent Fix Recommendations

    Fix 1 — Change Retention Policy to RECOVERY WINDOW

    RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 14 DAYS;
    

    REDUNDANCY 30 with weekly Level 0s means 7.5 months of retention — far beyond what any production SLA requires. A 14-day recovery window keeps 2 weeks of backups regardless of backup frequency, and delete obsolete will actually find and remove old pieces going forward.

    Fix 2 — Add Pre-Backup Space Check to Cron Script

    #!/bin/bash
    BACKUP_FS="/dbbackup"
    THRESHOLD=20
    
    AVAIL_PCT=$(df -hP $BACKUP_FS | awk 'NR==2 {gsub(/%/,""); print 100-$5}')
    
    if [ "$AVAIL_PCT" -lt "$THRESHOLD" ]; then
      echo "ABORT: $BACKUP_FS is ${AVAIL_PCT}% free — below ${THRESHOLD}% threshold" \
        | mailx -s "BACKUP ABORTED: Low space on $BACKUP_FS" $MAILTO
      exit 1
    fi
    

    A failing backup that writes 3G before dying is worse than a backup that never starts — it wastes the last 3G of free space and leaves partial pieces on disk.

    Fix 3 — Upgrade the Recovery Catalog

    RMAN> CONNECT TARGET /
    RMAN> CONNECT CATALOG rman/password@rmancat
    RMAN> UPGRADE CATALOG;
    RMAN> UPGRADE CATALOG;   -- run twice as prompted
    

    The catalog is 2 major patch levels behind the RMAN client. Some catalog-dependent operations will start failing if left unaddressed.

    Fix 4 — Filesystem Monitoring Alert

    The FRA check scripts already email on FRA usage above 80%. The same pattern should exist for /dbbackup. A simple cron entry checking disk usage every hour with alert at 80% would have caught this days before the disk hit 100%.


    9. Key Takeaways for Oracle DBAs

    REDUNDANCY N is not always safer than RECOVERY WINDOW. REDUNDANCY 30 with weekly Level 0 backups means 7.5 months of retention — likely far beyond your RPO requirement and a silent space accumulator.

    • Always verify what delete obsolete actually deletes. If it finds nothing to delete every single night, that is a warning sign — not reassurance.
    • Check backup naming conventions carefully. When a directory named DBPRC01 contains DBPRO01 backups, retention policies applied to the wrong database RMAN configuration control the cleanup behavior.
    • Patching days generate oversized backups. A Level 0 taken manually on patch day plus the regular Sunday Level 0 the next day equals 2x the normal space consumption in 24 hours. Ensure extra headroom exists going into patch windows.
    • Use DELETE BACKUP COMPLETED BEFORE SYSDATE-N for emergency cleanup — not OS-level rm. RMAN deletes atomically update both the physical files and the catalog, preventing expired/orphaned piece confusion later.
    • Never use rm on RMAN backup pieces directly unless you follow up with CROSSCHECK BACKUP and DELETE EXPIRED BACKUP to sync the catalog.

    10. Commands Reference — Quick Cheat Sheet

    -- Check retention policy
    RMAN> SHOW RETENTION POLICY;
    
    -- Preview what would be deleted (dry run)
    RMAN> REPORT OBSOLETE;
    RMAN> LIST BACKUP COMPLETED BEFORE 'SYSDATE-30';
    
    -- Emergency cleanup — delete pieces older than 30 days
    RMAN> DELETE NOPROMPT BACKUP COMPLETED BEFORE 'SYSDATE-30';
    
    -- Standard cleanup based on retention policy
    RMAN> DELETE NOPROMPT OBSOLETE;
    
    -- Sync catalog after any OS-level file operations
    RMAN> CROSSCHECK BACKUP;
    RMAN> DELETE NOPROMPT EXPIRED BACKUP;
    
    -- Change to time-based retention (recommended)
    RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 14 DAYS;
    
    -- Check backup piece date range in control file
    SELECT TO_CHAR(MIN(completion_time),'DD-MON-YYYY') oldest,
           TO_CHAR(MAX(completion_time),'DD-MON-YYYY') newest,
           COUNT(*) total_pieces
    FROM v$backup_piece_details
    WHERE status = 'A';
    
    -- Check backup history
    SELECT session_key, input_type, status,
           TO_CHAR(start_time,'YYYY-MM-DD HH24:MI:SS') start_time,
           output_bytes_display, time_taken_display
    FROM v$rman_backup_job_details
    ORDER BY start_time DESC;
    

    Conclusion

    What appeared to be a simple disk full incident turned out to involve a multi-database backup architecture, a misconfigured retention policy, and a cleanup mechanism that was technically running correctly but never finding anything to clean. The fix itself — one RMAN command — took under 5 minutes. The real work was the systematic investigation to understand exactly what was safe to delete and why.

    That is Oracle DBA work in a nutshell: the fix is often simple; understanding why it is safe to run is the real job.


    If you found this useful, connect with me on LinkedIn or explore more Oracle DBA scripts on my GitHub. More incident walkthroughs at syedanwarahmedoracle.blog.

  • Rethinking UNDO Tablespace Monitoring: When 97% Usage Is NOT a Problem

    A critical alert was triggered in production showing UNDO tablespace usage at 97% with near-zero free space. At first glance, this looked like an immediate outage risk. However, deeper analysis revealed a completely different story.


    Initial Observations

    Standard checks showed Tablespace UNDOTBS1 with a total size of 16 GB and only ~2 MB free space. This strongly suggested the UNDO tablespace was nearly full. However, deeper analysis revealed a different story.


    The Turning Point

    Instead of relying on DBA_FREE_SPACE, we analyzed UNDO internals using extent status:

    SELECT status,
           ROUND(SUM(bytes)/1024/1024,2) MB
    FROM dba_undo_extents
    GROUP BY status;

    Results showed: ACTIVE ~0 MB, UNEXPIRED ~7.3 GB, EXPIRED ~8.6 GB.


    Key Insight

    EXPIRED UNDO extents are fully reusable by Oracle. This means ~55% of the tablespace was immediately reusable and no actual space pressure existed.


    Real Usage Calculation

    SELECT ROUND(
           SUM(CASE WHEN status IN ('ACTIVE','UNEXPIRED') THEN bytes ELSE 0 END)
           / SUM(bytes) * 100, 2) actual_used_pct
    FROM dba_undo_extents;

    Actual usage was only ~45% — not 97%.


    Root Cause of the False Alert

    The monitoring system was using DBA_FREE_SPACE which does not include reusable EXPIRED extents. This is incorrect for UNDO tablespaces and leads to false critical alerts, unnecessary escalations, and wasted DBA effort.


    The Correct Monitoring Approach

    SELECT tablespace_name,
           ROUND(SUM(CASE WHEN status IN ('ACTIVE','UNEXPIRED') THEN bytes ELSE 0 END)
    / SUM(bytes) * 100, 2) actual_used_pct
    FROM dba_undo_extents
    GROUP BY tablespace_name;

    Only alert when actual usage exceeds 90% AND long-running transactions exist. The full script is available on GitLab: gitlab.com/sdanwarahmed/oracle-dba-scripts


    Key Takeaways

    • UNDO is not a regular tablespace — it has its own lifecycle
    • DBA_FREE_SPACE is misleading for UNDO monitoring
    • Always analyze UNDO extents by status: ACTIVE, UNEXPIRED, EXPIRED
    • Build context-aware monitoring that aligns with Oracle internals
    • Not all “97% full” alerts are real problems — sometimes it’s just the wrong metric

    Written by Syed Anwar Ahmed — Oracle Apps DBA with 11 years of production experience.
    Connect: sdanwarahmed@gmail.com  |  LinkedIn  |  GitLab

  • Rethinking UNDO Tablespace Monitoring in Multitenant Environments: A Case Study on APPS_UNDOTS1

    Introduction

    A production alert indicating UNDO tablespace usage at ~99% is typically treated as a critical issue. However, in Oracle Multitenant environments with local UNDO enabled, this metric can be misleading when evaluated using traditional tablespace monitoring techniques.

    This article presents a real-world scenario involving the PDB-level UNDO tablespace APPS_UNDOTS1, where utilization reached ~99%, yet no actual resource contention existed.


    The Incident

    An alert was triggered showing:

    Tablespace              TOTAL_MB     USED_MB     FREE_MB PCT_USED
    -------------------- ----------- ----------- ----------- --------
    APPS_UNDOTS1 28,863.25 28,681.56 181.69 99.37

    At first glance, this indicated:

    • UNDO nearly full
    • Minimal free space
    • Risk of ORA-30036 / ORA-01555

    Environment Context

    This issue occurred in a Multitenant environment with Local UNDO enabled:

    • UNDO tablespace: APPS_UNDOTS1
    • Scope: PDB-level
    • Each PDB maintains its own UNDO

    UNDO behavior must always be analyzed at the PDB level, not just at the CDB level.


    Investigation Approach

    Rather than relying on % used, a state-based analysis was performed.


    Step 1: Active Transactions

    SELECT COUNT(*) FROM v$transaction;

    Observation:

    • Minimal active transactions
    • No ongoing workload pressure

    Step 2: UNDO Extent Analysis

    SELECT status, SUM(bytes)/1024/1024 MB
    FROM dba_undo_extents
    GROUP BY status;

    Key Observation

    STATUS        MB         PCT
    ----------- -------- -------
    ACTIVE ~3 MB ~0.01%
    UNEXPIRED ~51 MB ~0.18%
    EXPIRED ~28 GB ~99.81%

    💡 Critical Insight

    ~99.81% of UNDO (APPS_UNDOTS1) was EXPIRED and fully reusable

    This means:

    • No real space pressure
    • No transaction risk
    • Tablespace is effectively available

    Understanding UNDO Behavior

    UNDO extents follow a lifecycle:

    ACTIVE → UNEXPIRED → EXPIRED → REUSED
    StateDescriptionReusable
    ACTIVEUsed by active transactions
    UNEXPIREDRetained for consistency⚠️
    EXPIREDNo longer needed

    UNDO extents are not physically freed but logically reused by Oracle based on demand.


    Why the Alert Was Misleading

    The alert was based on:

    SELECT * FROM dba_free_space;

    Limitation

    • Shows only physically free space
    • Does not reflect reusable UNDO
    • Ignores Oracle’s internal reuse mechanism

    Root Cause

    A long-running concurrent request:

    • Ran for ~30 hours
    • Performed heavy DELETE operations
    • Generated significant UNDO

    During execution:

    • UNDO reached ~99% → real pressure

    After completion:

    • UNDO became EXPIRED
    • Space became reusable
    • Alert persisted → false positive

    When UNDO Is Actually a Problem

    ACTIVE      > 20–30%
    UNEXPIRED very high
    EXPIRED very low

    UNDO becomes a real issue only when both EXPIRED and UNEXPIRED extents are exhausted and no reusable space remains.


    Recommended Monitoring Approach

    WITH undo AS (
    SELECT status, SUM(bytes)/1024/1024 mb
    FROM dba_undo_extents
    GROUP BY status
    ),
    total AS (
    SELECT SUM(mb) total_mb FROM undo
    )
    SELECT
    u.status,
    ROUND(u.mb,2) AS mb,
    ROUND((u.mb / t.total_mb) * 100, 2) AS pct
    FROM undo u, total t;

    Improved Alert Logic

    • ACTIVE > 20% → Critical
    • UNEXPIRED > 80% → Warning
    • Otherwise → Normal

    Key Takeaways

    • UNDO is state-driven, not space-driven
    • EXPIRED undo = reusable capacity
    • % used is misleading
    • Always analyze at PDB level in multitenant setups
    • Monitoring must align with Oracle internals

    Conclusion

    UNDO tablespace monitoring must evolve from:

    ❌ Static space-based metrics
    ➡️
    ✅ Dynamic lifecycle-based analysis

    A tablespace showing 99% utilization can still be completely healthy if most of its extents are reusable.


    Level Insight

    “UNDO is not a storage problem — it is a lifecycle problem. The difference between false alarms and accurate diagnosis lies in understanding how Oracle transitions undo extents.”


    Final Thought

    👉 Don’t ask:
    “How full is UNDO?”

    👉 Ask:
    “How much of it is actually in use?”


    Author

    Syed Anwar Ahmed
    Oracle Apps DBA | Oracle EBS | Performance & Troubleshooting

  • When ADOP Remembers Too Much: Fixing Patch Failures Caused by Stale Metadata in Oracle EBS

    During an Oracle E-Business Suite ADOP patching cycle in a multi-node environment, the apply phase failed on one node while completing successfully on others. Despite retries — including downtime mode — the issue persisted, pointing to a deeper inconsistency within the patching framework.


    Symptoms Observed

    • ADOP session status: FAILED
    • Patch applied successfully on some nodes, failed on admin node
    • Repeated failures even with restart=no, abandon=yes, and downtime mode
    • No immediate actionable error from standard logs

    Timeline of Events

    T0 -- Patch execution initiated (ADOP apply phase)
    T1 -- Failure observed on admin node
    T2 -- Retry using downtime mode -- Failure persists
    T3 -- ADOP session review shows inconsistent state
    T4 -- Internal metadata tables analyzed
    T5 -- Cleanup performed (tables + restart directory)
    T6 -- Patch re-executed -- Success across all nodes

    Investigation

    Step 1: Check ADOP Session State

    Query the ADOP session status to understand the current state across all nodes:

    -- Check current ADOP session status
    SELECT session_id, node_name, phase, status,
           start_date, end_date
    FROM applsys.ad_adop_sessions
    ORDER BY start_date DESC;
    
    -- Check apply phase status per node
    SELECT s.session_id, n.node_name, p.phase_code,
           p.status, p.start_date, p.end_date
    FROM applsys.ad_adop_sessions s,
         applsys.ad_adop_session_phases p,
         applsys.fnd_nodes n
    WHERE s.session_id = p.session_id
    AND p.node_id = n.node_id
    ORDER BY p.start_date DESC;

    The existing session showed status FAILED with the apply phase partially completed — a clear indicator of inconsistent execution state across nodes.

    Step 2: Check adalldefaults.txt

    Reviewed the defaults file for any relevant configuration:

    cat $APPL_TOP/admin/$TWO_TASK/adalldefaults.txt | grep -i missing
    -- Key parameter found:
    -- MISSING_TRANSLATED_VERSION = No

    Modifying and retrying with this parameter had no impact, confirming the issue was not translation-related.

    Step 3: Check Install Processes Table

    -- Check for stale entries in FND_INSTALL_PROCESSES
    SELECT COUNT(*) FROM applsys.fnd_install_processes;
    
    -- View stale entries in detail
    SELECT process_status, process_name, last_update_date
    FROM applsys.fnd_install_processes
    ORDER BY last_update_date DESC;
    
    -- Check AD_DEFERRED_JOBS
    SELECT COUNT(*) FROM applsys.ad_deferred_jobs;
    SELECT * FROM applsys.ad_deferred_jobs;

    Observation: FND_INSTALL_PROCESSES contained stale entries from the failed session. AD_DEFERRED_JOBS was empty.


    Root Cause

    The failure was caused by stale and inconsistent ADOP metadata tables — specifically APPLSYS.FND_INSTALL_PROCESSES and APPLSYS.AD_DEFERRED_JOBS. ADOP internally relies on these tables to track patch progress checkpoints, deferred job execution, and restart state management. When these tables retain entries from failed or incomplete sessions, ADOP assumes an incorrect execution state, leading to patch reconciliation failure, apply phase breakdown, and node-level inconsistencies.


    Resolution Steps

    Step 1: Backup Critical Tables

    -- Always backup before any cleanup
    CREATE TABLE applsys.fnd_install_processes_bak AS
    SELECT * FROM applsys.fnd_install_processes;
    
    CREATE TABLE applsys.ad_deferred_jobs_bak AS
    SELECT * FROM applsys.ad_deferred_jobs;
    
    -- Verify backups
    SELECT COUNT(*) FROM applsys.fnd_install_processes_bak;
    SELECT COUNT(*) FROM applsys.ad_deferred_jobs_bak;

    Step 2: Drop Stale Metadata Tables

    Dropping these tables forces ADOP to rebuild clean metadata during the next run:

    DROP TABLE applsys.fnd_install_processes;
    DROP TABLE applsys.ad_deferred_jobs;

    Step 3: Reset the Restart Directory

    The restart directory can silently preserve failure states. Back it up and create a fresh one:

    cd $APPL_TOP/admin/$TWO_TASK
    
    -- Backup existing restart directory
    mv restart restart_bkp_$(date +%Y%m%d)
    
    -- Create fresh restart directory
    mkdir restart
    
    -- Verify
    ls -la | grep restart

    Step 4: Re-run the Patch

    adop phase=apply \
         patches=<patch_id> \
         restart=no \
         abandon=yes \
         apply_mode=downtime

    The patch completed successfully across all nodes after the metadata cleanup.


    Before vs After

    ComponentBefore FixAfter Fix
    ADOP SessionFailedSuccessful
    Node ConsistencyPartialFull
    Restart BehaviorStuckClean
    Patch ExecutionIncompleteCompleted

    Key Takeaways

    • ADOP is state-driven — even when logs appear clean, internal metadata drives execution decisions
    • Partial success is a clue — if some nodes succeed and one fails, focus on local metadata, not the patch itself
    • The restart directory matters — it can silently preserve failure states and must be validated before retrying
    • Downtime mode is not a fix-all — even in downtime, ADOP still reads metadata tables; corruption persists unless cleaned
    • Always backup before cleanup — never drop tables without creating a backup first

    When NOT to Use This Approach

    Avoid applying this fix if the issue is caused by missing database patches (ETCC warnings), file system or permission issues, incorrect patch sequencing, or environment misconfiguration. Always validate the root cause before performing any metadata cleanup.


    This scenario highlights a subtle but critical behavior in ADOP — sometimes patch failures are not caused by the patch itself, but by what the system remembers about past attempts. By resetting stale metadata, we allow ADOP to re-evaluate the environment cleanly, leading to successful execution.

    Have questions or faced a similar issue? Reach out at sdanwarahmed@gmail.com.

  • Oracle EBS Performance Issue: Inactive Forms Session Holding TX Locks (RCA and Resolution)

    In Oracle E-Business Suite (EBS) environments, performance issues are often attributed to high workload or system resource constraints. However, some of the most critical slowdowns originate from less obvious sources — inactive sessions holding uncommitted transactions. This post walks through a real-world production incident where an inactive Oracle Forms session caused cascading blocking across multiple users due to TX row-level locks.


    Observed Symptoms

    • Oracle Forms screens becoming unresponsive in Order Management and Shipping modules
    • Concurrent programs stuck in running state
    • Increased database wait event: enq: TX - row lock contention

    Investigation

    Analysis of v$session, v$transaction, and dba_wait_chains revealed a single inactive Oracle Forms session (frmweb) holding an active transaction with multiple downstream sessions waiting on TX row lock contention.

    -- Identify blocking sessions
    SELECT blocking_session, sid, wait_class, event
    FROM v$session
    WHERE blocking_session IS NOT NULL;
    
    -- Detect inactive sessions with active transactions
    SELECT s.sid, s.serial#, s.status, s.program,
           s.username, t.start_time,
           ROUND(s.last_call_et/3600,2) hrs_inactive
    FROM v$session s, v$transaction t
    WHERE s.saddr = t.ses_addr
    AND s.status = 'INACTIVE'
    ORDER BY hrs_inactive DESC;
    
    -- Analyze full wait chain
    SELECT * FROM dba_wait_chains;

    The root blocking session showed STATUS = INACTIVE and EVENT = 'SQL*Net message from client' but had an active transaction in v$transaction — confirming it was idle at the application level but actively holding locks at the database level.


    Root Cause

    An Oracle Forms session executed a SELECT ... FOR UPDATE NOWAIT on WSH_DELIVERY_DETAILS, then became idle without committing or rolling back. This held exclusive row locks that blocked other sessions attempting to access the same rows, creating a cascading blocking chain.

    -- The problematic SQL pattern
    SELECT *
    FROM WSH_DELIVERY_DETAILS
    WHERE ROWID = :B1
    FOR UPDATE NOWAIT;

    Resolution

    The root blocking session was identified, verified to have no active business transactions, approvals were obtained, and the session was terminated:

    -- Kill blocking session (only after full validation and approval)
    ALTER SYSTEM KILL SESSION 'SID,SERIAL#' IMMEDIATE;

    Locks were released immediately, the blocking chain resolved, and application responsiveness was restored.


    Preventive Measures

    • Implement idle session timeout policies
    • Educate users on proper transaction handling in Oracle Forms
    • Review custom code using FOR UPDATE — keep transactions short and commit promptly
    • Monitor long-running and idle transactions proactively

    Key Takeaways

    • An inactive session can still hold active transactions and critical locks
    • Always identify the root blocker — intermediate sessions are symptoms, not the cause
    • Application-level inactivity does not mean database-level inactivity
    • In Oracle EBS, the most disruptive issues are often caused by inactive sessions holding uncommitted transactions

    Written by Syed Anwar Ahmed — Oracle Apps DBA with 11 years of production experience.
    Connect: sdanwarahmed@gmail.com  |  LinkedIn

  • Oracle Database Session Spike Mystery: When Connection Pools Collide

    In a production Oracle database environment, a sudden spike in session count exceeding 1000+ sessions triggered alerts and concern. Interestingly, the system recovered automatically without any database-level intervention. At first glance this appeared to be a database issue — but deeper analysis revealed a different story.


    The Incident

    An automated alert reported session count exceeding threshold (1000+), with the majority in INACTIVE state from middleware connection pool accounts. Despite the spike there were no blocking sessions, no performance degradation, and no database errors.

    -- Quick session count check
    SELECT COUNT(*) FROM gv$session;
    -- Result: 216 (already returning to normal)
    
    -- Session breakdown by status
    SELECT status, COUNT(*) cnt
    FROM gv$session
    GROUP BY status
    ORDER BY cnt DESC;

    Root Cause

    Multiple production mid-tier servers simultaneously created new connection pools at the same time window. New pools created new database sessions while existing pools kept their sessions alive (INACTIVE) pending graceful termination — resulting in a temporary overlap:

    Old Sessions (Inactive) + New Sessions (Active) = Session Surge

    As older pools were cleaned up, inactive sessions terminated automatically and the count returned to baseline. This was not a database problem — it was connection pool lifecycle behavior in the mid-tier layer.


    Recommendations

    • Stagger connection pool refresh across mid-tier servers to avoid simultaneous spikes
    • Monitor inactive session trends to detect abnormal accumulation early
    • Configure appropriate idle timeout, maximum pool size, and session reuse settings

    Key Takeaways

    • Not all session spikes are database problems — check middleware behavior first
    • High session count does not necessarily indicate database stress
    • Transient issues still require analysis as they reveal architectural inefficiencies
    • Database alerts can originate from upstream connection management behavior

    Written by Syed Anwar Ahmed — Oracle Apps DBA with 11 years of production experience.
    Connect: sdanwarahmed@gmail.com  |  LinkedIn

  • Oracle Alert Log Deep Dive: Interpreting ORA-00031 and Redo Log Pressure Without Production Changes

    Production alert logs often contain messages that appear critical but are, in reality, indicators of normal database behavior under load. This article presents a real-world Oracle database investigation where repeated ORA-00031: session marked for kill messages and redo log allocation waits were observed. Using read-only analysis techniques, we demonstrate how to distinguish between expected behavior and actionable signals without performing any intrusive changes.


    Observed Symptoms

    ORA-00031: session marked for kill
    Thread 1 cannot allocate new log
    Private strand flush not complete

    Phase 1: Interpreting ORA-00031 Correctly

    ORA-00031 is generated when sessions are terminated using ALTER SYSTEM KILL SESSION. Oracle marks the session for cleanup and handles it asynchronously via background processes. This is not an error — it is a confirmation of successful session termination.


    Phase 2: Identifying the True Performance Signal

    The more critical messages were Thread 1 cannot allocate new log and Private strand flush not complete. These occur when LGWR attempts a redo log switch but active redo strands are still flushing. Oracle briefly delays the log switch until consistency is ensured — this is a redo allocation wait, typically seen under sustained transactional load.


    Phase 3: Evidence-Based Analysis (Read-Only)

    Redo switch frequency was analyzed to validate system behavior:

    SELECT
        TO_CHAR(TRUNC(first_time, 'HH24'), 'YYYY-MM-DD HH24:MI') AS switch_hour,
        COUNT(*) AS switches
    FROM v$log_history
    WHERE first_time > SYSDATE - 1
    GROUP BY TRUNC(first_time, 'HH24')
    ORDER BY 1;

    Findings

    MetricObservation
    Average Switch Rate5-7 per hour
    Peak Rate8-10 per hour during business hours
    Off-Peak Rate1-3 per hour

    A direct correlation was observed between log switch spikes and high DML activity, confirming a cause-effect relationship rather than random errors.


    Why No Changes Were Made

    In this scenario, production environment restrictions were in place, no user impact was observed, and the behavior was transient and self-resolving. A monitoring-first approach was adopted instead of immediate tuning.


    Recommendations

    • Continuously monitor redo switch frequency during peak windows
    • Use collected data to justify future redo log sizing via change management
    • Avoid unnecessary intervention when behavior is transient and non-impacting
    • Distinguish informational alert log messages from actionable errors

    Key Takeaways

    • ORA-00031 is expected and harmless — it confirms session termination
    • Redo allocation waits are transient under sustained load
    • Proper analysis prevents unnecessary production intervention
    • Not all alert log warnings indicate failure — some are early signals of workload growth
    • The goal is not to eliminate every alert, but to understand which ones matter

    Written by Syed Anwar Ahmed — Oracle Apps DBA with 11 years of production experience.
    Connect: sdanwarahmed@gmail.com  |  LinkedIn