GoldenGate bi-direction Sequences

In active-active Oracle GoldenGate environments, where data is replicated in both directions between two or more databases, maintaining data consistency is critical. One often-overlooked source of conflict is Oracle SEQUENCE objects. If not handled correctly, sequences can generate duplicate values across replicated sites, resulting in data integrity issues, constraint violations, or silent data corruption.

In this post, we’ll cover a proven best practice to eliminate this risk: configuring sequences to generate non-overlapping values using odd-even partitioning.


The Problem: Sequence Conflicts in Active-Active Replication

Oracle SEQUENCE objects are commonly used to auto-generate primary key values. In a unidirectional GoldenGate setup, this poses no issue since the source is the sole generator of new values. However, in bi-directional (active-active) setups, both sites can generate new rows independently. If both sites use the exact sequence definition, collisions are inevitable.

For example:

  • Site A inserts a new row using MY_SEQ.NEXTVAL → value: 1001
  • Site B inserts a new row using the same MY_SEQ.NEXTVAL → value: 1001
  • Conflict occurs when replication attempts to insert the same value in both sites

The Solution: Odd-Even Sequence Partitioning

To avoid this, each site should generate values from disjoint sets. A simple and effective technique is to:

  • Set INCREMENT BY 2 on all sequences
  • Set Site A to use odd starting values
  • Set Site B to use even starting values

This way, Site A will generate: 1001, 1003, 1005…
And Site B will generate: 1002, 1004, 1006…

Since the two sets of values are mutually exclusive, no conflict can occur.


To streamline this process and minimize manual intervention, I developed a script that automatically generates DDLs to convert existing sequences into a format suitable for bi-directional replication.

The script analyzes each sequence’s current value and cache settings, calculates the appropriate starting point to avoid overlaps, and recreates the sequence with an INCREMENT BY 2. It then generates two versions of each sequence: one configured to produce odd values for Site A and another for even values for Site B. This ensures that each site generates non-overlapping values, effectively preventing primary key conflicts during active-active replication.

Here is the example script, you can always improve it for multiple sites, as I will describe below.

DROP SEQUENCE MY_SEQ;
CREATE SEQUENCE MY_SEQ START WITH 253 INCREMENT BY 1 CACHE 20 NOCYCLE;
DROP SEQUENCE MY_SEQ2;
CREATE SEQUENCE MY_SEQ2 START WITH 23075 INCREMENT BY 1 CACHE 20 NOCYCLE;

SET SERVEROUTPUT ON SIZE UNLIMITED
DECLARE
  v_owner           VARCHAR2(128) := 'HR';  -- Change to your schema
  v_nextval         NUMBER;
  v_cache_size      NUMBER;
  v_reserved_max    NUMBER;
  v_odd_start       NUMBER;
  v_even_start      NUMBER;
  v_odd_ddl         VARCHAR2(1000);
  v_even_ddl        VARCHAR2(1000);
BEGIN
  FOR seq_rec IN (
    SELECT sequence_name, cache_size
    FROM all_sequences
    WHERE sequence_owner = UPPER(v_owner)
    ORDER BY sequence_name
  )
  LOOP
    -- Dynamically get NEXTVAL for the sequence
    EXECUTE IMMEDIATE
      'SELECT ' || v_owner || '.' || seq_rec.sequence_name || '.NEXTVAL FROM dual'
      INTO v_nextval;

    v_cache_size := seq_rec.cache_size;
    v_reserved_max := v_nextval + (v_cache_size - 1);

    -- Compute next safe ODD/EVEN values
    IF MOD(v_reserved_max, 2) = 0 THEN
      v_even_start := v_reserved_max + 2;
      v_odd_start  := v_reserved_max + 1;
    ELSE
      v_even_start := v_reserved_max + 1;
      v_odd_start  := v_reserved_max + 2;
    END IF;

    -- Generate DDL statements
    v_odd_ddl := 'CREATE SEQUENCE ' || seq_rec.sequence_name ||
                 ' START WITH ' || v_odd_start ||
                 ' INCREMENT BY 2 CACHE ' || v_cache_size || ' NOCYCLE;';

    v_even_ddl := 'CREATE SEQUENCE ' || seq_rec.sequence_name ||
                  ' START WITH ' || v_even_start ||
                  ' INCREMENT BY 2 CACHE ' || v_cache_size || ' NOCYCLE;';

    -- Output
    DBMS_OUTPUT.PUT_LINE('-- Sequence: ' || seq_rec.sequence_name);
    DBMS_OUTPUT.PUT_LINE('   Current NEXTVAL           : ' || v_nextval);
    DBMS_OUTPUT.PUT_LINE('   Cached values reserved up to : ' || v_reserved_max);
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('-- Site A (ODD)');
    DBMS_OUTPUT.PUT_LINE('   ' || v_odd_ddl);
    DBMS_OUTPUT.PUT_LINE('-- Site B (EVEN)');
    DBMS_OUTPUT.PUT_LINE('   ' || v_even_ddl);
    DBMS_OUTPUT.PUT_LINE('------------------------------------------------');
  END LOOP;
END;
/

Example of the Script Output


Sequence MY_SEQ dropped.


Sequence MY_SEQ created.


Sequence MY_SEQ2 dropped.


Sequence MY_SEQ2 created.

-- Sequence: DEPARTMENTS_SEQ
   Current NEXTVAL           : 310
   Cached values reserved up to : 309

-- Site A (ODD)
   CREATE SEQUENCE DEPARTMENTS_SEQ START WITH 311 INCREMENT BY 2 CACHE 0 NOCYCLE;
-- Site B (EVEN)
   CREATE SEQUENCE DEPARTMENTS_SEQ START WITH 310 INCREMENT BY 2 CACHE 0 NOCYCLE;
------------------------------------------------
-- Sequence: EMPLOYEES_SEQ
   Current NEXTVAL           : 210
   Cached values reserved up to : 209

-- Site A (ODD)
   CREATE SEQUENCE EMPLOYEES_SEQ START WITH 211 INCREMENT BY 2 CACHE 0 NOCYCLE;
-- Site B (EVEN)
   CREATE SEQUENCE EMPLOYEES_SEQ START WITH 210 INCREMENT BY 2 CACHE 0 NOCYCLE;
------------------------------------------------
-- Sequence: LOCATIONS_SEQ
   Current NEXTVAL           : 3600
   Cached values reserved up to : 3599

-- Site A (ODD)
   CREATE SEQUENCE LOCATIONS_SEQ START WITH 3601 INCREMENT BY 2 CACHE 0 NOCYCLE;
-- Site B (EVEN)
   CREATE SEQUENCE LOCATIONS_SEQ START WITH 3600 INCREMENT BY 2 CACHE 0 NOCYCLE;
------------------------------------------------
-- Sequence: MY_SEQ
   Current NEXTVAL           : 253
   Cached values reserved up to : 272

-- Site A (ODD)
   CREATE SEQUENCE MY_SEQ START WITH 273 INCREMENT BY 2 CACHE 20 NOCYCLE;
-- Site B (EVEN)
   CREATE SEQUENCE MY_SEQ START WITH 274 INCREMENT BY 2 CACHE 20 NOCYCLE;
------------------------------------------------
-- Sequence: MY_SEQ2
   Current NEXTVAL           : 23075
   Cached values reserved up to : 23094

-- Site A (ODD)
   CREATE SEQUENCE MY_SEQ2 START WITH 23095 INCREMENT BY 2 CACHE 20 NOCYCLE;
-- Site B (EVEN)
   CREATE SEQUENCE MY_SEQ2 START WITH 23096 INCREMENT BY 2 CACHE 20 NOCYCLE;
------------------------------------------------


PL/SQL procedure successfully completed.

Multi-Site Sequence Generation Strategy

When working with three or more active-active GoldenGate sites, the odd/even strategy no longer applies. Instead, you need a general solution that scales with the number of participating sites. The best practice is to define sequences using an increment equal to the number of sites, and assign each site a unique starting offset.

Use INCREMENT BY “N” Where “N” = Number of Sites

Each site should:

  • Use the same sequence name
  • Set INCREMENT BY to the total number of active-active sites
  • Use a unique START WITH value between 1 and N

This ensures each site generates values that are disjoint and non-conflicting.


Example: 3-Site Active-Active Setup

SiteSTART WITHINCREMENT BYValues Generated
A131, 4, 7, 10, …
B232, 5, 8, 11, …
C333, 6, 9, 12, …

Example: 4-Site Active-Active Setup

SiteSTART WITHINCREMENT BYValues Generated
A141, 5, 9, 13, …
B242, 6, 10, 14, …
C343, 7, 11, 15, …
D444, 8, 12, 16, …

I would love to see someone improve the script to accommodate N number of sites. :-)


Conclusion

When deploying Oracle GoldenGate in active-active replication mode, sequence conflict prevention is not optional; it’s a must. By applying this odd-even partitioning technique, you can prevent subtle data corruption issues and maintain a robust and conflict-free replication architecture. With more than two sites in an active-active GoldenGate topology, it’s critical to partition sequence values using a scalable method. Configuring sequences with INCREMENT BY “N” unique starting offsets allows you to prevent ID collisions cleanly and predictably. This pattern ensures reliable insert operations while maintaining simplicity in your replication architecture.

Leave a comment

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