What Is Oracle ZDM?

Oracle Zero Downtime Migration (ZDM) is Oracle's own tooling for migrating Oracle databases with minimal or zero application downtime. Introduced alongside Oracle Database 19c, it has matured significantly through 21c and 23ai patch cycles.

ZDM handles the complexity of database migration by orchestrating:

The phrase "zero downtime" is slightly optimistic. You will have a brief switchover window. What ZDM eliminates is the multi-hour planned outage of traditional export/import or straight RMAN restore approaches.

Architecture Overview

ZDM uses three distinct tiers:

ZDM Service Host — A dedicated server or VM where the ZDM software is installed. This host orchestrates the migration but processes no data itself. It needs SSH access to both source and target database hosts.

Source Database Host — Your existing production Oracle server. ZDM connects here to take backups, configure Data Guard, or drive a Data Pump export.

Target Database Host — The destination. For cloud migrations this is typically an Oracle Base Database Service or ExaCS instance. For on-premises targets it is any Oracle-supported Linux host.

The ZDM service coordinates everything through a sequential phase model: it SSH's into both endpoints, executes remote commands, and tracks migration progress through a series of named phases with checkpoint/resume capability.

Migration Methods: Physical vs Logical

ZDM offers three migration methods, and the choice matters enormously for large databases:

Physical Migration (RMAN-based) — Uses RMAN backup and restore to transfer the database to the target, then configures a Data Guard standby for ongoing redo apply. Switchover is a Data Guard role change. Fastest for large databases (multi-TB). Requires compatible OS and endian format between source and target.

Logical Migration (Data Pump-based) — Exports schema and data using Oracle Data Pump, with optional GoldenGate for continuous replication. Required for cross-platform, cross-version, or cross-endian migrations. Slower for large databases but more flexible.

Online Logical Migration — Combines Data Pump for the initial load with GoldenGate Microservices for continuous replication. True near-zero downtime. Requires GoldenGate licensing.

For most lift-and-shift migrations to OCI, physical migration is the right choice. You get faster transfer times and a simpler change model.

Prerequisites Checklist

Before you run a single zdmcli command, verify each of these:

Network and SSH Connectivity

# From ZDM host — test passwordless SSH to both source and target
ssh -i /home/zdmuser/.ssh/id_rsa oracle@sourcehost "echo OK"
ssh -i /home/zdmuser/.ssh/id_rsa oracle@targethost "echo OK"

# Verify oracle user has passwordless sudo
ssh oracle@sourcehost "sudo /bin/ls /etc/oratab"

Oracle Database Versions

Wallets and sys Credentials

# Create source wallet with MKSTORE
mkstore -wrl /home/zdmuser/wallets/src -create
mkstore -wrl /home/zdmuser/wallets/src -createCredential "sourcehost:1521/PRODDB" sys

# Create target wallet
mkstore -wrl /home/zdmuser/wallets/tgt -create
mkstore -wrl /home/zdmuser/wallets/tgt -createCredential "targethost:1521/PRODDB_TGT" sys

# Create sqlnet.ora on ZDM host pointing to wallet
cat > $ORACLE_HOME/network/admin/sqlnet.ora <<EOF
WALLET_LOCATION = (SOURCE = (METHOD = FILE)(METHOD_DATA = (DIRECTORY = /home/zdmuser/wallets/src)))
SQLNET.WALLET_OVERRIDE = TRUE
EOF

Disk Space

Starting the ZDM Service

export ZDM_HOME=/u01/zdmhome
export ZDM_BASE=/u01/zdmbase

# Start
$ZDM_HOME/bin/zdmservice start

# Verify
$ZDM_HOME/bin/zdmservice status

# Expected:
# ZDM Service is up and running.
# Listening on port: 8895

# Logs go here if something is wrong
tail -100 $ZDM_BASE/zdm/log/zdm_service.log

The Response File

The response file is the single most important artefact in the migration. Every parameter lives here.

# /home/zdmuser/response/prod_to_oci.rsp

# Target DB unique name (must match what you provisioned on OCI)
TGT_DB_UNIQUE_NAME=PRODDB_TGT

# Physical migration using Data Guard replication
MIGRATION_METHOD=ONLINE_PHYSICAL

# Transfer data via OCI Object Storage
DATA_TRANSFER_MEDIUM=OSS
HOST=https://objectstorage.us-ashburn-1.oraclecloud.com
OPC_CONTAINER=zdm-migration-bucket

# RMAN compression — reduces transfer time significantly for large DBs
RMAN_COMPRESSION_ALGORITHM=MEDIUM

# Run datapatch on target post-switchover (recommended: TRUE)
TGT_SKIP_DATAPATCH=FALSE

# Source DB unique name
SRC_DB_UNIQUE_NAME=PRODDB

# Keep source running after switchover until you confirm target is stable
SHUTDOWN_SOURCE=FALSE

# Archive log gap threshold — how many seconds of lag is acceptable at switchover
# Default 30 is fine for most workloads; lower for very busy systems
# MAX_APPLY_LAG=30

Key Parameters Explained

MIGRATION_METHOD — This is the fundamental choice. ONLINE_PHYSICAL gives you live Data Guard sync with a rapid switchover. OFFLINE_PHYSICAL is a backup/restore with no sync. ONLINE_LOGICAL requires GoldenGate.

DATA_TRANSFER_MEDIUM — OSS uses OCI Object Storage (fast, scalable, recommended for anything >100GB). DBLINK transfers directly over SQL*Net (simpler setup, but slower for large DBs). NFS works for collocated target.

TGT_SKIP_DATAPATCH — Only set TRUE if you're doing a test migration and want to save time. For production migrations, always FALSE. Skipping datapatch can leave objects in an invalid state.

Running the Migration

$ZDM_HOME/bin/zdmcli migrate database \
  -sourcedb PRODDB \
  -sourcenode sourcehost.example.com \
  -srcauth zdmauth \
  -srcarg1 user:oracle \
  -srcarg2 identity_file:/home/zdmuser/.ssh/id_rsa \
  -srcarg3 sudo_location:/usr/bin/sudo \
  -targetnode targethost.oci.example.com \
  -tgtauth zdmauth \
  -tgtarg1 user:oracle \
  -tgtarg2 identity_file:/home/zdmuser/.ssh/id_rsa \
  -tgtarg3 sudo_location:/usr/bin/sudo \
  -rsp /home/zdmuser/response/prod_to_oci.rsp \
  -sourcesyswallet /home/zdmuser/wallets/src \
  -targetsyswallet /home/zdmuser/wallets/tgt

# Returns immediately with a job ID
# Job ID: 1

Important: ZDM submits the job and returns immediately. The migration runs asynchronously. Do not close your terminal session — use screen or tmux for long migrations.

Monitoring with zdmcli query job

# Poll current status
$ZDM_HOME/bin/zdmcli query job -jobid 1

# Sample output mid-migration:
# JOB ID       : 1
# OPERATION    : MIGRATE
# STATUS       : RUNNING
# CREATED      : 2025-05-20T09:00:00
# STARTED      : 2025-05-20T09:00:15
# SCHEDULED    : 2025-05-20T09:00:00
# PHASE_NAME         PHASE_STATUS    STARTED              DURATION
# SETUP              COMPLETED       09:00:15             00:02:14
# VALIDATESOURCE     COMPLETED       09:02:29             00:01:03
# VALIDATETARGET     COMPLETED       09:03:32             00:00:47
# INITIALTRANSFER    IN_PROGRESS     09:04:19             02:34:12

# Watch it live (runs every 60s)
watch -n 60 "$ZDM_HOME/bin/zdmcli query job -jobid 1"

Phase-by-Phase Breakdown

| Phase | Duration (typical) | What Happens |
|-------|-------------------|-------------|
| SETUP | 2–5 min | Creates working dirs, validates SSH, checks Oracle Net |
| VALIDATESOURCE | 1–2 min | Connects to source DB, checks version, mode, archive log |
| VALIDATETARGET | 1–2 min | Connects to target DB, checks provisioning |
| INITIALTRANSFER | Hours | RMAN backup from source → OCI Object Storage; restore to target |
| SYNCTARGET | Ongoing | Archive log apply loop — keeps target current |
| SWITCHOVER | 2–15 min | Role change: source → standby, target → primary |
| POSTSWITCHOVER | 30–60 min | TNS config update, datapatch if enabled, cleanup |

INITIALTRANSFER is where the bulk of time goes for large databases. For a 5TB database with MEDIUM compression, expect 4–8 hours depending on your network to OCI. SYNCTARGET then runs continuously until you trigger switchover.

The Switchover Decision

ZDM pauses in SYNCTARGET and waits for you to initiate switchover. This is intentional — you control when the service interruption happens. Check the apply lag before proceeding:

-- On target: check Data Guard apply lag
SELECT NAME, VALUE, UNIT FROM V$DATAGUARD_STATS WHERE NAME IN ('apply lag','transport lag');

-- Acceptable: apply lag < 30 seconds
-- If lag is high, wait for SYNCTARGET to catch up

When you are ready:

# Resume the job to trigger switchover
$ZDM_HOME/bin/zdmcli resume job -jobid 1

From this point forward, ZDM executes the actual switchover. Application teams should monitor for connectivity restoration.

Common Failures and Fixes

SSH Authentication Failures

Error: SSH connection to sourcehost failed — Permission denied (publickey)

Fix: Verify the identity file path is correct and the public key is in the oracle user's authorized_keys on both source and target.

Wallet Cannot Open

ORA-28353: failed to open wallet

Check that the wallet path in your sqlnet.ora on the ZDM host matches where you created the wallet, and that the wallet contains the correct credential. Use mkstore -wrl /path/to/wallet -listCredential to verify.

Archive Log Destination Full

-- On source during SYNCTARGET
SELECT DEST_ID, STATUS, ERROR FROM V$ARCHIVE_DEST WHERE STATUS != 'INACTIVE';
ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE = 500G SCOPE=BOTH;

Object Storage Auth Errors

# Test OCI OS connectivity from ZDM host
oci os bucket list --compartment-id <ocid> --namespace <namespace>
# If this fails, check your OCI config file and API key setup

Target DB Not Opening READ WRITE

After switchover, if the target database doesn't open normally:
-- Check for ORA-16038 or similar Data Guard errors
SELECT ERROR_CODE, MESSAGE FROM V$DATAGUARD_STATUS ORDER BY TIMESTAMP DESC;
-- Common fix: enable standby log file archiving was skipped
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT;

Post-Migration Validation

Run these on the target immediately after POSTSWITCHOVER completes:

-- 1. Confirm database role and mode
SELECT NAME, DB_UNIQUE_NAME, DATABASE_ROLE, OPEN_MODE FROM V$DATABASE;

-- 2. Check for invalid objects (especially in APPS schema for EBS)
SELECT OWNER, COUNT(*) CNT FROM DBA_OBJECTS
WHERE STATUS = 'INVALID'
GROUP BY OWNER ORDER BY 2 DESC;

-- 3. Verify key schemas accessible
SELECT USERNAME, ACCOUNT_STATUS FROM DBA_USERS
WHERE ACCOUNT_STATUS = 'OPEN' ORDER BY USERNAME;

-- 4. Check for any pending Data Guard transport
SELECT DEST_ID, STATUS, ERROR FROM V$ARCHIVE_DEST
WHERE TARGET = 'STANDBY' AND STATUS != 'INACTIVE';

-- 5. Verify tablespace and datafile status
SELECT FILE#, STATUS, NAME FROM V$DATAFILE WHERE STATUS != 'ONLINE';

For EBS databases specifically, also run autoconfig on both DB and apps tiers and verify the context file points to the new hostname/service.

TuneVault and Post-Migration Health

The 48 hours after a ZDM migration are the highest-risk period. Your database is running on new infrastructure with potentially different I/O characteristics, different memory allocation, and slightly different parameter defaults. TuneVault's automated health checks surface the common post-migration issues — missing optimizer statistics, tablespace sizing differences, redo log configuration, and invalid objects — within minutes of pointing at the new instance. Run a health check immediately after validation, then again after 24 hours of production load.