Oracle Data Guard Active Data Guard (ADG) Offloading Strategy for Reporting Workloads

Production guide for enterprise DBAs and engineers.

⚡ BLUF (Bottom Line Up Front) Summary

⚠️ Advisory Scope & Terms

Active Data Guard (ADG) offloading shifts read-only reporting, BI, and export workloads from the primary database to a synchronized physical standby using Real-Time Query, cutting primary CPU and I/O by 60–80% for read-heavy fleets. Success hinges on: (1) licensed ADG with a physical standby in READ ONLY WITH APPLY state, (2) SYNC/ASYNC redo transport with zero or bounded data loss, (3) standby redo logs, TEMP tablespaces, and UNDO sized for reporting concurrency, (4) role-based services (ROLE=PHYSICAL_STANDBY) with JDBC/OCI failover, and (5) continuous validation of APPLY_LAG, TRANSPORT_LAG, and standby AWR. ORA-00600 errors during broker reconfiguration or redo apply, compounded by patch drift between primary and standby, require aligned patch levels across both sites — verified via DBA_REGISTRY_SQLPATCH — before offloading is enabled. Root cause of most ADG offloading failures is not the standby engine but misconfigured services, missing TEMP/UNDO, or patch drift between primary and standby.

Environment & Prerequisites

ComponentVersion / Specification
Database EditionOracle Database Enterprise Edition 19c / 23ai
Licensing RequirementOracle Active Data Guard (ADG) Option
Replication TopologyPhysical Standby in READ ONLY WITH APPLY mode
Network TransportSYNC / ASYNC Redo Transport with Standby Redo Logs
Required PrivilegesSYSDBA / DGMGRL CLI access

Executive Summary

Active Data Guard (ADG) is Oracle’s flagship disaster-recovery and workload-offloading technology. By opening a physical standby database in READ ONLY WITH APPLY mode, ADG enables real-time query (RTQ) access to a transactionally consistent snapshot of the primary while redo is continuously applied. For enterprises running heavy reporting, BI, data-extract, and analytical workloads, ADG offloading is the most cost-effective way to reclaim primary CPU, I/O, and memory without licensing a second primary database.

This guide provides a production-ready strategy for designing, validating, and operating an ADG offloading architecture. It covers the diagnostic commands needed to assess readiness, a step-by-step runbook for implementation, and the operational guardrails that prevent the two most common failure modes: apply-lag-induced snapshot staleness and patch drift — including ORA-00600 internal errors that can surface during broker reconfiguration or redo apply when primary and standby run divergent patch levels.

ADG offloading is not a “flip a switch” feature. It requires deliberate design of redo transport, standby redo logs, TEMP/UNDO sizing, role-based services, and monitoring. Done correctly, it transforms a passive DR standby into an active asset that absorbs the majority of read traffic. Done incorrectly, it produces stale reports, ORA-00600 internal errors, and a standby that cannot keep up with apply.

1. Overview

ADG offloading is a layered architecture that depends on five components working in concert:

  1. Licensed ADG option with a physical standby in READ ONLY WITH APPLY state.
  2. Redo transport configured as SYNC or ASYNC with zero or bounded data loss.
  3. Standby redo logs, TEMP tablespaces, and UNDO sized for reporting concurrency.
  4. Role-based services (ROLE=PHYSICAL_STANDBY) with JDBC/OCI failover.
  5. Continuous validation of APPLY_LAG, TRANSPORT_LAG, and standby AWR snapshots.

The implementation sequence follows a six-phase process:

1
Assess
2
Configure
3
Validate
4
Route
5
Monitor
6
Optimize

Phase 1 — Assess: Inventory reporting workloads, quantify primary CPU/IO, and confirm ADG licensing and standby topology. Phase 2 — Configure: Add standby redo logs, TEMP tablespaces, and UNDO; verify READ ONLY WITH APPLY and redo transport health. Phase 3 — Validate: Confirm zero/bounded data loss, APPLY_LAG within SLA, and patch alignment (DBA_REGISTRY_SQLPATCH) across both sites. Phase 4 — Route: Create role-based services (ROLE=PHYSICAL_STANDBY) and point reporting connections at the standby service. Phase 5 — Monitor: Track apply lag, standby event histograms, TEMP/UNDO pressure, and standby AWR snapshots continuously. Phase 6 — Optimize: Tune apply processes, parallel query, and service failover; revisit sizing as reporting concurrency grows.

2. Diagnostic Checklist

Run the following checks before designing or enabling ADG offloading. Each command should be executed on the appropriate host (primary, standby, or both) and the output recorded as a baseline.

2.1 Topology & Role Verification

# On BOTH primary and standby, as the grid infrastructure owner:
dgmgrl sys/"<password>"@<connect_identifier>
DGMGRL> SHOW CONFIGURATION;
DGMGRL> SHOW DATABASE '<primary_db_unique_name>';
DGMGRL> SHOW DATABASE '<standby_db_unique_name>';
-- On BOTH primary and standby:
SELECT database_role, open_mode, protection_mode, protection_level,
       db_unique_name, force_logging
FROM   v$database;

SELECT inst_id, instance_name, status, database_status
FROM   gv$instance;

-- Confirm the standby is in the correct ADG state:
SELECT open_mode, recovery_status
FROM   v$database
WHERE  database_role = 'PHYSICAL STANDBY';
-- Expected: OPEN_MODE = READ ONLY WITH APPLY, RECOVERY_STATUS = ACTIVE

2.2 Redo Transport & Apply Lag

-- On the STANDBY:
SELECT name, value, unit, time_computed
FROM   v$dataguard_stats
WHERE  name IN ('transport lag', 'apply lag', 'apply finish time');

-- On the PRIMARY:
SELECT dest_id, destination, status, error, gap_status,
       redo_transport_mode, net_timeout
FROM   v$archive_dest
WHERE  dest_id > 1;

-- On the STANDBY — apply throughput and bottleneck events:
SELECT event, total_waits, time_waited_micro, average_wait
FROM   v$standby_event_histogram
WHERE  total_waits > 0
ORDER  BY time_waited_micro DESC;

-- On the STANDBY — redo apply rate (MB/s):
SELECT TO_CHAR(COMPLETION_TIME, 'DD-MON-YY HH24:MI') AS applied_at,
       ROUND(SUM(BLOCKS * BLOCK_SIZE) / 1024 / 1024, 2) AS mb_applied
FROM   v$archived_log
WHERE  applied = 'YES'
  AND  completion_time > SYSDATE - 1
GROUP  BY TO_CHAR(COMPLETION_TIME, 'DD-MON-YY HH24:MI')
ORDER  BY 1;

2.3 Standby Readiness (TEMP, UNDO, Datafiles)

-- On the STANDBY:
SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024/1024, 2) AS size_gb,
       ROUND(SUM(user_bytes)/1024/1024/1024, 2) AS used_gb
FROM   dba_temp_files
GROUP  BY tablespace_name;

-- TEMP usage by active reporting sessions:
SELECT tablespace_name, ROUND(SUM(blocks*block_size)/1024/1024, 2) AS temp_mb
FROM   v$tempseg_usage
GROUP  BY tablespace_name;

-- UNDO on standby (ADG uses local undo for read-consistent queries):
SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024/1024, 2) AS undo_gb
FROM   dba_data_files
WHERE  tablespace_name IN (SELECT tablespace_name FROM dba_tablespaces WHERE contents = 'UNDO')
GROUP  BY tablespace_name;

-- Datafile status on standby:
SELECT file#, status, ROUND(bytes/1024/1024/1024, 2) AS size_gb,
       checkpoint_time, last_change_time
FROM   v$datafile
WHERE  status NOT IN ('ONLINE', 'SYSTEM');

2.4 Service & Connection Routing

-- On the STANDBY — verify reporting services are registered:
SELECT name, network_name, creation_date, failover_type,
       failover_method, goal, clb_goal
FROM   dba_services
WHERE  name LIKE '%REPORT%' OR name LIKE '%ADG%';

-- On the STANDBY — active sessions using the reporting service:
SELECT inst_id, service_name, COUNT(*) AS session_count,
       ROUND(SUM(cpu_time)/1000000, 2) AS cpu_sec
FROM   gv$active_session_history
WHERE  sample_time > SYSDATE - 1/24
GROUP  BY inst_id, service_name
ORDER  BY 3 DESC;

2.5 Patch & Security Alignment

-- On BOTH primary and standby — compare output side-by-side:
SELECT patch_id, patch_type, action, status,
       TO_CHAR(action_time, 'DD-MON-YYYY HH24:MI') AS applied_at
FROM   dba_registry_sqlpatch
ORDER  BY action_time;

-- On BOTH — check Data Guard broker / redo transport component status:
SELECT comp_id, comp_name, version, status
FROM   dba_registry
WHERE  comp_id IN ('OAS', 'CATALOG', 'APS');

-- On BOTH — confirm the latest Quarterly CPU/PSU patch is applied:
SELECT patch_id, description, action, status
FROM   dba_registry_sqlpatch
WHERE  action = 'APPLY'
ORDER  BY action_time DESC
FETCH FIRST 5 ROWS ONLY;

2.6 Workload Measurement (Baseline)

-- On the PRIMARY — quantify the read workload to be offloaded:
SELECT s.service_name,
       ROUND(SUM(s.cpu_time)/1000000, 2) AS cpu_sec,
       ROUND(SUM(s.physical_read_bytes)/1024/1024/1024, 2) AS read_gb,
       COUNT(DISTINCT s.sample_time) AS samples
FROM   gv$active_session_history s
WHERE  s.session_type = 'FOREGROUND'
  AND  s.sample_time > SYSDATE - 7
GROUP  BY s.service_name
ORDER  BY 2 DESC;

-- On the PRIMARY — top SQL by physical reads (candidates for offloading):
SELECT sql_id, ROUND(SUM(physical_read_bytes)/1024/1024/1024, 2) AS read_gb,
       COUNT(*) AS executions
FROM   gv$sql
WHERE  command_type IN (3, 47)  -- SELECT and PL/SQL
GROUP  BY sql_id
ORDER  BY 2 DESC
FETCH FIRST 20 ROWS ONLY;

3. Step-by-Step Resolution Runbook

Step 0: Safety Checks

Before making any change, establish a safety baseline. ADG offloading touches redo transport, standby state, and connection routing — all of which are failover-critical.

# 0.1 — Verify primary is healthy and force logging is enabled:
sqlplus / as sysdba
SELECT force_logging, supplemental_log_data_pk, supplemental_log_data_all
FROM   v$database;
# Expected: FORCE_LOGGING = YES (mandatory for Data Guard)

# 0.2 — Verify a recent backup exists (RMAN):
rman target /
RMAN> LIST BACKUP OF DATABASE COMPLETED AFTER 'SYSDATE-2';

# 0.3 — Verify the standby is currently in a consistent state:
dgmgrl sys/"<password>"@<primary_connect>
DGMGRL> SHOW CONFIGURATION;
# Expected: SUCCESS state for both primary and standby

# 0.4 — Confirm no failover or switchover is in progress:
DGMGRL> SHOW DATABASE '<standby_db_unique_name>' 'StateReport';

Safety gate: If any of the above fails, stop and resolve before proceeding. Never enable ADG offloading on a standby with unresolved redo gaps or an unhealthy broker configuration.

Step 1: Validate Licensing & Prerequisites

Active Data Guard is a licensed option (Oracle Enterprise Edition + ADG option). Confirm entitlement before implementation.

# 1.1 — Confirm the ADG option is enabled:
sqlplus / as sysdba
SELECT value FROM v$option WHERE parameter = 'Active Data Guard';
# Expected: TRUE

# 1.2 — Confirm the standby is a physical standby (not logical):
SELECT database_role FROM v$database;
# Expected: PHYSICAL STANDBY

# 1.3 — Confirm the standby is open in ADG mode:
SELECT open_mode FROM v$database;
# Expected: READ ONLY WITH APPLY

If the standby is in MOUNTED mode, open it for ADG:

-- On the STANDBY:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;
ALTER DATABASE OPEN;
-- Then re-enable managed recovery:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;

Step 2: Configure Standby Redo Logs and TEMP

ADG reporting workloads consume TEMP aggressively (sorting, hash joins, parallel query) and require sufficient standby redo logs to absorb peak redo generation.

-- 2.1 — Add standby redo log groups on the STANDBY.
-- Rule of thumb: at least one more group than primary online redo logs,
-- and each SRL sized >= the largest primary online redo log.
ALTER DATABASE ADD STANDBY LOGFILE GROUP 11 ('/u01/oradata/STBY/srl11a.log') SIZE 2G;
ALTER DATABASE ADD STANDBY LOGFILE GROUP 12 ('/u01/oradata/STBY/srl12a.log') SIZE 2G;
ALTER DATABASE ADD STANDBY LOGFILE GROUP 13 ('/u01/oradata/STBY/srl13a.log') SIZE 2G;
ALTER DATABASE ADD STANDBY LOGFILE GROUP 14 ('/u01/oradata/STBY/srl14a.log') SIZE 2G;

-- Verify:
SELECT group#, thread#, sequence#, bytes, status
FROM   v$standby_log;

-- 2.2 — Add TEMP tablespaces on the STANDBY for reporting concurrency.
-- Size for the peak concurrent reporting sort/hash area, not the primary's TEMP.
CREATE TEMPORARY TABLESPACE temp_report
  TEMPFILE '/u01/oradata/STBY/temp_report01.dbf' SIZE 32G AUTOEXTEND ON NEXT 8G MAXSIZE 128G;

-- 2.3 — Add a dedicated UNDO tablespace on the standby if reporting
-- queries are long-running and primary UNDO is undersized.
CREATE UNDO TABLESPACE undotbs_adg
  DATAFILE '/u01/oradata/STBY/undotbs_adg01.dbf' SIZE 16G AUTOEXTEND ON NEXT 4G MAXSIZE 64G;

-- 2.4 — Assign the new TEMP as the default for the reporting service users:
ALTER DATABASE DEFAULT TEMPORARY TABLESPACE temp_report;

Step 3: Create Role-Based Reporting Services

Services are the linchpin of ADG offloading. A service with ROLE=PHYSICAL_STANDBY is only available on the standby; when a switchover occurs, the service automatically relocates to the new primary.

-- 3.1 — On the PRIMARY, create the reporting service (it will be
-- automatically propagated to the standby via the broker):
EXEC DBMS_SERVICE.CREATE_SERVICE(
  service_name => 'REPORT_ADG',
  network_name => 'REPORT_ADG',
  goal         => DBMS_SERVICE.GOAL_SERVICE_TIME,
  clb_goal     => DBMS_SERVICE.CLB_GOAL_LONG,
  failover_method => DBMS_SERVICE.FAILOVER_METHOD_BASIC,
  failover_type   => DBMS_SERVICE.FAILOVER_TYPE_SELECT,
  failover_retries => 30,
  failover_delay   => 5
);

-- 3.2 — Register the service with the Data Guard broker for role-based
-- startup on the standby:
dgmgrl sys/"<password>"@<primary_connect>
DGMGRL> EDIT DATABASE '<standby_db_unique_name>' SET PROPERTY 'ServiceRegistrationEnabled' = 'TRUE';
DGMGRL> EDIT DATABASE '<standby_db_unique_name>' SET PROPERTY 'StandbyFileManagement' = 'AUTO';

-- 3.3 — Start the service on the standby:
sqlplus / as sysdba
ALTER SYSTEM SET SERVICE_NAMES = 'REPORT_ADG' SCOPE = BOTH;

Step 4: Route Reporting Workloads

Point reporting connections at the standby service. Use connection strings that resolve to the standby host and include failover to the primary in case of switchover.

# 4.1 — JDBC connection string for reporting applications:
# jdbc:oracle:thin:@(DESCRIPTION=
#   (ADDRESS=(PROTOCOL=TCP)(HOST=standby-host)(PORT=1521))
#   (CONNECT_DATA=(SERVICE_NAME=REPORT_ADG)))

# 4.2 — Oracle Net (tnsnames.ora) entry:
REPORT_ADG =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = standby-host)(PORT = 1521))
    (CONNECT_DATA =
      (SERVICE_NAME = REPORT_ADG)
      (FAILOVER_MODE =
        (TYPE = SELECT)
        (METHOD = BASIC)
        (RETRIES = 30)
        (DELAY = 5))))
-- 4.3 — Verify sessions are landing on the standby:
-- On the STANDBY:
SELECT inst_id, username, service_name, program, module
FROM   gv$session
WHERE  service_name = 'REPORT_ADG'
  AND  type = 'USER';

-- 4.4 — Confirm no reporting sessions remain on the primary:
-- On the PRIMARY:
SELECT service_name, COUNT(*) AS session_count
FROM   gv$session
WHERE  service_name = 'REPORT_ADG'
GROUP  BY service_name;
-- Expected: 0 rows

Step 5: Validate Offloading Effectiveness

After routing, measure the actual reduction in primary load and confirm the standby is keeping up.

-- 5.1 — On the PRIMARY, compare before/after host CPU utilization:
SELECT ROUND((b.value / (b.value + i.value)) * 100, 2) AS cpu_busy_pct
FROM   (SELECT value FROM v$osstat WHERE stat_name = 'BUSY_TIME') b,
       (SELECT value FROM v$osstat WHERE stat_name = 'IDLE_TIME') i;

-- 5.2 — On the STANDBY, confirm apply lag is within SLA:
SELECT name, value
FROM   v$dataguard_stats
WHERE  name IN ('transport lag', 'apply lag');

-- 5.3 — On the STANDBY, capture an AWR snapshot for baseline:
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();

-- 5.4 — On the PRIMARY, confirm the top reporting SQL is no longer executing:
SELECT sql_id, executions, ROUND(elapsed_time/1000000, 2) AS elapsed_sec
FROM   v$sql
WHERE  sql_id IN ('<sql_id_1>', '<sql_id_2>')
  AND  parsing_schema_name NOT IN ('SYS', 'SYSTEM');

Step 6: Monitor & Tune

ADG offloading is an ongoing operation. Establish monitoring thresholds and tune apply performance.

-- 6.1 — Create a monitoring query for apply lag (run every 5 minutes):
SELECT TO_CHAR(SYSDATE, 'DD-MON-YY HH24:MI:SS') AS check_time,
       name, value
FROM   v$dataguard_stats
WHERE  name IN ('transport lag', 'apply lag')
UNION ALL
SELECT TO_CHAR(SYSDATE, 'DD-MON-YY HH24:MI:SS'),
       'apply_finish_time',
       value
FROM   v$dataguard_stats
WHERE  name = 'apply finish time';

-- 6.2 — Tune apply parallelism if apply lag is persistent:
-- On the STANDBY (requires restart of managed recovery):
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION PARALLEL 16;
-- Note: PARALLEL applies to 19c; in 23ai, use
-- ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION USING PARALLELISM 16;

-- 6.3 — Monitor TEMP pressure from reporting queries:
SELECT tablespace_name, ROUND(SUM(blocks*block_size)/1024/1024, 2) AS temp_mb,
       COUNT(*) AS active_sorts
FROM   v$tempseg_usage
GROUP  BY tablespace_name
HAVING ROUND(SUM(blocks*block_size)/1024/1024, 2) > 1024;

-- 6.4 — Set up alerts for lag thresholds (via OEM or custom script):
-- Alert when APPLY_LAG > 30 seconds or TRANSPORT_LAG > 15 seconds.

Step 7: Patch Alignment for ORA-00600 Prevention

Patch drift between primary and standby is a leading cause of ORA-00600 internal errors during broker reconfiguration, redo transport failover, or apply. The most common symptom is:

ORA-00600: internal error code, arguments: [krsu_validate_conn], [0], ...

followed by broker state transitions to UNKNOWN and redo transport gaps.

# 7.1 — Verify patch alignment on BOTH primary and standby:
sqlplus / as sysdba
SELECT patch_id, action, status, TO_CHAR(action_time, 'DD-MON-YYYY') AS applied_on
FROM   dba_registry_sqlpatch
ORDER  BY action_time;

# 7.2 — If the latest Quarterly CPU/PSU patch is missing on either site,
# apply it during the next maintenance window. Use OPatch:
#   $ORACLE_HOME/OPatch/opatch apply -oh $ORACLE_HOME /path/to/patch
#   $ORACLE_HOME/OPatch/opatch lsinventory

# 7.3 — After patching, restart the Data Guard broker on both sites:
dgmgrl sys/"<password>"@<primary_connect>
DGMGRL> DISABLE CONFIGURATION;
DGMGRL> ENABLE CONFIGURATION;
DGMGRL> SHOW CONFIGURATION;
# Expected: SUCCESS for both databases

# 7.4 — If ORA-00600 persists after patching, collect diagnostics:
#   - Alert log on both sites
#   - trc files from the DIAG destination
#   - dgmgrl SHOW CONFIGURATION VERBOSE
#   - MOS Note 1061463.1 (ORA-00600 troubleshooting) and Note 209768.1

Step 8: Rollback / Fallback Plan

Every production change needs a rollback path. If ADG offloading causes instability, revert in this order:

# 8.1 — Stop the reporting service on the standby:
sqlplus / as sysdba
ALTER SYSTEM SET SERVICE_NAMES = '' SCOPE = BOTH;

# 8.2 — Redirect reporting connections back to the primary service:
# Update tnsnames.ora / JDBC URLs to point to the primary service name.

# 8.3 — If the standby itself is unstable, stop managed recovery and
# return it to MOUNTED mode:
ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;

# 8.4 — Verify the primary is fully healthy and the broker is clean:
dgmgrl sys/"<password>"@<primary_connect>
DGMGRL> SHOW CONFIGURATION;
DGMGRL> SHOW DATABASE '<primary_db_unique_name>' 'StateReport';

Rollback gate: Do not leave the standby in a half-configured state. Either the standby is in READ ONLY WITH APPLY with the reporting service active, or it is in MOUNTED with managed recovery running. Any other state is a DR risk.

📚 Official Documentation & Technical References

Oracle Documentation

My Oracle Support


Need help designing or troubleshooting your ADG offloading architecture? Our enterprise database engineers have deployed ADG offloading for Fortune 500 financial, healthcare, and retail environments. We handle architecture design, patch alignment, performance tuning, and 24/7 incident response.

Contact our Oracle experts · Explore our database services

⚠️INFORMATIONAL & TECHNICAL ADVISORY DISCLAIMER

The diagnostic methodologies, commands, and runbooks provided on DBPros.Net are published for informational and educational purposes only. They do not constitute customized professional consulting advice. Operating engineers and DBAs are solely responsible for securing pre-flight backups (RMAN, VM snapshots, LVM clones), validating changes in non-production staging environments, and adhering to organizational change-control policies. All content, scripts, and runbooks are provided "AS IS" without warranty of any kind, and DBPros.Net assumes no liability for system downtime, database corruption, data loss, or operational disruption. For complete advisory limitations and legal terms, view our full Terms of Service & Advisory Disclaimer.