OCI GoldenGate: Real-Time Replication and Zero-Downtime Migration

Moving large databases without downtime requires log-based change data capture. OCI GoldenGate reads Oracle redo logs and replicates every committed transaction to the target in real time. When the target is current, cutover takes the time it takes to drain the remaining lag, which is typically seconds rather than hours. This post covers provisioning OCI GoldenGate with Terraform, preparing the source database, monitoring lag, and executing a clean cutover.

Step 1: Provision the GoldenGate Deployment

resource "oci_golden_gate_deployment" "production" {
  compartment_id          = var.compartment_id
  display_name            = "oracle-to-postgresql"
  license_model           = "LICENSE_INCLUDED"
  deployment_type         = "DATABASE_ORACLE"
  cpu_core_count          = 2
  is_auto_scaling_enabled = false
  subnet_id               = var.private_subnet_id
  nsg_ids                 = [var.goldengate_nsg_id]

  ogg_data {
    admin_username  = "ggadmin"
    admin_password  = var.gg_admin_password
    deployment_name = "ORACLE_TO_PG"
  }

  defined_tags = {
    "Operations.Environment" = "production"
    "Operations.ManagedBy"   = "terraform"
  }
}

output "deployment_url" {
  value = oci_golden_gate_deployment.production.deployment_url
}

Step 2: Register Database Connections

resource "oci_golden_gate_connection" "source_oracle" {
  compartment_id  = var.compartment_id
  display_name    = "source-oracle"
  connection_type = "ORACLE"
  technology_type = "ORACLE_DATABASE"
  username        = "gguser"
  password        = var.oracle_gg_password
  wallet          = var.oracle_wallet_base64
  subnet_id       = var.private_subnet_id
  nsg_ids         = [var.goldengate_nsg_id]
}

resource "oci_golden_gate_connection" "target_postgres" {
  compartment_id  = var.compartment_id
  display_name    = "target-postgresql"
  connection_type = "POSTGRESQL"
  technology_type = "POSTGRESQL"
  host            = var.pg_host
  port            = 5432
  database_name   = "analytics"
  username        = "gg_replicat"
  password        = var.pg_gg_password
  ssl_mode        = "VERIFY_CA"
  subnet_id       = var.private_subnet_id
}

Step 3: Prepare the Source Oracle Database

-- Enable supplemental logging at database level
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;

-- Column-level supplemental logging for each replicated table
ALTER TABLE orders.order_header ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;
ALTER TABLE orders.order_lines   ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

-- GoldenGate database user
CREATE USER gguser IDENTIFIED BY StrongPassw0rd;
GRANT CREATE SESSION, RESOURCE, SELECT ANY DICTIONARY TO gguser;
GRANT SELECT ANY TABLE, FLASHBACK ANY TABLE TO gguser;
GRANT EXECUTE ON DBMS_FLASHBACK TO gguser;
GRANT LOGMINING TO gguser;
GRANT SELECT ON SYS.V_$DATABASE TO gguser;
GRANT SELECT ON SYS.V_$LOG, SYS.V_$ARCHIVED_LOG TO gguser;

-- Enable GoldenGate replication parameter
ALTER SYSTEM SET enable_goldengate_replication = TRUE SCOPE=BOTH;

-- Confirm supplemental logging is active
SELECT log_mode, supplemental_log_data_min FROM v$database;

Step 4: Monitor Lag with Alarms

resource "oci_monitoring_alarm" "gg_lag_high" {
  compartment_id        = var.compartment_id
  display_name          = "goldengate-lag-high"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_golden_gate"
  query                 = "ExtractLag[5m]{deploymentId = '${oci_golden_gate_deployment.production.id}'}.max() > 60"
  severity              = "WARNING"
  pending_duration      = "PT5M"
  destinations          = [var.ops_notification_topic_id]
  body                  = "GoldenGate lag above 60 seconds. Check network throughput and Extract performance."
}

resource "oci_monitoring_alarm" "gg_abended" {
  compartment_id        = var.compartment_id
  display_name          = "goldengate-extract-abended"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_golden_gate"
  query                 = "ExtractStatus[5m]{deploymentId = '${oci_golden_gate_deployment.production.id}', status = 'ABENDED'}.max() > 0"
  severity              = "CRITICAL"
  pending_duration      = "PT2M"
  destinations          = [var.ops_notification_topic_id]
  body                  = "GoldenGate Extract has abended. Replication is stopped. Investigate immediately."
}

Step 5: Cutover Checklist

# Check deployment and process status
oci golden-gate deployment get \
  --deployment-id ${DEPLOYMENT_ID} \
  --query 'data.{state:"lifecycle-state", fqdn:fqdn}'

# Cutover steps:
# 1. Stop new writes to Oracle source (put app in maintenance mode)
# 2. Monitor lag until it drops to zero
#    Watch: oci monitoring get-metrics ... ExtractLag
# 3. Stop Extract and Replicat from GoldenGate Admin console
# 4. Verify row counts match
#    Oracle:     SELECT COUNT(*) FROM orders.order_header;
#    PostgreSQL: SELECT COUNT(*) FROM public.order_header;
# 5. Check sequence/identity values are ahead on PostgreSQL
# 6. Update application connection strings to PostgreSQL
# 7. Bring application out of maintenance mode
# 8. Verify application health against PostgreSQL
# 9. Keep GoldenGate running in a stopped state for 48 hours before decommissioning

Step 6: Monitor Lag via Python

import oci
from datetime import datetime, timezone, timedelta

def get_gg_lag(deployment_id: str, compartment_id: str) -> float:
    config  = oci.config.from_file()
    client  = oci.monitoring.MonitoringClient(config)
    now     = datetime.now(timezone.utc)

    resp = client.summarize_metrics_data(
        compartment_id=compartment_id,
        summarize_metrics_data_details=oci.monitoring.models.SummarizeMetricsDataDetails(
            namespace="oci_golden_gate",
            query=f"ExtractLag[5m]{{deploymentId = '{deployment_id}'}}.max()",
            start_time=(now - timedelta(minutes=10)).isoformat(),
            end_time=now.isoformat()
        )
    )

    items = resp.data
    if items and items[0].aggregated_datapoints:
        return items[0].aggregated_datapoints[-1].value
    return -1

lag = get_gg_lag(
    deployment_id="ocid1.ggdeployment.oc1..yourdeploymentocid",
    compartment_id="ocid1.compartment.oc1..yourcompartmentocid"
)
status = "READY" if 0 <= lag = 10 else "NO_DATA"
print(f"Lag: {lag:.1f}s | Status: {status}")

Operational Notes

Enable supplemental logging at the table level for the specific tables you are replicating, not globally at the database level. Database-level supplemental logging adds overhead to every redo log write for every table, even ones you are not replicating. Table-level logging scopes the overhead to only what GoldenGate needs.

Do not cut over until lag has been consistently below 10 seconds for at least 30 minutes under production load. A lag that spikes during peak hours means the GoldenGate deployment does not have enough CPU to keep up at peak throughput. Scale the deployment CPU count before scheduling the cutover window.

Regards,
Osama

#OCI #OracleCloud #GoldenGate #Replication #ZeroDowntime #Migration #Terraform #TechBlog #Oracle #DatabaseEngineering #CDC #ChangeDataCapture #OracleDatabase #PostgreSQL #OracleCloudInfrastructure #DataMigration #PlatformEngineering #OracleDBA #RealTimeData #CloudDatabase

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.