Skip to main content

Command Palette

Search for a command to run...

Oracle 26ai — Setting Up Data Guard Per Pluggable Database (DGPDB) From Scratch

Pluggable database level dataguard setup with switchover of only one pdb at a time. Redo flows bidirectionally between two CDB databases.

Updated
13 min read
Oracle 26ai — Setting Up Data Guard Per Pluggable Database (DGPDB) From Scratch
S
Hey, I am Sagar Thosar — and I have spent 13 years in the trenches of enterprise Oracle database administration so you do not have to figure it out alone. I started as an Assistant Oracle DBA at a training firm in Pune, deployed to client sites, managing databases I barely understood and learning everything the hard way — through production incidents, 2 AM calls, and the kind of pressure that either breaks you or makes you very, very good. Thirteen years later, I have managed 150+ database estates at companies like Equinix, BNY Mellon, and Oracle India. I have patched Exadata clusters, architected GoldenGate replication for zero-downtime migrations, designed RIRA frameworks that eliminated 300+ recurring incidents per month, and — most recently — built AWS Bedrock Agentic AI systems that autonomously handle database operations that used to wake me up at night. The industry changed. I changed with it. And I wish someone had been writing honestly about that journey while I was living it. That is why this blog exists. Here you will find: 🔧 Deep Oracle DBA content — Exadata internals, RAC architecture, GoldenGate replication patterns, Data Guard configurations, performance tuning war stories. Real scenarios from production environments, not sanitised textbook examples. ☁️ Cloud database engineering — Oracle to AWS migrations, self-service provisioning with Terraform and GitHub Actions, AWS RDS architecture decisions, and what the documentation never tells you about moving enterprise Oracle to the cloud. 🤖 Intelligent database automation — How to use AWS Bedrock, Python, and GitLab CI/CD to eliminate manual DBA work. The future of database operations is not more DBAs doing the same things faster. It is smarter systems doing routine things autonomously while DBAs focus on architecture. 📓 Notebook to blog — I have been accumulating handwritten notes across years of reading, production incidents, and experiments. Many of these blogs start as scribbled observations in notebooks. If a concept changed how I think about databases, it ends up here. I write for DBAs who are tired of firefighting and want to build systems that prevent fires. For cloud engineers who inherited Oracle environments and need to understand what they are dealing with. For architects who need to make sense of how traditional database operations translate into modern cloud infrastructure. I do not write to impress. I write to be useful. If that sounds like your kind of content — follow along. And if you have a question, a problem, or a topic you want me to cover — reach out. Thirteen years of Oracle DBA experience is most valuable when it is shared. Connect with me: 🔗 LinkedIn 💻 GitHub 📧 sagarthosar.careers@gmail.com

Level: Advanced DBA
Reading time: ~20 minutes

Introduction

Traditional Oracle Data Guard protects the entire CDB — if you want to failover, everything moves together. But in a multitenant environment with dozens of PDBs serving different business units, you may only want to failover one PDB while the others stay put.

That's exactly what DGPDB — Data Guard per Pluggable Database delivers.

Introduced in Oracle 21c (21.7 RU) and enhanced in Oracle 23ai/26ai, DGPDB lets each PDB act as an independent Data Guard unit. You can switchover ORCLPDB1 to your DR site while PDB2 and PDB3 continue running as-is on the original host — without touching them.

I spent two full days building this from scratch on Oracle 26ai (23.26.1.0.0) running in Docker on a Windows laptop. Here's everything I learned — including the errors that aren't in the official docs.


What Makes DGPDB Different From Traditional Data Guard

Before we touch a keyboard, understand this architectural shift:

Traditional Data Guard:
  Primary CDB  ──── redo ────►  Standby CDB (MOUNTED)
  Both PDBs move together on failover

DGPDB:
  CDB-A (PRIMARY, OPEN R/W)       CDB-B (PRIMARY, OPEN R/W)
    SALES_PDB  ► Primary             SALES_PDB  ► Standby (MOUNTED)
    HR_PDB     ► Primary             HR_PDB     ► Standby (MOUNTED)
    ERP_PDB    ► Standby (MOUNTED)   ERP_PDB    ► Primary

Both CDBs are primary at CDB level. There is no traditional "standby database." Each CDB hosts some PDBs as active (READ WRITE) and some as standby (MOUNTED, receiving redo from the peer CDB).

The DGPDB broker orchestrates everything across this peer-to-peer model. Which brings us to the first critical fact you need to know:

DGPDB is 100% broker-managed. There is no SQL-only alternative. Every operation — configuration, monitoring, switchover, failover — goes through dgmgrl.


Pre-Requisites and Environment

For this guide, I'm using:

  • Oracle Database 26ai EE (23.26.1.0.0) on Oracle Linux 8

  • Two separate CDB hostsoracle-primary (SID: ORCL) and oracle-standby (SID: ORCL2)

  • Three PDBs configured in a symmetric layout:

    • ORCLPDB1: primary on ORCL, standby on ORCL2

    • PDB2: primary on ORCL, standby on ORCL2

    • PDB3: primary on ORCL2, standby on ORCL

Both hosts must be able to reach each other over TCP (port 1521) by hostname.


Step 1 — Baseline CDB Configuration

Both CDBs need these foundations before DGPDB can work.

Enable Archivelog Mode

SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;

Set DB_UNIQUE_NAME

Each CDB needs a unique name for the broker to tell them apart.

-- On ORCL (oracle-primary)
ALTER SYSTEM SET DB_UNIQUE_NAME='ORCL_PRI' SCOPE=SPFILE;

-- On ORCL2 (oracle-standby)
ALTER SYSTEM SET DB_UNIQUE_NAME='ORCL2_PRI' SCOPE=SPFILE;

Restart both CDBs after this change.

Set REMOTE_LOGIN_PASSWORDFILE

ALTER SYSTEM SET REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE SCOPE=SPFILE;

Step 2 — Configure Network Connectivity

TNS Aliases (tnsnames.ora on Both Hosts)

ORCL =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = oracle-primary)(PORT = 1521))
    (CONNECT_DATA = (SERVER = DEDICATED)(SID = ORCL)))

ORCL2 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = oracle-standby)(PORT = 1521))
    (CONNECT_DATA = (SERVER = DEDICATED)(SID = ORCL2)))

Static Listener Registration (listener.ora on Both Hosts)

DGPDB broker needs to connect to both databases even when they're in different states. A static listener entry ensures the database is reachable regardless of its open mode:

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = ORCL)
      (ORACLE_HOME = /opt/oracle/product/26ai/dbhome_1)
      (SID_NAME = ORCL))
    (SID_DESC =
      (GLOBAL_DBNAME = ORCL2)
      (ORACLE_HOME = /opt/oracle/product/26ai/dbhome_1)
      (SID_NAME = ORCL2)))

Run lsnrctl reload after this change.

Create Oracle Wallet for Passwordless Broker Connections

The broker makes autonomous cross-CDB connections without interactive prompts. An Oracle wallet stores the SYS credentials for this:

# On both hosts, repeat for each
mkdir -p $ORACLE_HOME/wallet

mkstore -wrl $ORACLE_HOME/wallet -create
mkstore -wrl $ORACLE_HOME/wallet -createCredential ORCL sys "Oracle_DG#2024"
mkstore -wrl $ORACLE_HOME/wallet -createCredential ORCL2 sys "Oracle_DG#2024"

Then configure sqlnet.ora on both hosts:

SQLNET.WALLET_OVERRIDE = TRUE
WALLET_LOCATION =
  (SOURCE =
    (METHOD = FILE)
    (METHOD_DATA =
      (DIRECTORY = /opt/oracle/product/26ai/dbhome_1/wallet)))

Verify it works:

sqlplus /@ORCL as sysdba      # from oracle-primary
sqlplus /@ORCL2 as sysdba     # test cross-host connection

Step 3 — Create the DGPDB Broker Configurations

DGPDB uses a pair of linked broker configurations — one per CDB — rather than a single traditional DG configuration.

Create a Configuration on Each CDB

On oracle-primary:

export ORACLE_SID=ORCL
dgmgrl /@ORCL
CREATE CONFIGURATION ORCL_DGPDB
  AS PRIMARY DATABASE IS ORCL_PRI
  CONNECT IDENTIFIER IS ORCL;

Gotcha: PRIMARY DATABASE IS takes the DB_UNIQUE_NAME (ORCL_PRI), not the SID (ORCL). Getting this wrong causes ORA-16642.

On oracle-standby:

export ORACLE_SID=ORCL2
dgmgrl /@ORCL2
CREATE CONFIGURATION ORCL2_DGPDB
  AS PRIMARY DATABASE IS ORCL2_PRI
  CONNECT IDENTIFIER IS ORCL2;

From oracle-primary's dgmgrl:

ADD CONFIGURATION ORCL2_DGPDB CONNECT IDENTIFIER IS ORCL2;
ENABLE CONFIGURATION ALL;

This tells ORCL_DGPDB about ORCL2_DGPDB and creates the bidirectional link. The broker propagates the awareness to both sides automatically.

From oracle-standby's dgmgrl:

ENABLE CONFIGURATION ALL;

Both configs should show SUCCESS when you run SHOW CONFIGURATION VERBOSE.


Step 4 — PREPARE DATABASE FOR DATA GUARD

This is the step that most guides skip or get wrong. PREPARE DATABASE FOR DATA GUARD is a mandatory prerequisite on both CDBs. It:

  • Configures the Fast Recovery Area

  • Enables Force Logging and Flashback

  • Adds standby redo log groups at CDB level

  • Sets DB_LOST_WRITE_PROTECT, STANDBY_FILE_MANAGEMENT, and other critical DG parameters

The command has strict connection requirements:

  • Must use a bequeath (local) connection — not a TCP wallet connection

  • Must connect as SYSDBA — not SYSDG

  • Must include the RESTART keyword — some static parameters require a database restart

# On oracle-primary
export ORACLE_SID=ORCL
dgmgrl / as sysdba
PREPARE DATABASE FOR DATA GUARD
  WITH DB_UNIQUE_NAME IS ORCL_PRI
  DB_RECOVERY_FILE_DEST IS '/opt/oracle/fast_recovery_area'
  DB_RECOVERY_FILE_DEST_SIZE IS 10G
  RESTART;
# On oracle-standby
export ORACLE_SID=ORCL2
dgmgrl / as sysdba
PREPARE DATABASE FOR DATA GUARD
  WITH DB_UNIQUE_NAME IS ORCL2_PRI
  DB_RECOVERY_FILE_DEST IS '/opt/oracle/fast_recovery_area'
  DB_RECOVERY_FILE_DEST_SIZE IS 10G
  RESTART;

If static parameters need changing, Oracle shuts the database down and restarts it automatically. Let it run. Successful output ends with Flashback Database enabled. Succeeded.


Step 5 — EDIT CONFIGURATION PREPARE DGPDB

This is the most commonly missed step in DGPDB setups — and it's the one that causes ORA-16922: Configuration not ready for Oracle Data Guard per Pluggable Database every time you try ADD PLUGGABLE DATABASE.

This command creates the DGPDB_INT internal account on both CDBs and marks the broker configurations as DGPDB-ready. Run it once, from oracle-primary, using the wallet connection:

export ORACLE_SID=ORCL
dgmgrl /@ORCL
EDIT CONFIGURATION PREPARE DGPDB;

It prompts for a password for the DGPDB_INT account on each CDB:

Enter password for DGPDB_INT account at ORCL_PRI: <set a password>
Enter password for DGPDB_INT account at ORCL2_PRI: <set a password>
Prepared Data Guard for Pluggable Database at ORCL2_PRI.
Prepared Data Guard for Pluggable Database at ORCL_PRI.

Once you see those two Prepared lines, you're ready for ADD PLUGGABLE DATABASE.


Step 6 — Add PDBs to DGPDB Configuration

Now we register each PDB with its standby destination. The PDBFILENAMECONVERT clause maps source datafile paths to destination paths.

ORCLPDB1 and PDB2 (Primary on ORCL, Standby on ORCL2)

export ORACLE_SID=ORCL2
dgmgrl /@ORCL2
ADD PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI
  SOURCE IS ORCLPDB1 AT ORCL_PRI
  PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL/','/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/'";

ADD PLUGGABLE DATABASE PDB2 AT ORCL2_PRI
  SOURCE IS PDB2 AT ORCL_PRI
  PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL_PRI/','/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/'";

PDB3 (Primary on ORCL2, Standby on ORCL)

export ORACLE_SID=ORCL
dgmgrl /@ORCL
ADD PLUGGABLE DATABASE PDB3 AT ORCL_PRI
  SOURCE IS PDB3 AT ORCL2_PRI
  PDBFILENAMECONVERT IS "'/opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/','/opt/oracle/oradata/ORCL_PRI/'";

Each command returns: Pluggable Database "<PDB>" added

At this point the broker configuration knows about the PDBs, but the standby datafiles don't exist yet. The next step puts them there.


Step 7 — Copy Datafiles to Standby Location

The broker has registered the PDB relationship but the actual data files need to be physically copied to the standby CDB. This must be done one PDB at a time with the source PDB closed for consistency.

Here's the complete cycle for each PDB:

1. Close the Source PDB

-- On primary CDB (ORCL)
ALTER PLUGGABLE DATABASE ORCLPDB1 CLOSE IMMEDIATE;

2. Copy Datafiles

The recommended method is a direct container-to-container tar pipe. On Linux hosts, run this directly.

# On primary Database host tart the datafiles 
tar -cvf /opt/oracle/oradata/ORCL/53646B4FB1A0156FE063020014AC5EDA  

#Use scp to copy datafiles to the target host. 

#On standby database host , untar all the files to appropriate file path
tar -xvf /opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI

NOTE: Above commands are just for reference, use correct scp commands to get all the datafiles belonging to the PDB to the second host.

3. Fix File Ownership on Destination

chown -R oracle:oinstall \
  /opt/oracle/oradata/ORCL2_PRI/ORCL2_PRI/53646B4FB1A0156FE063020014AC5EDA

4. Reopen the Source PDB

ALTER PLUGGABLE DATABASE ORCLPDB1 OPEN;
ALTER PLUGGABLE DATABASE ORCLPDB1 SAVE STATE;

Repeat this cycle for PDB2 and PDB3.


Step 8 — Add PDB-Level Standby Redo Logs

This is another step the documentation buries. EDIT CONFIGURATION PREPARE DGPDB does NOT create standby redo logs inside the target PDB. Without them, apply runs against archived logs only — defeating the purpose of real-time transport.

You must add standby redo logs inside the mounted standby PDB. To do that, connect to CDB$ROOT and switch container:

First, stop apply so Oracle doesn't object:

dgmgrl /@ORCL2
EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI SET STATE=APPLY-OFF;

Then add the logs:

ALTER SESSION SET CONTAINER = ORCLPDB1;

ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;
ALTER DATABASE ADD STANDBY LOGFILE SIZE 200M;

SELECT GROUP#, BYTES/1024/1024 MB, STATUS FROM V$STANDBY_LOG;

Re-enable apply:

EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI SET STATE=APPLY-ON;

Repeat for each standby PDB.


Step 9 — Enable Redo Transport on Source Side

Enable transport from the primary PDB to the standby:

dgmgrl /@ORCL
EDIT PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI SET STATE=TRANSPORT-ON;

Step 10 — Initial Log Switch (One-Time Catch-Up)

At this point the standby has a copy of the datafiles from when the PDB was closed. The redo generated between the close and now is sitting in online redo logs on the primary. Force it to archive and ship:

-- On oracle-primary
ALTER SYSTEM SWITCH LOGFILE;
ALTER SYSTEM SWITCH LOGFILE;

After this, real-time redo transport via LGWR → LNS → RFS → SRL → MRP kicks in automatically. You will not need manual log switches going forward.


Step 11 — Verify the Setup

dgmgrl /@ORCL2
SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI

Healthy standby output:

Pluggable database - ORCLPDB1 at orcl2_pri
  Data Guard Role:     Physical Standby
  Con_ID:              3
  Source:              con_id 3 at ORCL_PRI
  Transport Lag:       6 seconds (computed 5 seconds ago)
  Apply Lag:           6 seconds (computed 5 seconds ago)
  Intended State:      APPLY-ON
  Apply State:         Running
  Apply Instance:      ORCL2
  Average Apply Rate:  67 KByte/s
  Real Time Query:     ON
Pluggable Database Status:
SUCCESS

What you want to see:

  • Data Guard Role: Physical Standby ✅

  • Transport Lag: Single-digit seconds ✅

  • Apply State: Running ✅

  • Real Time Query: ON ✅ (means PDB can be opened read-only for Active Data Guard queries)

Check the transport and apply processes directly:

-- From CDB$ROOT
SELECT PROCESS, STATUS, CLIENT_PROCESS, SEQUENCE#, BLOCK#
FROM V$MANAGED_STANDBY
ORDER BY PROCESS;
LNS  WRITING    LNS    18  5862   ← primary shipping redo
RFS  IDLE       LGWR   18  6624   ← standby receiving
MRP0 WAIT_FOR_LOG N/A  18     0   ← standby apply waiting for next record

This three-process chain confirms real-time redo transport is active.


Step 12 — Perform a PDB Switchover (only one without affecting other PDBs).

This is the moment DGPDB was built for.

dgmgrl /@ORCL2
SWITCHOVER TO PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI;
Performing switchover NOW, please wait...
Switchover succeeded, new primary is "orclpdb1"

Verify the roles reversed:

SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL2_PRI
-- Data Guard Role: Primary   ← ORCL2 is now primary for ORCLPDB1

SHOW PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI
-- Data Guard Role: Physical Standby   ← ORCL is now standby for ORCLPDB1

Check that PDB2 and PDB3 were not affected:

SHOW PLUGGABLE DATABASE PDB2 AT ORCL_PRI
-- Data Guard Role: Primary   ← unchanged

This is the core value of DGPDB — ORCLPDB1 switched roles completely independently. PDB2 and PDB3 didn't notice.

Switchover back:

SWITCHOVER TO PLUGGABLE DATABASE ORCLPDB1 AT ORCL_PRI;

What DGPDB Does NOT Support

Set expectations correctly before taking this to production:

Feature Available?
Maximum Performance (ASYNC)
Maximum Availability / Protection (SYNC)
Real-time apply from SRLs ✅ (confirmed)
Far Sync instances
Multiple standby targets per PDB ❌ (one target CDB only)
Rolling upgrades via DBMS_ROLLING
Snapshot Standby
GoldenGate downstream capture
Application Containers

Since only ASYNC transport is supported, there is potential data loss on failover. Mitigate with:

ALTER SYSTEM SET ARCHIVE_LAG_TARGET=30; -- max 30-second lag

Key Takeaways for DBAs

  1. Two primary CDBs — not primary + standby. Both are OPEN READ WRITE.

  2. Broker is mandatorydgmgrl for everything. No manual SQL equivalent exists.

  3. Run EDIT CONFIGURATION PREPARE DGPDB — the step the docs bury. Without it, ADD PLUGGABLE DATABASE always fails with ORA-16922.

  4. PREPARE DATABASE needs bequeath + SYSDBA + RESTARTdgmgrl / as sysdba with the RESTART keyword.

  5. Add PDB-level standby redo logs manuallyEDIT CONFIGURATION PREPARE DGPDB doesn't create them inside the PDB.

  6. cmd.exe for Docker tar pipes on Windows — PowerShell corrupts binary streams.

  7. One log switch after setup — clears the backlog from the offline period. After that, real-time transport runs continuously.

  8. V\(DATABASE.DATABASE_ROLE = 'PRIMARY' on both CDBs — always. PDB role is only visible via SHOW PLUGGABLE DATABASE in dgmgrl or V\)PDBS.OPEN_MODE (MOUNTED = standby PDB).

  9. Standby PDB cannot be opened read-write manually — ORA-65339 if you try. Only switchover/failover via broker can change its role.

  10. Switchover is genuinely independent — switching one PDB doesn't affect others. This is the whole point.


Conclusion

DGPDB is a meaningful step forward for Oracle multitenant HA. It's not for every scenario — the lack of SYNC transport and the one-target-per-PDB limitation are real constraints — but for workloads that need independent PDB-level failover without moving the entire CDB, it delivers.

The four steps that consistently trip people up are: the connection type for PREPARE DATABASE, running EDIT CONFIGURATION PREPARE DGPDB, adding PDB-level standby redo logs manually, and the datafile copy sequence. Get those right and the rest follows logically.


Tested on Oracle 26ai (23.26.1.0.0) — Oracle Linux 8 — Docker Desktop on Windows
All switchovers confirmed working including independent PDB-level role reversal