By Navdeep Saini, Senior Director, Cloud and Technology, Centroid
If you need to move a pluggable database (PDB) to Oracle AI Database 26ai but can’t afford a long outage to get there, Oracle Zero Downtime Migration (ZDM) 26.1 has a workflow built for exactly that: refreshable PDB cloning.
ZDM 26.1 introduced the PDB Cloning migration workflow, which gives you three cloning options: cold, hot, and refreshable. Cold and hot cloning copy the PDB once and cut over; refreshable cloning keeps a clone of your source PDB on the target continuously in sync until you’re ready to switch over, so the actual cutover window is just minutes, not hours.
This blog walks through the process of using refreshable PDB cloning to migrate a 19c database to Oracle AI Database 26ai end to end including installing ZDM, building the response file, running the migration in two stages, and troubleshooting for a component-mismatch error you may hit along the way.
Refreshable PDB Cloning
The refreshable cloning workflow splits your migration into two stages, controlled by the pause after option:
- Stage 1 clones the source PDB into the target CDB and starts refreshing it on a schedule the ZDM_PDB_REFRESH_INTERVAL parameter controls how often, in minutes.
- Stage 2 is triggered when you resume the job, stops the refresh, performs the final sync, and switches your application over to the target.
As you can imagine, this is great for production databases where you want the bulk of the data transfer to happen well ahead of your cutover window, leaving only a small final delta to apply when you actually flip over.
The Environment
For demonstration purposes, this walkthrough uses OCI Oracle Base Database Service running Oracle AI Database 26ai, version 23.26.1.0.0, as the target.
- Source: hostname ebsai, IP 10.0.0.253, DB name EBSCDB, DB unique name EBSCDB, PDB name EBSDB
- Target: hostname smzap-26ai, IP 10.0.0.172, DB name EBS26AI, DB unique name EBS26AI
- ZDM service host: hostname smzap-zdm
Prerequisites: Installing and Configuring ZDM
Before you can run any migration, ZDM needs to be installed and configured on its own host. The following steps were performed once on the ZDM service host (smzap-zdm).
1.Install required OS packages
As root, install the OS packages ZDM depends on:
[root@smzap-zdm ~]# dnf install -y expect libnsl glibc-devel libstdc++ libaio bc unzip tar
2. Create the OS users and groups
[root@smzap-zdm ~]# groupadd oinstall
[root@smzap-zdm ~]# groupadd dba
[root@smzap-zdm ~]# useradd -m -g oinstall -G dba zdmuser
[root@smzap-zdm ~]# passwd zdmuser
[root@smzap-zdm ~]# mkdir -p /u01/app/zdmhome /u01/app/zdmbase
[root@smzap-zdm ~]# chown -R zdmuser:oinstall /u01/app
[root@smzap-zdm ~]# chmod -R 775 /u01/app
3. Download and stage the ZDM kit
Download the ZDM kit from Oracle Software Delivery Cloud (eDelivery). Search for Oracle Zero Downtime Migration, select the 26.1 release for Linux x86-64, and download. The file is V1055491-01.zip (~1.1 GB). SCP it to smzap-zdm:/tmp/zdm_kit/.
4. Install ZDM
As zdmuser, unzip and run the installer:
[zdmuser@smzap-zdm ~]$ mkdir -p /tmp/zdm_kit
[zdmuser@smzap-zdm ~]$ cd /tmp/zdm_kit
[zdmuser@smzap-zdm zdm_kit]$ cp /path/to/V1055491-01.zip .
[zdmuser@smzap-zdm zdm_kit]$ unzip V1055491-01.zip
[zdmuser@smzap-zdm zdm_kit]$ cd zdm26.1
[zdmuser@smzap-zdm zdm26.1]$ ./zdminstall.sh setup \\\\
oraclehome=/u01/app/zdmhome \\\\
oraclebase=/u01/app/zdmbase \\\\
ziploc=/tmp/zdm_kit/zdm26.1/zdm_home.zip
Expected output ends with:
Adding Certs to ZDM
Adding java jre to ZDM
ZDM service setup finished successfully.
Important: the kit must be staged outside $ZDM_HOME. If you unzip directly into /u01/app/zdmhome, the installer silently skips laying down bin/zdmcli.
5. Start the ZDM service and check status
[zdmuser@smzap-zdm ~]$ /u01/app/zdmhome/bin/zdmservice start
[zdmuser@smzap-zdm ~]$ /u01/app/zdmhome/bin/zdmservice status
Running: true
Conn String: jdbc:derby:/u01/app/zdmbase/derbyRepo;create=true
RMI port: 8897
HTTP port: 8898
SSH port: 22
Wallet path: /u01/app/zdmbase/crsdata/smzap-zdm/security
[zdmuser@smzap-zdm ~]$ /u01/app/zdmhome/bin/zdmcli -build
6. Generate an SSH key for ZDM
ZDM connects to your source and target hosts over SSH, so generate a dedicated key pair and copy it to both:
[zdmuser@smzap-zdm ~]$ ssh-keygen -t rsa -b 4096 -f /home/zdmuser/.ssh/zdm_rsa -N “”
[zdmuser@smzap-zdm ~]$ ssh-copy-id -i /home/zdmuser/.ssh/zdm_rsa.pub oracle@ebsai
[zdmuser@smzap-zdm ~]$ ssh-copy-id -i /home/zdmuser/.ssh/zdm_rsa.pub oracle@smzap-26ai
7. Grant passwordless sudo on the source and target
As root on each host (ebsai and smzap-26ai), allow the oracle OS user to run sudo without a password. ZDM needs this to perform privileged steps during the migration:
[root@ebsai ~]# echo ‘oracle ALL=(ALL) NOPASSWD:ALL’ > /etc/sudoers.d/oracle ; chmod 440 /etc/sudoers.d/oracle
[root@smzap-26ai ~]# echo ‘oracle ALL=(ALL) NOPASSWD:ALL’ > /etc/sudoers.d/oracle ; chmod 440 /etc/sudoers.d/oracle
8. Validate SSH connectivity from the ZDM host
Confirm the ZDM host can reach both source and target as Oracle, and that sudo works without a password prompt:
[zdmuser@smzap-zdm ~]$ ssh -i /home/zdmuser/.ssh/zdm_rsa oracle@ebsai “hostname; id”
ebsai
uid=54321(oracle) gid=54321(oinstall) …
[zdmuser@smzap-zdm ~]$ ssh -i /home/zdmuser/.ssh/zdm_rsa oracle@smzap-26ai “hostname; id”
smzap-26ai
uid=54321(oracle) gid=54321(oinstall) …
[zdmuser@smzap-zdm ~]$ ssh -i /home/zdmuser/.ssh/zdm_rsa oracle@ebsai “sudo -n id”
uid=0(root) gid=0(root) …
[zdmuser@smzap-zdm ~]$ ssh -i /home/zdmuser/.ssh/zdm_rsa oracle@smzap-26ai “sudo -n id”
uid=0(root) gid=0(root) …
With all checks passing, the ZDM host is ready to run migrations.
Running the Migration
Step 1: Create the response file
On the ZDM host, create a response file describing the refreshable clone migration. The key parameters here are ZDM_PDB_CLONE_METHOD=REFRESHABLE and ZDM_PDB_REFRESH_INTERVAL, which sets how often (in minutes) the target PDB refreshes from the source:
[zdmuser@smzap-zdm ~]$ vi /home/zdmuser/zdm_pdb_clone_refreshable.rsp
TGT_DB_UNIQUE_NAME=EBS26AI
MIGRATION_METHOD=ONLINE_PHYSICAL
DATA_TRANSFER_MEDIUM=DBLINK
PLATFORM_TYPE=NON_CLOUD
ZDM_PDB_CLONE_METHOD=REFRESHABLE
ZDM_SRC_PDB_NAME=EBSDB
ZDM_TGT_PDB_NAME=EBSDB_26AI
ZDM_PDB_REFRESH_INTERVAL=5
ZDM_SWITCHOVER=FALSE
Step 2: Evaluate the migration
Before running the real thing, run zdmcli with -eval to validate the migration end to end without making any changes:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli migrate database \\\\
-rsp /home/zdmuser/zdm_pdb_clone_refreshable.rsp \\\\
-sourcedb EBSCDB \\\\
-sourcenode ebsai \\\\
-srcauth zdmauth \\\\
-srcarg1 user:oracle \\\\
-srcarg2 identity_file:/home/zdmuser/.ssh/zdm_rsa \\\\
-srcarg3 sudo_location:/usr/bin/sudo \\\\
-targetnode smzap-26ai \\\\
-tgtauth zdmauth \\\\
-tgtarg1 user:oracle \\\\
-tgtarg2 identity_file:/home/zdmuser/.ssh/zdm_rsa \\\\
-tgtarg3 sudo_location:/usr/bin/sudo \\\\
-targethome /u01/app/oracle/product/26.0.0/dbhome_1 \\\\
-tgttdekeystorepasswd \\\\
-ignore PATCH_CHECK \\\\
-eval
Enter source database EBSCDB SYS password:
Enter target container database TDE keystore password:
Operation “zdmcli migrate database” scheduled with the job ID “1”.
Check the job status:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli query job -jobid 1
Job ID: 1
User: zdmuser
Job Type: “EVAL”
Current status: SUCCEEDED
Result file path: “/home/zdmuser/zdmbase/chkbase/scheduled/job-1-.log”
Job execution elapsed time: 1 minutes 58 seconds
ZDM_GET_SRC_INFO ………… PRECHECK_PASSED
ZDM_GET_TGT_INFO ………… PRECHECK_PASSED
ZDM_PRECHECKS_SRC ……….. PRECHECK_PASSED
ZDM_PRECHECKS_TGT ……….. PRECHECK_PASSED
ZDM_SETUP_SRC …………… PRECHECK_PASSED
ZDM_SETUP_TGT …………… PRECHECK_PASSED
ZDM_PREUSERACTIONS ……….. PRECHECK_PASSED
ZDM_PREUSERACTIONS_TGT ……. PRECHECK_PASSED
ZDM_VALIDATE_SRC …………. PRECHECK_PASSED
ZDM_VALIDATE_TGT …………. PRECHECK_PASSED
ZDM_POSTUSERACTIONS ………. PRECHECK_PASSED
ZDM_POSTUSERACTIONS_TGT …… PRECHECK_PASSED
ZDM_CLEANUP_SRC ………….. PRECHECK_PASSED
ZDM_CLEANUP_TGT ………….. PRECHECK_PASSED
Step 3: Start the migration and pause before switchover
Now run the same command for real, but this time use -pauseafter to stop the job once the ZDM_FINALIZE_TGT phase completes. This is the phase where the target PDB clone is created and refreshing on the schedule defined by ZDM_PDB_REFRESH_INTERVAL, everything up to your actual cutover happens here, ahead of time:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli migrate database \\\\
-rsp /home/zdmuser/zdm_pdb_clone_refreshable.rsp \\\\
-sourcedb EBSCDB \\\\
-sourcenode ebsai \\\\
-srcauth zdmauth \\\\
-srcarg1 user:oracle \\\\
-srcarg2 identity_file:/home/zdmuser/.ssh/zdm_rsa \\\\
-srcarg3 sudo_location:/usr/bin/sudo \\\\
-targetnode smzap-26ai \\\\
-tgtauth zdmauth \\\\
-tgtarg1 user:oracle \\\\
-tgtarg2 identity_file:/home/zdmuser/.ssh/zdm_rsa \\\\
-tgtarg3 sudo_location:/usr/bin/sudo \\\\
-targethome /u01/app/oracle/product/26.0.0/dbhome_1 \\\\
-tgttdekeystorepasswd \\\\
-ignore PATCH_CHECK \\\\
-pauseafter ZDM_FINALIZE_TGT
Enter source database EBSCDB SYS password:
Enter target container database TDE keystore password:
Operation “zdmcli migrate database” scheduled with the job ID “2”.
Check the job status:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli query job -jobid 2
Job ID: 2
User: zdmuser
Job Type: “MIGRATE”
Current status: PAUSED
Current Phase: “ZDM_FINALIZE_TGT”
Result file path: “/home/zdmuser/zdmbase/chkbase/scheduled/job-2-.log”
Job execution elapsed time: 35 minutes 12 seconds
ZDM_GET_SRC_INFO …………. COMPLETED
ZDM_GET_TGT_INFO …………. COMPLETED
ZDM_PRECHECKS_SRC ………… COMPLETED
ZDM_PRECHECKS_TGT ………… COMPLETED
ZDM_SETUP_SRC ……………. COMPLETED
ZDM_SETUP_TGT ……………. COMPLETED
ZDM_PREUSERACTIONS ……….. COMPLETED
ZDM_PREUSERACTIONS_TGT ……. COMPLETED
ZDM_VALIDATE_SRC ………….. COMPLETED
ZDM_VALIDATE_TGT ………….. COMPLETED
ZDM_DISCOVER_SRC ………….. COMPLETED
ZDM_COPYFILES ……………… COMPLETED
ZDM_PREPARE_TGT ……………. COMPLETED
ZDM_CLONE_PDB_TGT ………….. COMPLETED
ZDM_FINALIZE_TGT ……………. COMPLETED
ZDM_SWITCHOVER_TGT ………….. PENDING
ZDM_SWITCHOVER_SRC ………….. PENDING
ZDM_NONCDBTOPDB_CONVERSION …… PENDING
ZDM_DATAPATCH_TGT ……………. PENDING
ZDM_POST_DATABASE_OPEN_TGT ……. PENDING
ZDM_POSTUSERACTIONS ………….. PENDING
ZDM_POSTUSERACTIONS_TGT ………. PENDING
ZDM_CLEANUP_SRC ………………. PENDING
ZDM_CLEANUP_TGT ……………….. PENDING
Pause After Phase: “ZDM_FINALIZE_TGT”
At this point you can check the PDB status on the target database and confirm it’s actively refreshing:
SQL> set lines 300
col pdb_name for a15
col refresh_mode for a15
select con_id, pdb_name, status, refresh_mode, refresh_interval from dba_pdbs;
CON_ID PDB_NAME STATUS REFRESH_MODE REFRESH_INTERVAL
—— ————— ——— ————— —————-
3 EBSDB_26AI REFRESHING AUTO 5
2 PDB$SEED NORMAL NONE
This is the state you can sit in for as long as you need. The target keeps refreshing every 5 minutes while you plan your cutover window.
Step 4: Resume the job to switch over
When you’re ready to cut your application over, resume the paused job. ZDM stops the refresh, applies the final delta, and switches over:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli resume job -jobid 2
Check the job status again you should see every phase, including the switchover and post-open phases, complete:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli query job -jobid 2
Job ID: 2
User: zdmuser
Job Type: “MIGRATE”
Current status: SUCCEEDED
Current Phase: “ZDM_CLEANUP_TGT”
Result file path: “/home/zdmuser/zdmbase/chkbase/scheduled/job-2-.log”
Job execution elapsed time: 38 minutes 45 seconds
ZDM_GET_SRC_INFO …………. COMPLETED
ZDM_GET_TGT_INFO …………. COMPLETED
ZDM_PRECHECKS_SRC ………… COMPLETED
ZDM_PRECHECKS_TGT ………… COMPLETED
ZDM_SETUP_SRC ……………. COMPLETED
ZDM_SETUP_TGT ……………. COMPLETED
ZDM_PREUSERACTIONS ……….. COMPLETED
ZDM_PREUSERACTIONS_TGT ……. COMPLETED
ZDM_VALIDATE_SRC ………….. COMPLETED
ZDM_VALIDATE_TGT ………….. COMPLETED
ZDM_DISCOVER_SRC ………….. COMPLETED
ZDM_COPYFILES ……………… COMPLETED
ZDM_PREPARE_TGT ……………. COMPLETED
ZDM_CLONE_PDB_TGT ………….. COMPLETED
ZDM_FINALIZE_TGT ……………. COMPLETED
ZDM_SWITCHOVER_TGT ………….. COMPLETED
ZDM_SWITCHOVER_SRC ………….. COMPLETED
ZDM_NONCDBTOPDB_CONVERSION …… COMPLETED
ZDM_DATAPATCH_TGT ……………. COMPLETED
ZDM_POST_DATABASE_OPEN_TGT ……. COMPLETED
ZDM_POSTUSERACTIONS ………….. COMPLETED
ZDM_POSTUSERACTIONS_TGT ………. COMPLETED
ZDM_CLEANUP_SRC ………………. COMPLETED
ZDM_CLEANUP_TGT ……………….. COMPLETED
Finally, confirm the PDB on the target is now a normal, fully switched-over PDB:
SQL> set lines 300
col pdb_name for a15
col refresh_mode for a15
select con_id, pdb_name, status, refresh_mode, refresh_interval from dba_pdbs;
CON_ID PDB_NAME STATUS REFRESH_MODE REFRESH_INTERVAL
—— ————— ——— ————— —————-
3 EBSDB_26AI NORMAL NONE
2 PDB$SEED NORMAL NONE
And that’s it, your 19c PDB is now running on Oracle AI Database 26ai, with a cutover window measured in minutes instead of hours.
Troubleshooting: Registry Components Mismatch (PRGZ-3944 / ORA-65346)
During ZDM_CLONE_PDB_TGT, you may find the target CDB refuses to clone the source PDB because components present in the source PDB are missing in the target CDB:
PRGZ-3944 : SQL statements to clone a pluggable database (PDB) EBSDB
to a pluggable database EBSDB_26AI on the target multitenant
container database (CDB) EBS26AI failed.
ORA-65346 : The PDB version is lower and components (APS) are missing in CDB.
Diagnose
Compare dba_registry on the source and target CDB$ROOT to identify the missing components. Run this on both the source (EBSCDB) and target (EBS26AI) CDB$ROOT:
set lines 200 pages 200
col comp_id for a10
col comp_name for a45
col version for a30
col status for a12
select comp_id, comp_name, version, status from dba_registry order by comp_id;
In this environment, the source 19c PDB carried APS (OLAP Analytic Workspace) and XOQ (OLAP API) with status OPTION OFF. The 26ai target CDB had no entry for either component at all. The clone-time validator requires matching component presence, regardless of whether OLAP is actually used.
Fix (on the target CDB, as Oracle)
- Connect to CDB$ROOT on the target:
ALTER SESSION SET CONTAINER=CDB$ROOT;
- Install OLAP Analytic Workspace (APS) objects:
@?/olap/admin/olap.sql SYSAUX TEMP
- Install OLAP API (XOQ) objects:
@?/olap/admin/catxoq.sql
If the 26ai binary doesn’t have the OLAP kernel option enabled, these scripts complete but the registry rows show OPTION OFF. Update the registry so the clone validator accepts them:
UPDATE registry$ SET status = 1 WHERE cid IN (‘APS’,’XOQ’);
COMMIT;
— Verify
select comp_id, status from dba_registry where comp_id in (‘APS’,’XOQ’);
— expected: VALID for both
Warning: do NOT run utlrp.sql or any dictionary validation script between this fix and the ZDM job. The validator recomputes component state from binary feature flags and will reset APS/XOQ back to OPTION OFF, causing the same ORA-65346 on the next attempt. Run utlrp.sql only after the clone has succeeded and the PDB has been opened post-cutover.
Resume
After applying the fix, resume the failed ZDM job – it will automatically retry ZDM_CLONE_PDB_TGT:
[zdmuser@smzap-zdm ~]$ $ZDMHOME/bin/zdmcli resume job -jobid 2
Why This Approach Is Worth Using
Stepping back, here’s what the walkthrough above actually did. You installed and configured ZDM 26.1 on a dedicated host OS package, the zdmuser account, SSH trust, and passwordless sudo to both the source (ebsai) and target (smzap-26ai) hosts. From there, you built a response file telling ZDM to use the REFRESHABLE PDB clone method with a 5-minute refresh interval, ran an eval pass to validate the plan with zero risk, and then kicked off the real migration with pause after ZDM_FINALIZE_TGT.
That single flag is what splits the migration into two stages: ZDM clones EBSDB into the target CDB as EBSDB_26AI and keeps it continuously refreshing, then sits and waits. When you were ready, resuming the job triggered the final sync and switchover, converting EBSDB_26AI from a REFRESHING clone into a NORMAL, fully operational PDB on Oracle AI Database 26ai.
The reason this is worth the extra setup is the cutover math. A traditional cold clone or full export/import must move all of your data, which for a multi-terabyte EBS database can take hours inside the maintenance window your business actually feels. Refreshable PDB cloning moves that bulk transfer out of the window entirely: the clone is created and kept in sync days or even weeks ahead of time, refreshing automatically every ZDM_PDB_REFRESH_INTERVAL minutes. When the time comes to cut over, ZDM only has to apply the last few minutes of changes, so your actual outage shrinks to the time it takes to run the final sync, switchover, and datapatch phases, typically minutes, not hours.
This makes the approach particularly useful for production databases with strict change-control windows, think E-Business Suite, ERP, or other systems where the business can tolerate a short maintenance window but not an extended one. It’s also valuable when you’re migrating across platforms (on-premises to OCI, as in this example) where network transfer time for the initial copy is the dominant cost: refreshable cloning lets that transfer happen well in advance and on its own schedule, decoupled from your go-live date. And because the job pauses cleanly at ZDM_FINALIZE_TGT, you get a natural checkpoint to run validation, application testing, or, as covered in the troubleshooting section, fix registry component mismatches like the ORA-65346 / PRGZ-3944 error before you commit to the final switchover.
Ready to modernize your Oracle environment with minimal downtime? Whether you’re planning a PDB migration or a broader database transformation, Centroid’s Oracle consultants can help every step of the way. Reach out to Centroid to explore how we can simplify your migration and maximize the value of your Oracle investments.