> ## Documentation Index
> Fetch the complete documentation index at: https://cockroachlabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Phased Delta Migration with Failback Replication from Oracle

export const InternalLink = ({version, path = "", children, ...props}) => {
  let detectedVersion = version || "stable";
  if (typeof window !== 'undefined' && !version) {
    const match = window.location.pathname.match(/\/docs\/([^/]+)/);
    if (match) {
      detectedVersion = match[1];
    }
  }
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
  return <a href={`/docs/${detectedVersion}/${normalizedPath}`} {...props}>
      {children}
    </a>;
};

A <InternalLink version="molt" path="migration-approach-phased-delta-failback">*Phased Delta Migration with Failback Replication*</InternalLink> involves <InternalLink version="molt" path="migration-overview">migrating data to CockroachDB</InternalLink> in several phases. Data can be sliced per tenant, per service, per region, or per table to suit the needs of the migration. **For each given migration phase**, you use <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink> to perform an initial bulk load of the data, you use <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> to update the target database via forward replication and to activate failback replication, and then you cut over application traffic to CockroachDB after schema finalization and data verification. This process is repeated for each phase of data.

* Data is migrated to the target <InternalLink version="molt" path="migration-considerations-granularity">in phases</InternalLink>.

* This approach utilizes <InternalLink version="molt" path="migration-considerations-replication">continuous replication</InternalLink>.

* <InternalLink version="molt" path="migration-considerations-rollback">Rollback</InternalLink> is achieved via failback replication.

This approach is comparable to the <InternalLink version="molt" path="migration-approach-delta">Delta Migration</InternalLink>, but dividing the data into multiple phases allows each downtime window to be shorter, and it allows each phase of the migration to be less complex. Depending on how you divide the data, it also may allow your downtime windows to affect only a subset of users. For example, dividing the data per region could mean that, when migrating the data from Region A, application usage in Region B may remain unaffected. This approach may increase overall migration complexity: its duration is longer, you will need to do the work of partitioning the data, and you will have a longer period when you run both the source and the target database concurrently.

<InternalLink version="molt" path="migration-considerations-rollback">Failback replication</InternalLink> keeps the source database up to date with changes that occur in the target database once the target database begins receiving write traffic. Failback replication ensures that, if something goes wrong during the migration process, traffic can easily be returned to the source database without data loss. Like forward replication, in this approach, failback replication is run on a per-phase basis. It can persist indefinitely, until you're comfortable maintaining the target database as your sole data store.

This approach is best for databases that are too large to migrate all at once, and in business cases where downtime must be minimal. It's also suitable for risk-averse situations in which a safe rollback path must be ensured. It can only be performed if your team can handle the complexity of this approach, and if your source database can easily be divided into the phases you need.

This page describes an example scenario. While the commands provided can be copy-and-pasted, they may need to be altered or reconsidered to suit the needs of your specific environment.

<img src="https://mintcdn.com/cockroachlabs/mEy4bzaxzRbvNkMw/images/molt/molt_phased_delta_flow.svg?fit=max&auto=format&n=mEy4bzaxzRbvNkMw&q=85&s=1e158ae44b606980847737ee49418a2a" alt="Phased Delta Migration flow" style={{ maxWidth: "100%", display: "block", marginLeft: "auto", marginRight: "auto" }} width="470" height="534" data-path="images/molt/molt_phased_delta_flow.svg" />

## Example scenario

You have a moderately-sized (500GB) database that provides the data store for a web application. You want to migrate the entirety of this database to a new CockroachDB cluster. You will divide this migration into four geographic regions (A, B, C, and D).

The application runs on a Kubernetes cluster with an NGINX Ingress Controller.

Your business could not accommodate major performance issues that could arise after the migration. Therefore, you want to enable failback replication so that you can easily return to using your original database with minimal interruption.

**Estimated system downtime:** 3-5 minutes per region.

## Before the migration

* Install the <InternalLink version="molt" path="molt-fetch-installation#installation">MOLT (Migrate Off Legacy Technology)</InternalLink> tools.
* Review the <InternalLink version="molt" path="molt-fetch-best-practices">MOLT Fetch</InternalLink> and <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> documentation.
* <InternalLink version="molt" path="migration-strategy#develop-a-migration-plan">Develop a migration plan</InternalLink> and <InternalLink version="molt" path="migration-strategy#prepare-for-migration">prepare for the migration</InternalLink>.
* **Recommended:** Perform a dry run of this full set of instructions in a development environment that closely resembles your production environment. This can help you get a realistic sense of the time and complexity it requires.
* Understand the prerequisites and limitations of the MOLT tools:

### Prerequisites

#### Oracle Instant Client

Install Oracle Instant Client on the machine that will run `molt` and `replicator`. If using the MOLT Replicator binary (instead of Docker), the Oracle Instant Client libraries must be accessible at `/usr/lib`.

* On macOS ARM machines, download the [Oracle Instant Client](https://www.oracle.com/database/technologies/instant-client/macos-arm64-downloads.html#ic_osx_inst). After installation, you should have a new directory at `/Users/$USER/Downloads/instantclient_23_3` containing `.dylib` files. Set the `LD_LIBRARY_PATH` environment variable to this directory:

  ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  export LD_LIBRARY_PATH=/Users/$USER/Downloads/instantclient_23_3
  ```

* On Linux machines, install the Oracle Instant Client dependencies and set the `LD_LIBRARY_PATH` to the client library path:

  ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  sudo apt-get install -yqq --no-install-recommends libaio1t64
  sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1
  unzip -d /tmp /tmp/instantclient-basiclite-linux-amd64.zip
  sudo mv /tmp/instantclient_21_13/* /usr/lib
  export LD_LIBRARY_PATH=/usr/lib
  ```

  <Tip>
    You can also download Oracle Instant Client directly from the Oracle site for [Linux ARM64](https://www.oracle.com/database/technologies/instant-client/linux-amd64-downloads.html) or [Linux x86-64](https://www.oracle.com/ca-en/database/technologies/instant-client/linux-x86-64-downloads.html).
  </Tip>

### Limitations

#### MOLT Fetch limitations

* Only tables with <InternalLink version="stable" path="primary-key">primary key</InternalLink> types of <InternalLink version="stable" path="int">`INT`</InternalLink>, <InternalLink version="stable" path="float">`FLOAT`</InternalLink>, or <InternalLink version="stable" path="uuid">`UUID`</InternalLink> can be sharded with <InternalLink version="molt" path="molt-fetch-best-practices#configure-the-source-database-and-connection">`--export-concurrency`</InternalLink>.

* `GEOMETRY` and `GEOGRAPHY` types are not supported.

* Migrations must be performed from a single Oracle schema. You **must** include <InternalLink version="molt" path="molt-fetch#select-data-to-migrate">`--schema-filter`</InternalLink> so that MOLT Fetch only loads data from the specified schema. Refer to <InternalLink version="molt" path="molt-fetch#select-data-to-migrate">Schema and table filtering</InternalLink>.
  * Specifying <InternalLink version="molt" path="molt-fetch#select-data-to-migrate">`--table-filter`</InternalLink> is also strongly recommended to ensure that only necessary tables are migrated from the Oracle schema.

* Oracle advises against `LONG RAW` columns and [recommends converting them to `BLOB`](https://www.orafaq.com/wiki/LONG_RAW#History). `LONG RAW` can only store binary values up to 2GB, and only one `LONG RAW` column per table is supported.

#### MOLT Replicator limitations

* Replication modes require connection to the primary instance (PostgreSQL primary, MySQL primary/master, or Oracle primary). MOLT cannot obtain replication checkpoints or transaction metadata from replicas.

* Running DDL on the source or target while replication is in progress can cause replication failures.

* `TRUNCATE` operations on the source are not captured. Only `INSERT`, `UPDATE`, `UPSERT`, and `DELETE` events are replicated.

* Changes to virtual columns are not replicated automatically. To migrate these columns, you must define them explicitly with <InternalLink version="molt" path="molt-fetch">transformation rules</InternalLink>.

* Replication will not work for tables or column names exceeding 30 characters. This is a [limitation of Oracle LogMiner](https://docs.oracle.com/en/database/oracle/oracle-database/21/sutil/oracle-logminer-utility.html#GUID-7594F0D7-0ACD-46E6-BD61-2751136ECDB4).

* The following data types are not supported for replication:
  * User-defined types (UDTs)
  * Nested tables
  * `VARRAY`
  * `LONGBLOB`/`CLOB` columns (over 4000 characters)

* If your Oracle workload executes `UPDATE` statements that modify only LOB columns, these `UPDATE` statements are not supported by Oracle LogMiner and will not be replicated.

* If you are using Oracle 11 and execute `UPDATE` statements on `XMLTYPE` or LOB columns, those changes are not supported by Oracle LogMiner and will be excluded from ongoing replication.

* If you are migrating LOB columns from Oracle 12c, use [AWS DMS Binary Reader](https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.Oracle.html#CHAP_Source.Oracle.CDC) instead of LogMiner. Oracle LogMiner does not support LOB replication in 12c.

#### MOLT Verify limitations

* MOLT Verify compares 20,000 rows at a time by default, and row values can change between batches, potentially resulting in temporary inconsistencies in data. To configure the row batch size, use the `--row_batch_size` <InternalLink version="molt" path="molt-verify#flags">flag</InternalLink>.
* MOLT Verify checks for collation mismatches on <InternalLink version="stable" path="primary-key">primary key</InternalLink> columns. This may cause validation to fail when a <InternalLink version="stable" path="string">`STRING`</InternalLink> is used as a primary key and the source and target databases are using different <InternalLink version="stable" path="collate">collations</InternalLink>.
* MOLT Verify might give an error in case of schema changes on either the source or target database.
* <InternalLink version="stable" path="spatial-data-overview#spatial-objects">Geospatial types</InternalLink> cannot yet be compared.

## Step 1: Prepare the source database

In this step, you will:

* [Create a dedicated migration user on your source database](#create-migration-user-on-source-database).
* [Configure the source database for replication](#configure-source-database-for-replication).

### Create migration user on source database

Create a dedicated migration user (for example, `MIGRATION_USER`) on the source database. This user is responsible for reading data from source tables during the migration. You will pass this username in the [source connection string](#source-connection-string).

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE USER MIGRATION_USER IDENTIFIED BY 'password';
```

<Note>
  When migrating from Oracle Multitenant (PDB/CDB), this should be a [common user](https://docs.oracle.com/database/121/ADMQS/GUID-DA54EBE5-43EF-4B09-B8CC-FAABA335FBB8.htm). Prefix the username with `C##` (e.g., `C##MIGRATION_USER`).
</Note>

Grant the user privileges to connect, read metadata, and `SELECT` and `FLASHBACK` the tables you plan to migrate. The tables should all reside in a single schema (for example, `migration_schema`). For details, refer to [Schema and table filtering](#schema-and-table-filtering).

#### Oracle Multitenant (PDB/CDB) user privileges

Connect to the Oracle CDB as a DBA and grant the following:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Basic access
GRANT CONNECT TO C##MIGRATION_USER;
GRANT CREATE SESSION TO C##MIGRATION_USER;

-- General metadata access
GRANT EXECUTE_CATALOG_ROLE TO C##MIGRATION_USER;
GRANT SELECT_CATALOG_ROLE TO C##MIGRATION_USER;

-- Access to necessary V$ views
GRANT SELECT ON V_$LOG TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOGFILE TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOGMNR_CONTENTS TO C##MIGRATION_USER;
GRANT SELECT ON V_$ARCHIVED_LOG TO C##MIGRATION_USER;
GRANT SELECT ON V_$DATABASE TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOG_HISTORY TO C##MIGRATION_USER;

-- Direct grants to specific DBA views
GRANT SELECT ON ALL_USERS TO C##MIGRATION_USER;
GRANT SELECT ON DBA_USERS TO C##MIGRATION_USER;
GRANT SELECT ON DBA_OBJECTS TO C##MIGRATION_USER;
GRANT SELECT ON DBA_SYNONYMS TO C##MIGRATION_USER;
GRANT SELECT ON DBA_TABLES TO C##MIGRATION_USER;
```

Connect to the Oracle PDB (not the CDB) as a DBA and grant the following:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Allow C##MIGRATION_USER to connect to the PDB and see active transaction metadata
GRANT CONNECT TO C##MIGRATION_USER;
GRANT CREATE SESSION TO C##MIGRATION_USER;

-- General metadata access
GRANT SELECT_CATALOG_ROLE TO C##MIGRATION_USER;

-- Access to necessary V$ views
GRANT SELECT ON V_$SESSION TO C##MIGRATION_USER;
GRANT SELECT ON V_$TRANSACTION TO C##MIGRATION_USER;

-- Grant these two for every table to migrate in the migration_schema
GRANT SELECT, FLASHBACK ON migration_schema.tbl TO C##MIGRATION_USER;
```

#### Single-tenant Oracle user privileges

Connect to the Oracle database as a DBA and grant the following:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Basic access
GRANT CONNECT TO MIGRATION_USER;
GRANT CREATE SESSION TO MIGRATION_USER;

-- General metadata access
GRANT SELECT_CATALOG_ROLE TO MIGRATION_USER;
GRANT EXECUTE_CATALOG_ROLE TO MIGRATION_USER;

-- Access to necessary V$ views
GRANT SELECT ON V_$DATABASE TO MIGRATION_USER;
GRANT SELECT ON V_$SESSION TO MIGRATION_USER;
GRANT SELECT ON V_$TRANSACTION TO MIGRATION_USER;

-- Direct grants to specific DBA views
GRANT SELECT ON ALL_USERS TO MIGRATION_USER;
GRANT SELECT ON DBA_USERS TO MIGRATION_USER;
GRANT SELECT ON DBA_OBJECTS TO MIGRATION_USER;
GRANT SELECT ON DBA_SYNONYMS TO MIGRATION_USER;
GRANT SELECT ON DBA_TABLES TO MIGRATION_USER;

-- Grant these two for every table to migrate in the migration_schema
GRANT SELECT, FLASHBACK ON migration_schema.tbl TO MIGRATION_USER;
```

### Configure source database for replication

<Note>
  Connect to the primary instance (PostgreSQL primary, MySQL primary/master, or Oracle primary), **not** a replica. Replicas cannot provide the necessary replication checkpoints and transaction metadata required for ongoing replication.
</Note>

#### Enable ARCHIVELOG and FORCE LOGGING

Enable `ARCHIVELOG` mode for LogMiner to access archived redo logs:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Check current log mode
SELECT log_mode FROM v$database;

-- Enable ARCHIVELOG (requires database restart)
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;

-- Verify ARCHIVELOG is enabled
SELECT log_mode FROM v$database; -- Expected: ARCHIVELOG
```

Enable supplemental logging for primary keys:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Enable minimal supplemental logging for primary keys
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY) COLUMNS;

-- Verify supplemental logging status
SELECT supplemental_log_data_min, supplemental_log_data_pk FROM v$database;
-- Expected:
--   SUPPLEMENTAL_LOG_DATA_MIN: IMPLICIT (or YES)
--   SUPPLEMENTAL_LOG_DATA_PK: YES
```

Enable `FORCE LOGGING` to ensure all changes are logged:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER DATABASE FORCE LOGGING;

-- Verify FORCE LOGGING is enabled
SELECT force_logging FROM v$database; -- Expected: YES
```

#### Create source sentinel table

Create a checkpoint table called `REPLICATOR_SENTINEL` in the Oracle schema you will migrate:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE TABLE migration_schema."REPLICATOR_SENTINEL" (
  keycol NUMBER PRIMARY KEY,
  lastSCN NUMBER
);
```

Grant privileges to modify the checkpoint table. In Oracle Multitenant, grant the privileges on the PDB:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SELECT, INSERT, UPDATE ON migration_schema."REPLICATOR_SENTINEL" TO C##MIGRATION_USER;
```

#### Grant LogMiner privileges

Grant LogMiner privileges. In Oracle Multitenant, grant the permissions on the CDB:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Access to necessary V$ views
GRANT SELECT ON V_$LOG TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOGFILE TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOGMNR_CONTENTS TO C##MIGRATION_USER;
GRANT SELECT ON V_$ARCHIVED_LOG TO C##MIGRATION_USER;
GRANT SELECT ON V_$LOG_HISTORY TO C##MIGRATION_USER;

-- SYS-prefixed views (for full dictionary access)
GRANT SELECT ON SYS.V_$LOGMNR_DICTIONARY TO C##MIGRATION_USER;
GRANT SELECT ON SYS.V_$LOGMNR_LOGS TO C##MIGRATION_USER;
GRANT SELECT ON SYS.V_$LOGMNR_PARAMETERS TO C##MIGRATION_USER;
GRANT SELECT ON SYS.V_$LOGMNR_SESSION TO C##MIGRATION_USER;

-- Access to LogMiner views and controls
GRANT LOGMINING TO C##MIGRATION_USER;
GRANT EXECUTE ON DBMS_LOGMNR TO C##MIGRATION_USER;
```

The user must:

* Query [redo logs from LogMiner](#verify-logminer-privileges).
* Retrieve active transaction information to determine the starting point for ongoing replication.
* Update the internal [`REPLICATOR_SENTINEL` table](#create-source-sentinel-table) created on the Oracle source schema by the DBA.

#### Verify LogMiner privileges

Query the locations of redo files in the Oracle database:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT
    l.GROUP#,
    lf.MEMBER,
    l.FIRST_CHANGE# AS START_SCN,
    l.NEXT_CHANGE# AS END_SCN
FROM
    V$LOG l
JOIN
    V$LOGFILE lf
ON
    l.GROUP# = lf.GROUP#;
```

```
   GROUP# MEMBER                                       START_SCN                END_SCN
_________ _________________________________________ ____________ ______________________
        3 /opt/oracle/oradata/ORCLCDB/redo03.log         1232896    9295429630892703743
        2 /opt/oracle/oradata/ORCLCDB/redo02.log         1155042                1232896
        1 /opt/oracle/oradata/ORCLCDB/redo01.log         1141934                1155042

3 rows selected.
```

Get the current snapshot System Change Number (SCN):

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT CURRENT_SCN FROM V$DATABASE;
```

```
CURRENT_SCN
-----------
2358840

1 row selected.
```

Add the redo log files to LogMiner, using the redo log file paths you queried:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
EXEC DBMS_LOGMNR.ADD_LOGFILE(LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo01.log', OPTIONS => DBMS_LOGMNR.NEW);
EXEC DBMS_LOGMNR.ADD_LOGFILE(LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo02.log', OPTIONS => DBMS_LOGMNR.ADDFILE);
EXEC DBMS_LOGMNR.ADD_LOGFILE(LOGFILENAME => '/opt/oracle/oradata/ORCLCDB/redo03.log', OPTIONS => DBMS_LOGMNR.ADDFILE);
```

Start LogMiner, specifying the SCN you queried:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
EXEC DBMS_LOGMNR.START_LOGMNR(
  STARTSCN => 2358840,
  OPTIONS  => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG
);
```

```
PL/SQL procedure successfully completed.
```

<Tip>
  If you receive `ORA-01435: user does not exist`, the Oracle user lacks sufficient LogMiner privileges. Refer to [Grant LogMiner privileges](#grant-logminer-privileges).
</Tip>

## Step 2: Prepare the target database

### Define the target tables

Convert the source table definitions into CockroachDB-compatible equivalents. CockroachDB supports the PostgreSQL wire protocol and is largely <InternalLink version="stable" path="postgresql-compatibility#features-that-differ-from-postgresql">compatible with PostgreSQL syntax</InternalLink>.

* The source and target table definitions must **match**. Review <InternalLink version="molt" path="molt-fetch#type-mapping">Type mapping</InternalLink> to understand which source types can be mapped to CockroachDB types.

  For example, an Oracle source table defined as `CREATE TABLE migration_schema.tbl (pk INT PRIMARY KEY);` must have a corresponding schema and table in CockroachDB:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  CREATE SCHEMA migration_schema;
  CREATE TABLE migration_schema.tbl (pk INT PRIMARY KEY);
  ```

  * MOLT Fetch can automatically define matching CockroachDB tables using the <InternalLink version="molt" path="molt-fetch#handle-target-tables">`drop-on-target-and-recreate`</InternalLink> option.

  * If you define the target tables manually, review how MOLT Fetch handles <InternalLink version="molt" path="molt-fetch#mismatch-handling">type mismatches</InternalLink>. You can use the [MOLT Schema Conversion Tool](#schema-conversion-tool) to create matching table definitions.

  * By default, table and column names are case-insensitive in MOLT Fetch. If using the <InternalLink version="molt" path="molt-fetch-commands-and-flags#global-flags">`--case-sensitive`</InternalLink> flag, schema, table, and column names must match Oracle's default uppercase identifiers. Use quoted names on the target to preserve case. For example, the following CockroachDB SQL statement will error:

    ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    CREATE TABLE co.stores (... store_id ...);
    ```

    It should be written as:

    ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    CREATE TABLE "CO"."STORES" (... "STORE_ID" ...);
    ```

    When using `--case-sensitive`, quote all identifiers and match the case exactly (for example, use `"CO"."STORES"` and `"STORE_ID"`).

* Every table **must** have an explicit primary key.

  <Danger>
    Avoid using sequential keys. To learn more about the performance issues that can result from their use, refer to the <InternalLink version="stable" path="sql-faqs#how-do-i-generate-unique-slowly-increasing-sequential-numbers-in-cockroachdb">guidance on indexing with sequential keys</InternalLink>. If a sequential key is necessary in your CockroachDB table, you must create it manually, after using <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink> to load and replicate the data.
  </Danger>

* Review <InternalLink version="molt" path="molt-fetch">Transformations</InternalLink> to understand how computed columns and partitioned tables can be mapped to the target, and how target tables can be renamed.

* By default on CockroachDB, `INT` is an alias for `INT8`, which creates 64-bit signed integers. PostgreSQL and MySQL default to 32-bit integers. Depending on your source database or application requirements, you may need to change the integer size to `4`. For more information, refer to <InternalLink version="stable" path="int#considerations-for-64-bit-signed-integers">Considerations for 64-bit signed integers</InternalLink>.

#### Schema Conversion Tool

The <InternalLink version="cockroachcloud" path="migrations-page">MOLT Schema Conversion Tool</InternalLink> (SCT) converts source table definitions to CockroachDB-compatible syntax. It requires a free <InternalLink version="cockroachcloud" path="create-an-account">CockroachDB Cloud account</InternalLink>.

1. Upload a source `.sql` file to convert the syntax and identify <InternalLink version="molt" path="migration-strategy#unimplemented-features-and-syntax-incompatibilities">unimplemented features and syntax incompatibilities</InternalLink> in the table definitions.

2. Import the converted table definitions to a CockroachDB cluster:
   * When migrating to CockroachDB Cloud, the Schema Conversion Tool automatically <InternalLink version="cockroachcloud" path="migrations-page#migrate-the-schema">applies the converted table definitions to a new Cloud database</InternalLink>.
   * When migrating to a self-hosted CockroachDB cluster, <InternalLink version="cockroachcloud" path="migrations-page#export-the-schema">export a converted DDL file</InternalLink> and pipe the <InternalLink version="stable" path="sql-statements#data-definition-statements">data definition language (DDL)</InternalLink> directly into <InternalLink version="stable" path="cockroach-sql">`cockroach sql`</InternalLink>.

#### Drop constraints and indexes

To optimize data load performance, drop all non-`PRIMARY KEY` <InternalLink version="stable" path="alter-table#drop-constraint">constraints</InternalLink> and <InternalLink version="stable" path="drop-index">indexes</InternalLink> on the target CockroachDB database before migrating:

* <InternalLink version="stable" path="foreign-key">`FOREIGN KEY`</InternalLink>
* <InternalLink version="stable" path="unique">`UNIQUE`</InternalLink>
* <InternalLink version="stable" path="schema-design-indexes">Secondary indexes</InternalLink>
* <InternalLink version="stable" path="check">`CHECK`</InternalLink>
* <InternalLink version="stable" path="default-value">`DEFAULT`</InternalLink>
* <InternalLink version="stable" path="not-null">`NOT NULL`</InternalLink> (you do not need to drop this constraint when using `drop-on-target-and-recreate` for [table handling](#table-handling-mode))

<Danger>
  Do **not** drop <InternalLink version="stable" path="primary-key">`PRIMARY KEY`</InternalLink> constraints.
</Danger>

You can [recreate the constraints and indexes after loading the data](#add-constraints-and-indexes).

### Create the SQL user

Create a SQL user in the CockroachDB cluster that has the necessary privileges.

To create a user `crdb_user` in the default database (you will pass this username in the [target connection string](#target-connection-string)):

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE USER crdb_user WITH PASSWORD 'password';
```

Grant database-level privileges for schema creation within the target database:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT ALL ON DATABASE defaultdb TO crdb_user;
```

Grant permission to modify cluster settings:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SYSTEM MODIFYCLUSTERSETTING TO crdb_user;
```

Grant usage on schemas being migrated:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT USAGE ON SCHEMA migration_schema TO crdb_user;
GRANT USAGE ON SCHEMA public TO crdb_user;
```

Grant user privileges to create tables in the `migration_schema` schema and internal MOLT tables like `_molt_fetch_exceptions` in the `public` CockroachDB schema:

<Note>
  Ensure that you are connected to the target database.
</Note>

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT CREATE ON SCHEMA migration_schema TO crdb_user;
GRANT CREATE ON SCHEMA public TO crdb_user;
```

If you manually defined the target tables (which means that [`drop-on-target-and-recreate`](#table-handling-mode) will not be used), grant the following privileges on the schema:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA migration_schema TO crdb_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA migration_schema
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crdb_user;
```

Grant the same privileges for internal MOLT tables in the `public` CockroachDB schema:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO crdb_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crdb_user;
```

If you will be running Fetch with [`drop-on-target-and-recreate`](#table-handling-mode), and the target schema has pre-existing tables that were created by a different user, you may need to change table ownership to allow the migration user to drop those tables. Make the following alteration for each table:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER TABLE migration_schema.table1 OWNER TO crdb_user;
```

Depending on the MOLT Fetch <InternalLink version="molt" path="molt-fetch">data load mode</InternalLink> you will use, grant the necessary privileges to run either [`IMPORT INTO`](#import-into-privileges) or [`COPY FROM`](#copy-from-privileges) on the target tables:

#### `IMPORT INTO` privileges

Grant `SELECT`, `INSERT`, and `DROP` (required because the table is taken offline during the `IMPORT INTO`) privileges on all tables being migrated:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SELECT, INSERT, DROP ON ALL TABLES IN SCHEMA migration_schema TO crdb_user;
```

If you plan to use [cloud storage with implicit authentication](#cloud-storage-authentication) for data load, grant the `EXTERNALIOIMPLICITACCESS` <InternalLink version="stable" path="security-reference/authorization#supported-privileges">system-level privilege</InternalLink>:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SYSTEM EXTERNALIOIMPLICITACCESS TO crdb_user;
```

#### `COPY FROM` privileges

Grant <InternalLink version="stable" path="security-reference/authorization#admin-role">`admin`</InternalLink> privileges to the user:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT admin TO crdb_user;
```

#### Set session variable

Ensure that the `statement_timeout` <InternalLink path="set-vars">session variable</InternalLink> is set to `0s` for the user:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER ROLE crdb_user SET statement_timeout = '0s';
```

#### Replication privileges

Grant permissions to create the staging schema for replication:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER USER crdb_user CREATEDB;
```

### Configure GC TTL

Before starting the [initial data load](#run-molt-fetch), configure the <InternalLink version="stable" path="configure-replication-zones">garbage collection (GC) TTL</InternalLink> on the source CockroachDB cluster to ensure that historical data remains available when replication begins. The GC TTL must be long enough to cover the full duration of the data load.

Increase the GC TTL before starting the data load. For example, to set the GC TTL to 24 hours:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER DATABASE defaultdb CONFIGURE ZONE USING gc.ttlseconds = 86400;
```

<Note>
  The GC TTL duration must be higher than your expected time for the initial data load.
</Note>

Once replication has started successfully (which automatically protects its own data range), you can restore the GC TTL to its original value. For example, to restore to 5 minutes:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER DATABASE defaultdb CONFIGURE ZONE USING gc.ttlseconds = 300;
```

For details, refer to <InternalLink version="stable" path="protect-changefeed-data">Protect Changefeed Data from Garbage Collection</InternalLink>.

## Migrate each phase

Steps 3-12 are run for each phase of the data migration. When migrating the first phase, you will run through these steps for Region A. You will repeat these steps for the other regions during each subsequent migration phase.

## Step 3: Load data into CockroachDB

In this step, you will:

* [Configure MOLT Fetch with the flags needed for your migration](#configure-molt-fetch).
* [Run MOLT Fetch](#run-molt-fetch).
* [Understand how to continue a load after an interruption](#continue-molt-fetch-after-an-interruption).

### Configure MOLT Fetch

The <InternalLink version="molt" path="molt-fetch">MOLT Fetch documentation</InternalLink> includes detailed information about how to <InternalLink version="molt" path="molt-fetch">configure MOLT Fetch</InternalLink>, and how to <InternalLink version="molt" path="molt-fetch-monitoring">monitor MOLT Fetch metrics</InternalLink>.

When you run `molt fetch`, you can configure the following options for data load:

<a id="schema-and-table-filtering" />

<a id="source-connection-string" />

<a id="table-handling-mode" />

<a id="target-connection-string" />

<a id="cloud-storage-authentication" />

<a id="secure-connections" />

<a id="intermediate-file-storage" />

<a id="data-load-mode" />

<a id="connection-strings" />

* <InternalLink version="molt" path="molt-fetch#specify-source-and-target-databases">Specify source and target databases</InternalLink>: Specify URL‑encoded source and target connections.
* <InternalLink version="molt" path="molt-fetch#select-data-to-migrate">Select data to migrate</InternalLink>: Specify schema and table names to migrate. **Important for a phased migration.**
* <InternalLink version="molt" path="molt-fetch#define-intermediate-storage">Define intermediate file storage</InternalLink>: Export data to cloud storage or a local file server.
* <InternalLink version="molt" path="molt-fetch#define-fetch-mode">Define fetch mode</InternalLink>: Specifies whether data will only be loaded into/from intermediate storage.
* <InternalLink version="molt" path="molt-fetch#shard-tables-for-concurrent-export">Shard tables</InternalLink>: Divide larger tables into multiple shards during data export.
* <InternalLink version="molt" path="molt-fetch#import-into-vs-copy-from">Data load mode</InternalLink>: Choose between `IMPORT INTO` and `COPY FROM`.
* <InternalLink version="molt" path="molt-fetch#handle-target-tables">Table handling mode</InternalLink>: Determine how existing target tables are initialized before load.
* <InternalLink version="molt" path="molt-fetch#define-transformations">Define data transformations</InternalLink>: Define any row-level transformations to apply to the data before it reaches the target.
* <InternalLink version="molt" path="molt-fetch-monitoring">Monitor fetch metrics</InternalLink>: Configure metrics collection during initial data load.

Read through the documentation to understand how to configure your `molt fetch` command and its flags. Follow <InternalLink version="molt" path="molt-fetch-best-practices">best practices</InternalLink>, especially those related to security.

At minimum, the `molt fetch` command should include the source, target, data path, and <InternalLink version="molt" path="molt-fetch-commands-and-flags">`--ignore-replication-check`</InternalLink> flags. For a phased migration, you may also choose to include <InternalLink version="molt" path="molt-fetch-commands-and-flags">`--schema-filter`</InternalLink> or <InternalLink version="molt" path="molt-fetch-commands-and-flags">`--table-filter`</InternalLink> flags:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
molt fetch \
--source $SOURCE \
--target $TARGET \
--table-filter '.*user.*' \
--bucket-path 's3://bucket/path' \
--ignore-replication-check
```

However, depending on the needs of your migration, you may have many more flags set, and you may need to prepare some accompanying .json files.

### Run MOLT Fetch

<a id="start-fetch" />

Perform the initial load of the source data.

1. Issue the <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink> command to move the source data to CockroachDB. This example command passes the source and target connection strings [as environment variables](#secure-connections), writes [intermediate files](#intermediate-file-storage) to S3 storage, and uses the `truncate-if-exists` [table handling mode](#table-handling-mode) to truncate the target tables before loading data. It also limits the migration to a single schema and filters three specific tables to migrate. The <InternalLink version="molt" path="molt-fetch">data load mode</InternalLink> defaults to `IMPORT INTO`.

   The command assumes an Oracle Multitenant (CDB/PDB) source. <InternalLink version="molt" path="molt-fetch-commands-and-flags#source-cdb">`--source-cdb`</InternalLink> specifies the container database (CDB) connection string.

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   molt fetch \
   --source $SOURCE \
   --source-cdb $SOURCE_CDB \
   --target $TARGET \
   --schema-filter 'migration_schema' \
   --table-filter 'employees|payments|orders' \
   --bucket-path 's3://migration/data/cockroach' \
   --table-handling truncate-if-exists
   ```

2. Check the output to observe `fetch` progress.

   A `starting fetch` message indicates that the task has started:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","type":"summary","num_tables":3,"cdc_cursor":"backfillFromSCN=26685444,scn=26685786","time":"2025-02-10T14:28:11-05:00","message":"starting fetch"}
   ```

   `data extraction` messages are written for each table that is exported to the location in `--bucket-path`:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","table":"migration_schema.employees","time":"2025-02-10T14:28:11-05:00","message":"data extraction phase starting"}
   ```

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","table":"migration_schema.employees","type":"summary","num_rows":200000,"export_duration_ms":1000,"export_duration":"000h 00m 01s","time":"2025-02-10T14:28:12-05:00","message":"data extraction from source complete"}
   ```

   `data import` messages are written for each table that is loaded into CockroachDB:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","table":"migration_schema.employees","time":"2025-02-10T14:28:12-05:00","message":"starting data import on target"}
   ```

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","table":"migration_schema.employees","type":"summary","net_duration_ms":1899.748333,"net_duration":"000h 00m 01s","import_duration_ms":1160.523875,"import_duration":"000h 00m 01s","export_duration_ms":1000,"export_duration":"000h 00m 01s","num_rows":200000,"cdc_cursor":"backfillFromSCN=26685444,scn=26685786","time":"2025-02-10T14:28:13-05:00","message":"data import on target for table complete"}
   ```

   A `fetch complete` message is written when the fetch task succeeds:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","type":"summary","fetch_id":"f5cb422f-4bb4-4bbd-b2ae-08c4d00d1e7c","num_tables":3,"tables":["migration_schema.employees","migration_schema.payments","migration_schema.payments"],"skipped_unmigratable_tables":[],"cdc_cursor":"backfillFromSCN=26685444,scn=26685786","net_duration_ms":6752.847625,"net_duration":"000h 00m 06s","time":"2024-03-18T12:30:37-04:00","message":"fetch complete"}
   ```

   This message shows the appropriate values for the `--backfillFromSCN` and `--scn` flags to use when [starting Replicator](#start-replicator):

   ```
   --backfillFromSCN 26685444
   --scn 26685786
   ```

### Continue MOLT Fetch after an interruption

If MOLT Fetch fails while loading data into CockroachDB from intermediate files, it exits with an error message, fetch ID, and *continuation token* for each table that failed to load on the target database.

```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
{"level":"info","table":"public.employees","file_name":"shard_01_part_00000001.csv.gz","message":"creating or updating token for duplicate key value violates unique constraint \"employees_pkey\"; Key (id)=(1) already exists."}
{"level":"info","table":"public.employees","continuation_token":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","message":"created continuation token"}
{"level":"info","fetch_id":"f5cb422f-4bb4-4bbd-b2ae-08c4d00d1e7c","message":"continue from this fetch ID"}
{"level":"error","message":"Error: error from fetching table for public.employees: error importing data: duplicate key value violates unique
  constraint \"employees_pkey\" (SQLSTATE 23505)"}
```

You can use this information to <InternalLink version="molt" path="molt-fetch">continue the task from the *continuation point*</InternalLink> where it was interrupted.

Continuation is only possible under the following conditions:

* All data has been exported from the source database into intermediate files on <InternalLink version="molt" path="molt-fetch#bucket-path">cloud</InternalLink> or <InternalLink version="molt" path="molt-fetch#local-path">local storage</InternalLink>.
* The *initial load* of source data into the target CockroachDB database is incomplete.
* The load uses [`IMPORT INTO` rather than `COPY FROM`](#data-load-mode).

<Note>
  Only one fetch ID and set of continuation tokens, each token corresponding to a table, are active at any time. See <InternalLink version="molt" path="molt-fetch#list-active-continuation-tokens">List active continuation tokens</InternalLink>.
</Note>

The following command reattempts the data load starting from a specific continuation file, but you can also use individual continuation tokens to <InternalLink version="molt" path="molt-fetch">reattempt the data load for individual tables</InternalLink>.

The command assumes an Oracle Multitenant (CDB/PDB) source. <InternalLink version="molt" path="molt-fetch-commands-and-flags#source-cdb">`--source-cdb`</InternalLink> specifies the container database (CDB) connection string.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
molt fetch \
--source $SOURCE \
--source-cdb $SOURCE_CDB \
--target $TARGET \
--schema-filter 'migration_schema' \
--table-filter 'employees|payments|orders' \
--bucket-path 's3://migration/data/cockroach' \
--table-handling truncate-if-exists \
--ignore-replication-check \
--fetch-id f5cb422f-4bb4-4bbd-b2ae-08c4d00d1e7c \
--continuation-file-name shard_01_part_00000001.csv.gz
```

## Step 4: Verify the initial data load

In this step, you will use <InternalLink version="molt" path="molt-verify">MOLT Verify</InternalLink> to confirm that the source and target data is consistent. This ensures that the data load was successful. Use MOLT Verify's <InternalLink version="molt" path="molt-verify#flags">`--schema-filter`</InternalLink> or <InternalLink version="molt" path="molt-verify#flags">`--table-filter`</InternalLink> to select only the tables that are relevant for the given phase.

### Run MOLT Verify

1. Run the <InternalLink version="molt" path="molt-verify">MOLT Verify</InternalLink> command, specifying the source and target [connection strings](#connection-strings) and the tables to validate.

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   molt verify \
   --source $SOURCE \
   --target $TARGET \
   --table-filter 'employees|payments|orders'
   ```

   <Note>
     With Oracle Multitenant deployments, while `--source-cdb` is required for `fetch`, it is **not** necessary for `verify`.
   </Note>

2. Check the output to observe `verify` progress.

   A `verification in progress` indicates that the task has started:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","time":"2025-02-10T15:35:04-05:00","message":"verification in progress"}
   ```

   `starting verify` messages are written for each specified table:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","time":"2025-02-10T15:35:04-05:00","message":"starting verify on public.employees, shard 1/1"}
   ```

   A `finished row verification` message is written after each table is compared. If `num_success` equals `num_truth_rows` and the error counters (`num_missing`, `num_mismatch`, `num_extraneous`, and `num_column_mismatch`) are all `0`, the table verified successfully. Any non-zero values in the error counters indicate data discrepancies that need investigation. For details on each field, refer to the <InternalLink version="molt" path="molt-verify#usage">MOLT Verify</InternalLink> page.

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","type":"summary","table_schema":"public","table_name":"employees","num_truth_rows":200004,"num_success":200004,"num_conditional_success":0,"num_missing":0,"num_mismatch":0,"num_extraneous":0,"num_live_retry":0,"num_column_mismatch":0,"time":"2025-02-10T15:35:05-05:00","message":"finished row verification on public.employees (shard 1/1)"}
   ```

   A `verification complete` message is written when the verify task succeeds:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {"level":"info","net_duration_ms":699.804875,"net_duration":"000h 00m 00s","time":"2025-02-10T15:35:05-05:00","message":"verification complete"}
   ```

## Step 5: Finalize the target schema

### Add constraints and indexes

Add any constraints or indexes that you previously [removed from the CockroachDB schema](#drop-constraints-and-indexes) to facilitate data load.

<Note>
  If you used the `--table-handling drop-on-target-and-recreate` option for data load, only <InternalLink version="stable" path="primary-key">`PRIMARY KEY`</InternalLink> and <InternalLink version="stable" path="not-null">`NOT NULL`</InternalLink> constraints are preserved. You **must** manually recreate all other constraints and indexes.
</Note>

For the appropriate SQL syntax, refer to <InternalLink version="stable" path="alter-table#add-constraint">`ALTER TABLE ... ADD CONSTRAINT`</InternalLink> and <InternalLink version="stable" path="create-index">`CREATE INDEX`</InternalLink>. Review the <InternalLink version="stable" path="schema-design-indexes#best-practices">best practices for creating secondary indexes</InternalLink> on CockroachDB.

## Step 6: Begin forward replication

In this step, you will:

* [Configure MOLT Replicator with the flags needed for your migration](#configure-molt-replicator-forward-replication).
* [Start MOLT Replicator](#start-molt-replicator-forward-replication).
* [Understand how to continue replication after an interruption](#continue-molt-replicator-after-an-interruption-forward-replication).

### Configure MOLT Replicator (forward replication)

When you run `replicator`, you can configure the following options for replication:

* [Replication connection strings](#replication-connection-strings): Specify URL-encoded source and target database connections.

* [Replicator flags](#replicator-flags): Specify required and optional flags to configure replicator behavior.

* [Tuning parameters](#tuning-parameters): Optimize replication performance and resource usage.

* [Replicator metrics](#replicator-metrics): Monitor replication progress and performance.

#### Replication connection strings

MOLT Replicator uses `--sourceConn` and `--targetConn` to specify the source and target database connections.

`--sourceConn` specifies the connection string of the source database:

```
--sourceConn 'oracle://{username}:{password}@{host}:{port}/{service_name}'
```

For Oracle Multitenant databases, also specify `--sourcePDBConn` with the PDB connection string:

```
--sourcePDBConn 'oracle://{username}:{password}@{host}:{port}/{pdb_service_name}'
```

`--targetConn` specifies the target CockroachDB connection string:

```
--targetConn 'postgresql://{username}:{password}@{host}:{port}/{database}'
```

<Tip>
  Follow best practices for securing connection strings. Refer to [Secure connections](#secure-connections).
</Tip>

#### Replicator flags

Configure the following <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> flags for continuous replication. For details on all available flags, refer to <InternalLink version="molt" path="replicator-flags">Replicator Flags</InternalLink>.

| Flag                                                                                        | Description                                                                                                                                                                                                                                                              |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| <InternalLink version="molt" path="replicator-flags">`--sourceSchema`</InternalLink>        | **Required.** Oracle user that owns the tables to replicate. Oracle capitalizes identifiers by default, so use uppercase (for example, `MIGRATION_USER`).                                                                                                                |
| <InternalLink version="molt" path="replicator-flags">`--targetSchema`</InternalLink>        | **Required.** Target schema name on CockroachDB where tables will be replicated. Schema name must be fully qualified in the format `database.schema`.                                                                                                                    |
| <InternalLink version="molt" path="replicator-flags">`--scn`</InternalLink>                 | **Required.** Snapshot System Change Number (SCN) for the initial changefeed starting point.                                                                                                                                                                             |
| <InternalLink version="molt" path="replicator-flags">`--backfillFromSCN`</InternalLink>     | **Required.** SCN of the earliest active transaction at the time of the snapshot. Ensures no transactions are skipped.                                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--stagingSchema`</InternalLink>       | **Required.** Staging schema name on CockroachDB for replication metadata and checkpoints. Schema name must be fully qualified in the format `database.schema`.                                                                                                          |
| <InternalLink version="molt" path="replicator-flags">`--stagingCreateSchema`</InternalLink> | Automatically create the staging schema if it does not exist. Include this flag when starting replication for the first time.                                                                                                                                            |
| <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink>         | Enable Prometheus metrics at a specified `{host}:{port}`. Metrics are served at `http://{host}:{port}/_/varz`.                                                                                                                                                           |
| <InternalLink version="molt" path="replicator-flags">`--userscript`</InternalLink>          | Path to a <InternalLink version="molt" path="userscript-overview">userscript</InternalLink> that enables data filtering, routing, or transformations. For examples, refer to <InternalLink version="molt" path="userscript-cookbook">Userscript Cookbook</InternalLink>. |

You can find the SCN values in the message `replication-only mode should include the following replicator flags` after the [initial data load](#start-fetch) completes.

#### Tuning parameters

Configure the following <InternalLink version="molt" path="replicator-flags">`replicator` flags</InternalLink> to optimize replication throughput and resource usage. Test different combinations in a pre-production environment to find the optimal balance of stability and performance for your workload.

<Note>
  The following parameters apply to PostgreSQL, Oracle, and CockroachDB (failback) sources.
</Note>

| Flag                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                         |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink>           | Control the maximum number of concurrent target transactions. Higher values increase throughput but require more target connections. Start with a conservative value and increase based on target database capacity.                                                                                                                                                |
| <InternalLink version="molt" path="replicator-flags">`--flushSize`</InternalLink>             | Balance throughput and latency. Controls how many mutations are batched into each query to the target. Increase for higher throughput at the cost of higher latency.                                                                                                                                                                                                |
| <InternalLink version="molt" path="replicator-flags">`--targetApplyQueueSize`</InternalLink>  | Control memory usage during operation. Increase to allow higher throughput at the expense of memory; decrease to apply backpressure and limit memory consumption.                                                                                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--targetMaxPoolSize`</InternalLink>     | Set larger than <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink> by a safety factor to avoid exhausting target pool connections. Replicator enforces setting parallelism to 80% of this value.                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--collapseMutations`</InternalLink>     | Reduce the number of queries to the target by combining multiple mutations on the same primary key within each batch. Disable only if exact mutation order matters more than end state.                                                                                                                                                                             |
| <InternalLink version="molt" path="replicator-flags">`--enableParallelApplies`</InternalLink> | Improve apply throughput for independent tables and table groups that share foreign key dependencies. Increases memory and target connection usage, so ensure you increase <InternalLink version="molt" path="replicator-flags">`--targetMaxPoolSize`</InternalLink> or reduce <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink>. |
| <InternalLink version="molt" path="replicator-flags">`--flushPeriod`</InternalLink>           | Set to the maximum allowable time between flushes (for example, `10s` if data must be applied within 10 seconds). Works with <InternalLink version="molt" path="replicator-flags">`--flushSize`</InternalLink> to control when buffered mutations are committed to the target.                                                                                      |
| <InternalLink version="molt" path="replicator-flags">`--quiescentPeriod`</InternalLink>       | Lower this value if constraint violations resolve quickly on your workload to make retries more frequent and reduce latency. Do not lower if constraint violations take time to resolve.                                                                                                                                                                            |
| <InternalLink version="molt" path="replicator-flags">`--scanSize`</InternalLink>              | Applies to <InternalLink version="molt" path="molt-replicator">failback</InternalLink> (`replicator start`) scenarios **only**. Balance memory usage and throughput. Increase to read more rows at once from the CockroachDB staging cluster for higher throughput, at the cost of memory pressure. Decrease to reduce memory pressure and increase stability.      |

#### Replicator metrics

MOLT Replicator metrics are not enabled by default. Enable Replicator metrics by specifying the <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink> flag with a port (or `host:port`) when you start Replicator. This exposes Replicator metrics at `http://{host}:{port}/_/varz`. For example, the following flag exposes metrics on port `30005`:

```
--metricsAddr :30005
```

For guidelines on using and interpreting replication metrics, refer to <InternalLink version="molt" path="replicator-metrics?filters=oracle">Replicator Metrics</InternalLink>.

### Start MOLT Replicator (forward replication)

<a id="start-replicator" />

With initial load complete, start replication of ongoing changes on the source to CockroachDB using <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink>.

<Note>
  MOLT Fetch captures a consistent point-in-time checkpoint at the start of the data load (shown as `cdc_cursor` in the fetch output). Starting replication from this checkpoint ensures that all changes made during and after the data load are replicated to CockroachDB, preventing data loss or duplication. The following steps use the checkpoint values from the fetch output to start replication at the correct position.
</Note>

Run the `replicator` command, specifying the backfill and starting SCN from the [checkpoint recorded during data load](#run-molt-fetch). Use `--stagingSchema` to specify a unique name for the staging database, and include `--stagingCreateSchema` to have MOLT Replicator automatically create the staging database. If you [filtered tables during the initial load](#schema-and-table-filtering), <InternalLink version="molt" path="userscript-cookbook#filter-multiple-tables">write a userscript to filter tables on replication</InternalLink> and specify the path with `--userscript`.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
replicator oraclelogminer \
--sourceConn $SOURCE \
--sourcePDBConn $SOURCE_PDB \
--targetConn $TARGET \
--sourceSchema MIGRATION_USER \
--targetSchema defaultdb.migration_schema \
--backfillFromSCN 26685444 \
--scn 26685786 \
--stagingSchema defaultdb._replicator \
--stagingCreateSchema \
--metricsAddr :30005 \
--userscript table_filter.ts \
-v
```

<Note>
  When <InternalLink version="molt" path="userscript-cookbook#filter-multiple-tables">filtering out tables in a schema with a userscript</InternalLink>, replication performance may decrease because filtered tables are still included in LogMiner queries and processed before being discarded.
</Note>

#### Check that replication is working

Verify that Replicator is processing changes successfully. To do so, check the MOLT Replicator logs. Since you enabled debug logging with `-v`, you should see connection and row processing messages:

When transactions are read from the Oracle source, you should see registered transaction IDs (XIDs):

```
DEBUG  [2025-07-03T15:55:12-05:00] registered xid 0f001f0040060000
DEBUG  [2025-07-03T15:55:12-05:00] registered xid 0b001f00bb090000
```

When rows are successfully replicated, you should see debug output like the following:

```
DEBUG  [2025-07-03T15:55:12-05:00] upserted rows                                 conflicts=0 duration=2.620009ms proposed=13 target="\"molt_movies\".\"USERS\".\"CUSTOMER_CONTACT\"" upserted=13
DEBUG  [2025-07-03T15:55:12-05:00] upserted rows                                 conflicts=0 duration=2.212807ms proposed=16 target="\"molt_movies\".\"USERS\".\"CUSTOMER_DEVICE\"" upserted=16
```

These messages confirm successful replication. You can disable verbose logging after verifying the connection.

### Continue MOLT Replicator after an interruption (forward replication)

Run the <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> `oraclelogminer` command using the same `--stagingSchema` value from your [initial replication command](#start-molt-replicator-forward-replication).

Replicator will automatically find the correct restart SCN (System Change Number) from the `_oracle_checkpoint` table in the staging schema. The restart point is determined by the non-committed row with the smallest `startscn` column value.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
replicator oraclelogminer \
--sourceConn $SOURCE \
--sourcePDBConn $SOURCE_PDB \
--sourceSchema MIGRATION_USER \
--targetSchema defaultdb.migration_schema \
--targetConn $TARGET \
--stagingSchema defaultdb._replicator \
--metricsAddr :30005 \
--userscript table_filter.ts \
-v
```

<Note>
  When <InternalLink version="molt" path="userscript-cookbook#filter-multiple-tables">filtering out tables in a schema with a userscript</InternalLink>, replication performance may decrease because filtered tables are still included in LogMiner queries and processed before being discarded.
</Note>

Replication resumes from the last checkpoint without performing a fresh load. Monitor the metrics endpoint at `http://localhost:30005/_/varz` to track replication progress.

## Step 7: Stop application traffic

Once the inital data load has been verified and the target schema has been finalized, it's time to begin the cutover process. First, stop application traffic to the source for this particular region.

If the Kubernetes cluster that deploys the application has pre-region deployments (for example, `app-us`, `app-eu`, `app-apac`), you can scale down only the deployment for that region.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
kubectl scale deploy/app-eu --replicas=0
```

Or this can be handled by the NGINX Ingress Controller, by including the following to your NGINX configuration, ensuring that the conditional statement is suitable for your deployment:

```yml theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  annotations:
    nginx.ingress.kubernetes.io/server-snippet: |
      if ($http_x_region = "eu") {
        return 503;
      }
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app
            port:
              number: 80
```

<Danger>
  Application downtime begins now, for users in the given region.

  It is strongly recommended that you perform a dry run of this migration in a test environment. This will allow you to practice using the MOLT tools in real time, and it will give you an accurate sense of how long application downtime might last.
</Danger>

## Step 8: Stop forward replication

Before you can cut over traffic to the target, the changes to the source database need to finish being written to the target. Once the source is no longer receiving write traffic, MOLT Replicator will take some seconds to finish replicating the final changes. This is known as *drainage*.

1. Wait for replication to drain, which means that all transactions that occurred on the source database have been fully processed and replicated. There are several ways to determine that replication has fully drained:
   * When replication is caught up, you will not see new `upserted rows` logs.
   * If you set up the replication metrics endpoint with <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink> in the preceding steps, metrics are available at:

     ```
     http://{host}:{port}/_/varz
     ```

     Use the following Prometheus alert expression to observe when the combined rate of upserts and deletes is `0` for each schema:

     ```
     sum by (schema) (rate(apply_upserts_total[$__rate_interval]) + rate(apply_deletes_total[$__rate_interval]))
     ```
   * You can also check Prometheus metrics associated with replication lag, including <InternalLink version="molt" path="replicator-metrics#source-commit-to-apply-lag-seconds">`source_commit_to_apply_lag_seconds`</InternalLink>.

2. Cancel replication by entering `ctrl-c` to issue a `SIGTERM` signal. This returns an exit code `0`.

## Step 9: Verify the replicated data

Repeat [Step 4](#step-4-verify-the-initial-data-load) to verify the updated data.

## Step 10: Begin failback replication

In this step, you will:

* [Prepare both databases for failback replication](#prepare-your-source-and-target-databases-for-failback-replication)
* [Configure MOLT Replicator with the flags needed for your migration](#configure-molt-replicator-failback-replication).
* [Start MOLT Replicator](#start-molt-replicator-failback-replication).
* [Understand how to continue replication after an interruption](#continue-molt-replicator-after-an-interruption-failback-replication).

### Prepare your source and target databases for failback replication

#### Prepare the CockroachDB cluster

<Tip>
  For details on enabling CockroachDB changefeeds, refer to <InternalLink version="stable" path="create-and-configure-changefeeds">Create and Configure Changefeeds</InternalLink>.
</Tip>

If you are migrating to a CockroachDB self-hosted cluster, <InternalLink version="stable" path="create-and-configure-changefeeds#enable-rangefeeds">enable rangefeeds</InternalLink> on the cluster:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SET CLUSTER SETTING kv.rangefeed.enabled = true;
```

Use the following optional settings to increase changefeed throughput.

<Danger>
  The following settings can impact source cluster performance and stability, especially SQL foreground latency during writes. For details, refer to <InternalLink version="stable" path="advanced-changefeed-configuration">Advanced Changefeed Configuration</InternalLink>.
</Danger>

To lower changefeed emission latency, but increase SQL foreground latency:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SET CLUSTER SETTING kv.rangefeed.closed_timestamp_refresh_interval = '250ms';
```

To lower the <InternalLink version="stable" path="architecture/transaction-layer#closed-timestamps">closed timestamp</InternalLink> lag duration:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s';
```

To improve catchup speeds but increase cluster CPU usage:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SET CLUSTER SETTING kv.rangefeed.concurrent_catchup_iterators = 64;
```

#### Grant target database user permissions

You should have already created a migration user on the target database (your **original source database**) with the necessary privileges. Refer to [Create migration user on source database](#create-migration-user-on-source-database).

For failback replication, grant the user additional privileges to write data back to the target database:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
-- Grant INSERT, UPDATE, and FLASHBACK on tables to fail back to
GRANT SELECT, INSERT, UPDATE, FLASHBACK ON migration_schema.employees TO MIGRATION_USER;
GRANT SELECT, INSERT, UPDATE, FLASHBACK ON migration_schema.payments TO MIGRATION_USER;
GRANT SELECT, INSERT, UPDATE, FLASHBACK ON migration_schema.orders TO MIGRATION_USER;
```

#### Create a CockroachDB changefeed

On the target cluster, create a CockroachDB changefeed to send changes to MOLT Replicator.

1. Get the current logical timestamp from CockroachDB, after [ensuring that forward replication has fully drained](#step-8-stop-forward-replication):

   ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   SELECT cluster_logical_timestamp();
   ```

   ```
       cluster_logical_timestamp
   ----------------------------------
     1759246920563173000.0000000000
   ```

2. Create the CockroachDB changefeed pointing to the MOLT Replicator webhook endpoint. Use `cursor` to specify the logical timestamp from the preceding step. For details on the webhook sink URI, refer to <InternalLink version="stable" path="changefeed-sinks#webhook-sink">Webhook sink</InternalLink>.

   <Note>
     Explicitly set a default `10s` <InternalLink version="stable" path="create-changefeed#options">`webhook_client_timeout`</InternalLink> value in the `CREATE CHANGEFEED` statement. This value ensures that the webhook can report failures in inconsistent networking situations and make crash loops more visible.
   </Note>

   The webhook URL path specifies the schema name on the target Oracle database. Oracle capitalizes identifiers by default. For example, `/MIGRATION_SCHEMA` routes changes to the `MIGRATION_SCHEMA` schema.

   ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   CREATE CHANGEFEED FOR TABLE employees, payments, orders \
   INTO 'webhook-https://replicator-host:30004/MIGRATION_SCHEMA?client_cert={base64_encoded_cert}&client_key={base64_encoded_key}&ca_cert={base64_encoded_ca}' \
   WITH updated, resolved = '250ms', min_checkpoint_frequency = '250ms', initial_scan = 'no', cursor = '1759246920563173000.0000000000', webhook_sink_config = '{"Flush":{"Bytes":1048576,"Frequency":"1s"}}', webhook_client_timeout = '10s';
   ```

   The output shows the job ID:

   ```
           job_id
   -----------------------
     1101234051444375553
   ```

   <Tip>
     Ensure that only **one** changefeed points to MOLT Replicator at a time to avoid mixing streams of incoming data.
   </Tip>

3. Monitor the changefeed status, specifying the job ID:

   ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   SHOW CHANGEFEED JOB 1101234051444375553;
   ```

   ```
           job_id        | ... | status  |              running_status               | ...
   ----------------------+-----+---------+-------------------------------------------+----
     1101234051444375553 | ... | running | running: resolved=1759246920563173000,0 | ...
   ```

   To confirm the changefeed is active and replicating changes to the target database, check that `status` is `running` and `running_status` shows `running: resolved={timestamp}`.

   <Danger>
     `running: resolved` may be reported even if data isn't being sent properly. This typically indicates incorrect host/port configuration or network connectivity issues.
   </Danger>

4. Verify that Replicator is reporting incoming HTTP requests from the changefeed. To do so, check the MOLT Replicator logs. Since you enabled debug logging with `-v`, you should see periodic HTTP request successes:

   ```
   DEBUG  [2025-08-25T11:52:47-05:00]  httpRequest="&{0x14000b068c0 45 200 3 9.770958ms   false false}"
   DEBUG  [2025-08-25T11:52:48-05:00]  httpRequest="&{0x14000d1a000 45 200 3 13.438125ms   false false}"
   ```

   These debug messages confirm successful changefeed connections to MOLT Replicator. You can disable verbose logging after verifying the connection.

### Configure MOLT Replicator (failback replication)

When you run `replicator`, you can configure the following options for replication:

* [Connection strings](#connection-strings): Specify URL‑encoded source and target connections.

* [TLS certificate and key](#tls-certificate-and-key): Configure secure TLS connections.

* [Replicator flags](#replicator-flags): Specify required and optional flags to configure replicator behavior.

* [Tuning parameters](#tuning-parameters): Optimize failback performance and resource usage.

* [Replicator metrics](#replicator-metrics): Monitor failback replication performance.

#### Replication connection strings

MOLT Replicator uses `--sourceConn` and `--targetConn` to specify the source and target database connections.

<Note>
  For MOLT Replicator, the source is always the **replication** source, while the target is always the **replication** target. This is distinct from the **migration** source and target. In the case of this example migration, the new CockroachDB cluster is the migration target, but because failback replication moves data from the migration target back to the migration source, the **replication** target is the original source database. In essence, the `--sourceConn` and `--targetConn` strings should be reversed for failback replication.
</Note>

`--sourceConn` specifies the connection string of the CockroachDB cluster:

```
--sourceConn 'postgresql://{username}:{password}@{host}:{port}/{database}'
```

`--targetConn` specifies the original source database:

```
--targetConn 'oracle://{username}:{password}@{host}:{port}/{service_name}'
```

<Tip>
  Follow best practices for securing connection strings. Refer to [Secure connections](#secure-connections).
</Tip>

##### Secure connections

* To keep your database credentials out of shell history and logs, follow these best practices when specifying your source and target connection strings:

  * Avoid plaintext connection strings.

  * Provide your connection strings as environment variables. For example:

    ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    export SOURCE="postgres://migration_user:a%2452%26@localhost:5432/migration_db?sslmode=verify-full"
    export TARGET="postgres://root@localhost:26257/defaultdb?sslmode=verify-full"
    ```

    Afterward, reference the environment variables in MOLT commands:

    ```
    --source $SOURCE
    --target $TARGET
    ```

  * If possible, use an external secrets manager to load the environment variables from stored secrets.

* Use TLS-enabled connection strings to encrypt data in transit from MOLT to the database. When using TLS certificates, ensure certificate files are accessible to the MOLT binary on the same machine.

  For example, a PostgreSQL connection string with TLS certificates:

  ```
  postgresql://migration_user@db.example.com:5432/appdb?sslmode=verify-full&sslrootcert=/etc/migration_db/certs/ca.pem&sslcert=/etc/migration_db/certs/client.crt&sslkey=/etc/migration_db/certs/client.key
  ```

* URL-encode connection strings for the source database and <InternalLink version="stable" path="connect-to-the-database">CockroachDB</InternalLink> so special characters in passwords are handled correctly.

  * Given a password `a$52&`, pass it to the `molt escape-password` command with single quotes:

    ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    molt escape-password --password 'a$52&'
    ```

    Use the encoded password in your connection string. For example:

    ```
    postgres://migration_user:a%2452%26@localhost:5432/migration_db
    ```

* Remove `sslmode=disable` from production connection strings.

#### TLS certificate and key

Always use **secure TLS connections** for failback replication to protect data in transit. Do **not** use insecure configurations in production: avoid the `--disableAuthentication` and `--tlsSelfSigned` Replicator flags and `insecure_tls_skip_verify=true` query parameter in the changefeed webhook URI.

Generate self-signed TLS certificates or certificates from an external CA. Ensure the TLS server certificate and key are accessible on the MOLT Replicator host machine via a relative or absolute file path. When you [start failback with Replicator](#start-replicator), specify the paths with `--tlsCertificate` and `--tlsPrivateKey`. For example:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
replicator start \
... \
--tlsCertificate ./certs/server.crt \
--tlsPrivateKey ./certs/server.key
```

The client certificates defined in the changefeed webhook URI must correspond to the server certificates specified in the `replicator` command. This ensures proper TLS handshake between the changefeed and MOLT Replicator. To include client certificates in the changefeed webhook URL, encode them with `base64` and then URL-encode the output with `jq`:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
base64 -i ./client.crt | jq -R -r '@uri'
base64 -i ./client.key | jq -R -r '@uri'
base64 -i ./ca.crt | jq -R -r '@uri'
```

When you [create the changefeed](#create-a-cockroachdb-changefeed), pass the encoded certificates in the changefeed URL, where `client_cert`, `client_key`, and `ca_cert` are <InternalLink version="stable" path="changefeed-sinks">webhook sink parameters</InternalLink>:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE CHANGEFEED FOR TABLE table1, table2
INTO 'webhook-https://host:port/database/schema?client_cert={base64_and_url_encoded_cert}&client_key={base64_and_url_encoded_key}&ca_cert={base64_and_url_encoded_ca}'
WITH ...;
```

For additional details on the webhook sink URI, refer to <InternalLink version="stable" path="changefeed-sinks#webhook-sink">Webhook sink</InternalLink>.

#### Replicator flags

| Flag                                                                                   | Description                                                                                                                                                                                                                                                              |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| <InternalLink version="molt" path="replicator-flags">`--stagingSchema`</InternalLink>  | **Required.** Staging schema name on CockroachDB for the changefeed checkpoint table. Schema name must be fully qualified in the format `database.schema`.                                                                                                               |
| <InternalLink version="molt" path="replicator-flags">`--bindAddr`</InternalLink>       | **Required.** Network address to bind the webhook sink for the changefeed. For example, `:30004`.                                                                                                                                                                        |
| <InternalLink version="molt" path="replicator-flags">`--tlsCertificate`</InternalLink> | Path to the server TLS certificate for the webhook sink. Refer to [TLS certificate and key](#tls-certificate-and-key).                                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--tlsPrivateKey`</InternalLink>  | Path to the server TLS private key for the webhook sink. Refer to [TLS certificate and key](#tls-certificate-and-key).Q                                                                                                                                                  |
| <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink>    | Enable Prometheus metrics at a specified `{host}:{port}`. Metrics are served at `http://{host}:{port}/_/varz`.                                                                                                                                                           |
| <InternalLink version="molt" path="replicator-flags">`--userscript`</InternalLink>     | Path to a <InternalLink version="molt" path="userscript-overview">userscript</InternalLink> that enables data filtering, routing, or transformations. For examples, refer to <InternalLink version="molt" path="userscript-cookbook">Userscript Cookbook</InternalLink>. |

* The staging schema is first created during <InternalLink version="molt" path="molt-replicator">initial replication setup</InternalLink> with <InternalLink version="molt" path="replicator-flags">`--stagingCreateSchema`</InternalLink>.

* When configuring a [secure changefeed](#tls-certificate-and-key) for failback, you **must** include <InternalLink version="molt" path="replicator-flags">`--tlsCertificate`</InternalLink> and <InternalLink version="molt" path="replicator-flags">`--tlsPrivateKey`</InternalLink>, which specify the paths to the server certificate and private key for the webhook sink connection.

### Tuning parameters

Configure the following <InternalLink version="molt" path="replicator-flags">`replicator` flags</InternalLink> to optimize replication throughput and resource usage. Test different combinations in a pre-production environment to find the optimal balance of stability and performance for your workload.

<Note>
  The following parameters apply to PostgreSQL, Oracle, and CockroachDB (failback) sources.
</Note>

| Flag                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                         |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink>           | Control the maximum number of concurrent target transactions. Higher values increase throughput but require more target connections. Start with a conservative value and increase based on target database capacity.                                                                                                                                                |
| <InternalLink version="molt" path="replicator-flags">`--flushSize`</InternalLink>             | Balance throughput and latency. Controls how many mutations are batched into each query to the target. Increase for higher throughput at the cost of higher latency.                                                                                                                                                                                                |
| <InternalLink version="molt" path="replicator-flags">`--targetApplyQueueSize`</InternalLink>  | Control memory usage during operation. Increase to allow higher throughput at the expense of memory; decrease to apply backpressure and limit memory consumption.                                                                                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--targetMaxPoolSize`</InternalLink>     | Set larger than <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink> by a safety factor to avoid exhausting target pool connections. Replicator enforces setting parallelism to 80% of this value.                                                                                                                                   |
| <InternalLink version="molt" path="replicator-flags">`--collapseMutations`</InternalLink>     | Reduce the number of queries to the target by combining multiple mutations on the same primary key within each batch. Disable only if exact mutation order matters more than end state.                                                                                                                                                                             |
| <InternalLink version="molt" path="replicator-flags">`--enableParallelApplies`</InternalLink> | Improve apply throughput for independent tables and table groups that share foreign key dependencies. Increases memory and target connection usage, so ensure you increase <InternalLink version="molt" path="replicator-flags">`--targetMaxPoolSize`</InternalLink> or reduce <InternalLink version="molt" path="replicator-flags">`--parallelism`</InternalLink>. |
| <InternalLink version="molt" path="replicator-flags">`--flushPeriod`</InternalLink>           | Set to the maximum allowable time between flushes (for example, `10s` if data must be applied within 10 seconds). Works with <InternalLink version="molt" path="replicator-flags">`--flushSize`</InternalLink> to control when buffered mutations are committed to the target.                                                                                      |
| <InternalLink version="molt" path="replicator-flags">`--quiescentPeriod`</InternalLink>       | Lower this value if constraint violations resolve quickly on your workload to make retries more frequent and reduce latency. Do not lower if constraint violations take time to resolve.                                                                                                                                                                            |
| <InternalLink version="molt" path="replicator-flags">`--scanSize`</InternalLink>              | Applies to <InternalLink version="molt" path="molt-replicator">failback</InternalLink> (`replicator start`) scenarios **only**. Balance memory usage and throughput. Increase to read more rows at once from the CockroachDB staging cluster for higher throughput, at the cost of memory pressure. Decrease to reduce memory pressure and increase stability.      |

#### Replicator metrics

MOLT Replicator metrics are not enabled by default. Enable Replicator metrics by specifying the <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink> flag with a port (or `host:port`) when you start Replicator. This exposes Replicator metrics at `http://{host}:{port}/_/varz`. For example, the following flag exposes metrics on port `30005`:

```
--metricsAddr :30005
```

For guidelines on using and interpreting replication metrics, refer to <InternalLink version="molt" path="replicator-metrics?filters=cockroachdb">Replicator Metrics</InternalLink>.

### Start MOLT Replicator (failback replication)

With initial load complete, start replication of ongoing changes on the source to CockroachDB using <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink>.

<Note>
  MOLT Fetch captures a consistent point-in-time checkpoint at the start of the data load (shown as `cdc_cursor` in the fetch output). Starting replication from this checkpoint ensures that all changes made during and after the data load are replicated to CockroachDB, preventing data loss or duplication. The following steps use the checkpoint values from the fetch output to start replication at the correct position.
</Note>

Run the <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> `start` command to begin failback replication from CockroachDB to your source database. In this example, `--metricsAddr :30005` enables a Prometheus endpoint for monitoring replication metrics, and `--bindAddr :30004` sets up the webhook endpoint for the changefeed.

`--stagingSchema` specifies the staging database name (`defaultdb._replicator` in this example) used for replication checkpoints and metadata. This staging database was created during [initial forward replication](#step-6-begin-forward-replication) when you first ran MOLT Replicator with `--stagingCreateSchema`.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
replicator start \
--targetConn $TARGET \
--stagingConn $STAGING \
--stagingSchema defaultdb._replicator \
--metricsAddr :30005 \
--bindAddr :30004 \
--tlsCertificate ./certs/server.crt \
--tlsPrivateKey ./certs/server.key \
-v
```

### Continue MOLT Replicator after an interruption (failback replication)

Run the <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> `oraclelogminer` command using the same `--stagingSchema` value from your [initial replication command](#start-molt-replicator-failback-replication).

Replicator will automatically find the correct restart SCN (System Change Number) from the `_oracle_checkpoint` table in the staging schema. The restart point is determined by the non-committed row with the smallest `startscn` column value.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
replicator oraclelogminer \
--sourceConn $SOURCE \
--sourcePDBConn $SOURCE_PDB \
--sourceSchema MIGRATION_USER \
--targetSchema defaultdb.migration_schema \
--targetConn $TARGET \
--stagingSchema defaultdb._replicator \
--metricsAddr :30005 \
--userscript table_filter.ts \
-v
```

<Note>
  When <InternalLink version="molt" path="userscript-cookbook#filter-multiple-tables">filtering out tables in a schema with a userscript</InternalLink>, replication performance may decrease because filtered tables are still included in LogMiner queries and processed before being discarded.
</Note>

Replication resumes from the last checkpoint without performing a fresh load. Monitor the metrics endpoint at `http://localhost:30005/_/varz` to track replication progress.

## Step 11: Cut over application traffic

With the target cluster verified and finalized, it's time to resume application traffic for the current migration phase.

### Modify application code

In the application back end, update the application to route traffic for this migration phase to the CockroachDB cluster. A simple example:

```yml theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
env:
  - name: DATABASE_URL_US_EAST
    value: postgres://root@cockroachdb.us-east:26257/defaultdb?sslmode=verify-full
  - name: DATABASE_URL_US_WEST
    value: postgres://legacy-db.us-west:5432/defaultdb  # Still on source
```

In your application code, route database connections based on the user's region:

```python theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
def get_db_connection(user_region):
    if user_region == "us-east":
        return os.getenv("DATABASE_URL_US_EAST")  # CockroachDB
    else:
        return os.getenv("DATABASE_URL_US_WEST")  # Source database
```

### Resume application traffic

If you halted traffic by scaling down a regional Kubernetes deployment, scale it back up.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
kubectl scale deploy/app-eu --replicas=3
```

Or if this was handled by the NGINX Controller, remove the 503 block that was written in step 3:

```yml theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
#   annotations:
#     nginx.ingress.kubernetes.io/server-snippet: |
#       if ($http_x_region = "eu") {
#         return 503;
#       }
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app
            port:
              number: 80
```

This ends downtime for the current migration phase.

## Step 12: Stop failback replication

After traffic has been cut over to the target, you can maintain failback replication indefinitely. Once you decide that you want to use the CockroachDB cluster as your sole data store going forward, you can end failback replication with the following steps.

1. Wait for replication to drain, which means that all transactions that occurred on the source database have been fully processed and replicated. There are several ways to determine that replication has fully drained:
   * When replication is caught up, you will not see new `upserted rows` logs.
   * If you set up the replication metrics endpoint with <InternalLink version="molt" path="replicator-flags">`--metricsAddr`</InternalLink> in the preceding steps, metrics are available at:

     ```
     http://{host}:{port}/_/varz
     ```

     Use the following Prometheus alert expression to observe when the combined rate of upserts and deletes is `0` for each schema:

     ```
     sum by (schema) (rate(apply_upserts_total[$__rate_interval]) + rate(apply_deletes_total[$__rate_interval]))
     ```
   * You can also check Prometheus metrics associated with replication lag, including <InternalLink version="molt" path="replicator-metrics#source-commit-to-apply-lag-seconds">`source_commit_to_apply_lag_seconds`</InternalLink>.

2. Cancel replication by entering `ctrl-c` to issue a `SIGTERM` signal. This returns an exit code `0`.

## Repeat for each phase

During the next scheduled migration phase, [return to step 3](#step-3-load-data-into-cockroachdb) to migrate the next phase of data. Repeat steps 3-12 for each phase of data, until every region's data has been migrated and all application traffic has been cut over to the target.

## See also

* <InternalLink version="molt" path="migration-overview">Migration Overview</InternalLink>
* <InternalLink version="molt" path="migration-considerations">Migration Considerations</InternalLink>
* <InternalLink version="molt" path="migration-approach-phased-bulk-load">Phased Bulk Load Migration</InternalLink>
* <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink>
* <InternalLink version="molt" path="molt-verify">MOLT Verify</InternalLink>
* <InternalLink version="molt" path="molt-fetch-troubleshooting">MOLT Fetch Troubleshooting for Oracle</InternalLink>
* <InternalLink version="molt" path="molt-replicator-troubleshooting">MOLT Replicator Troubleshooting for Oracle</InternalLink>
