> ## 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.

# Delta Migration from MySQL

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-delta">*Delta Migration*</InternalLink> uses an initial data load, followed by <InternalLink version="molt" path="migration-considerations-replication">continuous replication</InternalLink>, to <InternalLink version="molt" path="migration-overview">migrate data to CockroachDB</InternalLink>. In this approach, you migrate most application data to the target using <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink> **before** stopping application traffic to the source database. You then use <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> to keep the target database in sync with any changes in the source database (the migration *delta*), before finally halting traffic to the source and cutting over to the target after schema finalization and data verification.

* All source data is migrated to the target <InternalLink version="molt" path="migration-considerations-granularity">at once</InternalLink>.

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

* <InternalLink version="molt" path="migration-considerations-rollback">Failback replication</InternalLink> is supported, though this example will not use it. See <InternalLink version="molt" path="migration-approach-phased-delta-failback">Phased Delta Migration with Failback Replication</InternalLink> for an example of a migration that uses failback replication.

This approach is best for production environments that need to <InternalLink version="molt" path="migration-considerations#permissible-downtime">minimize system downtime</InternalLink>.

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_delta_flow.svg?fit=max&auto=format&n=mEy4bzaxzRbvNkMw&q=85&s=5e8db5dd7413fc42558032aefc8e1eba" alt="Delta migration flow" style={{ maxWidth: "100%", display: "block", marginLeft: "auto", marginRight: "auto" }} width="456" height="391" data-path="images/molt/molt_delta_flow.svg" />

## Example scenario

You have a small (300 GB) database that provides the data store for a web application. You want to migrate the entirety of this database to a new CockroachDB cluster. Business cannot accommodate a full maintenance window, but it can accommodate a brief (\<60 second) halt in traffic.

The application runs on a Kubernetes cluster.

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

## 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:

### 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.

#### 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>.

* MySQL replication is supported only with [GTID](https://dev.mysql.com/doc/refman/8.0/en/replication-gtids.html)-based configurations. Binlog-based features that do not use GTID are not supported.

#### 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.

* MOLT Verify only supports comparing one MySQL database to a whole CockroachDB schema (which is assumed to be `public`).

## 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';
```

Grant the user privileges to select the tables you migrate and access GTID information for snapshot consistency:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT SELECT ON migration_db.* TO 'migration_user'@'%';
GRANT SELECT ON mysql.gtid_executed TO 'migration_user'@'%';
FLUSH PRIVILEGES;
```

For replication, grant additional privileges for binlog access:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'migration_user'@'%';
FLUSH PRIVILEGES;
```

### 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 [global transaction identifiers (GTID)](https://dev.mysql.com/doc/refman/8.0/en/replication-options-gtids.html) and configure binary logging. Set `binlog-row-metadata` or `binlog-row-image` to `full` to provide complete metadata for replication.

Configure binlog retention to ensure GTIDs remain available throughout the migration:

* MySQL 8.0.1+: Set `binlog_expire_logs_seconds` (default: 2592000 = 30 days) based on your migration timeline.
* MySQL \< 8.0: Set `expire_logs_days`, or manually manage retention by setting `max_binlog_size` and using `PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 HOUR` (adjusting the interval as needed). Force binlog rotation with `FLUSH BINARY LOGS` if needed.
* Managed services: Refer to provider-specific configuration for [Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/mysql-stored-proc-configuring.html) or [Google Cloud SQL](https://cloud.google.com/sql/docs/mysql/flags#mysql-b).

<Note>
  GTID replication sends all database changes to Replicator. To limit replication to specific tables or schemas, [apply a userscript](#replicator-flags) when you run Replicator. Refer to the <InternalLink version="molt" path="userscript-cookbook#filter-multiple-tables">Filter multiple tables</InternalLink> cookbook example.
</Note>

| Version    | Configuration                                                                                                                                                                                              |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MySQL 5.6  | `--gtid-mode=on`<br />`--enforce-gtid-consistency=on`<br />`--server-id={unique_id}`<br />`--log-bin=mysql-binlog`<br />`--binlog-format=row`<br />`--binlog-row-image=full`<br />`--log-slave-updates=ON` |
| MySQL 5.7  | `--gtid-mode=on`<br />`--enforce-gtid-consistency=on`<br />`--binlog-row-image=full`<br />`--server-id={unique_id}`<br />`--log-bin=log-bin`                                                               |
| MySQL 8.0+ | `--gtid-mode=on`<br />`--enforce-gtid-consistency=on`<br />`--binlog-row-metadata=full`                                                                                                                    |
| MariaDB    | `--log-bin`<br />`--server_id={unique_id}`<br />`--log-basename=master1`<br />`--binlog-format=row`<br />`--binlog-row-metadata=full`                                                                      |

## 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.

  MySQL tables belong directly to the database specified in the connection string. A MySQL source table defined as `CREATE TABLE tbl (id INT PRIMARY KEY);` should map to CockroachDB's default `public` schema:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  CREATE TABLE tbl (id 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.

* 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>.

Syntax that cannot automatically be converted will be displayed in the <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql#summary-report">**Summary Report**</InternalLink>. These may include the following:

##### String case sensitivity

Strings are case-insensitive in MySQL and case-sensitive in CockroachDB. You may need to edit your MySQL data to get the results you expect from CockroachDB. For example, you may have been doing string comparisons in MySQL that will need to be changed to work with CockroachDB.

For more information about the case sensitivity of strings in MySQL, refer to [Case Sensitivity in String Searches](https://dev.mysql.com/doc/refman/8.0/en/case-sensitivity.html) from the MySQL documentation. For more information about CockroachDB strings, refer to <InternalLink version="stable" path="string">`STRING`</InternalLink>.

##### Identifier case sensitivity

Identifiers are case-sensitive in MySQL and <InternalLink version="stable" path="keywords-and-identifiers#identifiers">case-insensitive in CockroachDB</InternalLink>. When <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql#convert-a-schema">using the Schema Conversion Tool</InternalLink>, you can either keep case sensitivity by enclosing identifiers in double quotes, or make identifiers case-insensitive by converting them to lowercase.

##### `AUTO_INCREMENT` attribute

The MySQL [`AUTO_INCREMENT`](https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html) attribute, which creates sequential column values, is not supported in CockroachDB. When <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql#convert-a-schema">using the Schema Conversion Tool</InternalLink>, columns with `AUTO_INCREMENT` can be converted to use <InternalLink version="stable" path="create-sequence">sequences</InternalLink>, `UUID` values with <InternalLink version="stable" path="functions-and-operators">`gen_random_uuid()`</InternalLink>, or unique `INT8` values using <InternalLink version="stable" path="functions-and-operators">`unique_rowid()`</InternalLink>. Cockroach Labs does not recommend using a sequence to define a primary key column. For more information, refer to <InternalLink version="stable" path="performance-best-practices-overview#unique-id-best-practices">Unique ID best practices</InternalLink>.

<Note>
  Changing a column type during table definition conversion will cause <InternalLink version="molt" path="molt-verify">MOLT Verify</InternalLink> to identify a type mismatch during data validation. This is expected behavior.
</Note>

##### `ENUM` type

MySQL `ENUM` types are defined in table columns. On CockroachDB, <InternalLink version="stable" path="enum">`ENUM`</InternalLink> is a standalone type. When <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql#convert-a-schema">using the Schema Conversion Tool</InternalLink>, you can either deduplicate the `ENUM` definitions or create a separate type for each column.

##### `TINYINT` type

`TINYINT` data types are not supported in CockroachDB. The <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql">Schema Conversion Tool</InternalLink> automatically converts `TINYINT` columns to <InternalLink version="stable" path="int">`INT2`</InternalLink> (`SMALLINT`).

##### Geospatial types

MySQL geometry types are not converted to CockroachDB <InternalLink version="stable" path="spatial-data-overview#spatial-objects">geospatial types</InternalLink> by the <InternalLink version="cockroachcloud" path="migrations-page?filters=mysql">Schema Conversion Tool</InternalLink>. They should be manually converted to the corresponding types in CockroachDB.

##### `FIELD` function

The MYSQL `FIELD` function is not supported in CockroachDB. Instead, you can use the <InternalLink version="stable" path="functions-and-operators">`array_position`</InternalLink> function, which returns the index of the first occurrence of element in the array.

Example usage:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT array_position(ARRAY[4,1,3,2],1);
```

```
  array_position
------------------
               2
(1 row)
```

While MySQL returns 0 when the element is not found, CockroachDB returns `NULL`. So if you are using the `ORDER BY` clause in a statement with the `array_position` function, the caveat is that sort is applied even when the element is not found. As a workaround, you can use the <InternalLink version="stable" path="functions-and-operators#conditional-and-function-like-operators">`COALESCE`</InternalLink> operator.

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT * FROM table_a ORDER BY COALESCE(array_position(ARRAY[4,1,3,2],5),999);
```

#### 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 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 public 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>.

## 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.
* <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:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
molt fetch \
--source $SOURCE \
--target $TARGET \
--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`.

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   molt fetch \
   --source $SOURCE \
   --target $TARGET \
   --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":"4c658ae6-e8ad-11ef-8449-0242ac140006:1-29","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":"public.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":"public.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":"public.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":"public.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":"4c658ae6-e8ad-11ef-8449-0242ac140006:1-29","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":["public.employees","public.payments","public.payments"],"skipped_unmigratable_tables":[],"cdc_cursor":"4c658ae6-e8ad-11ef-8449-0242ac140006:1-29","net_duration_ms":6752.847625,"net_duration":"000h 00m 06s","time":"2024-03-18T12:30:37-04:00","message":"fetch complete"}
   ```

   This message includes a `cdc_cursor` value. You must set the `--defaultGTIDSet` replication flag to this value when [starting Replicator](#start-replicator):

   ```
   --defaultGTIDSet 4c658ae6-e8ad-11ef-8449-0242ac140006:1-29
   ```

### 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>.

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
molt fetch \
--source $SOURCE \
--target $TARGET \
--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

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.

### 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'
   ```

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).
* [Start MOLT Replicator](#start-molt-replicator).
* [Understand how to continue replication after an interruption](#continue-molt-replicator-after-an-interruption).

### Configure MOLT Replicator

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.
* [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 'mysql://{username}:{password}@{protocol}({host}:{port})/{database}'
```

`--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">`--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">`--defaultGTIDSet`</InternalLink>      | **Required.** Default GTID set for changefeed.                                                                                                                                                                                                                           |
| <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">`--fetchMetadata`</InternalLink>       | Explicitly fetch column metadata for MySQL versions that do not support `binlog_row_metadata`. Requires `SELECT` permissions on the source database or `PROCESS` privileges.                                                                                             |
| <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 starting GTID in the `cdc_cursor` field of the `fetch complete` message after the [initial data load](#start-fetch) completes.

#### 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=mysql">Replicator Metrics</InternalLink>.

### Start MOLT Replicator

<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>

1. Run the `replicator` command, specifying the GTID 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 mylogical \
   --sourceConn $SOURCE \
   --targetConn $TARGET \
   --targetSchema defaultdb.public \
   --defaultGTIDSet 4c658ae6-e8ad-11ef-8449-0242ac140006:1-29 \
   --stagingSchema defaultdb._replicator \
   --stagingCreateSchema \
   --metricsAddr :30005 \
   --userscript table_filter.ts \
   -v
   ```

   <Tip>
     For MySQL versions that do not support `binlog_row_metadata`, include `--fetchMetadata` to explicitly fetch column metadata. This requires additional permissions on the source MySQL database. Grant `SELECT` permissions with `GRANT SELECT ON migration_db.* TO 'migration_user'@'localhost';`. If that is insufficient for your deployment, use `GRANT PROCESS ON *.* TO 'migration_user'@'localhost';`, though this is more permissive and allows seeing processes and server status.
   </Tip>

#### Check that replication is working

1. 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:

   You should see binlog syncer connection and row processing:

   ```
   [2025/08/25 15:29:09] [info] binlogsyncer.go:463 begin to sync binlog from GTID set 77263736-7899-11f0-81a5-0242ac120002:1-38
   [2025/08/25 15:29:09] [info] binlogsyncer.go:409 Connected to mysql 8.0.43 server
   INFO   [2025-08-25T15:29:09-05:00] connected to MySQL version 8.0.43
   ```

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

   ```
   DEBUG  [2025-08-25T15:29:38-05:00] upserted rows                                 conflicts=0 duration=1.801ms proposed=1 target="\"molt\".\"public\".\"tbl1\"" upserted=1
   DEBUG  [2025-08-25T15:29:38-05:00] progressed to consistent point: 77263736-7899-11f0-81a5-0242ac120002:1-39
   ```

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

### Continue MOLT Replicator after an interruption

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

Replicator will automatically use the saved GTID (Global Transaction Identifier) from the `memo` table in the staging schema (in this example, `defaultdb._replicator.memo`) and track advancing GTID checkpoints there. To have Replicator start from a different GTID instead of resuming from the checkpoint, clear the `memo` table with `DELETE FROM defaultdb._replicator.memo;` and run the `replicator` command with a new `--defaultGTIDSet` value.

<Tip>
  For MySQL versions that do not support `binlog_row_metadata`, include `--fetchMetadata` to explicitly fetch column metadata. This requires additional permissions on the source MySQL database. Grant `SELECT` permissions with `GRANT SELECT ON migration_db.* TO 'migration_user'@'localhost';`. If that is insufficient for your deployment, use `GRANT PROCESS ON *.* TO 'migration_user'@'localhost';`, though this is more permissive and allows seeing processes and server status.
</Tip>

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

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. Scale down the Kubernetes cluster to zero pods.

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

<Danger>
  Application downtime begins now.

  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">`target_apply_transaction_lag_seconds`</InternalLink>, <InternalLink version="molt" path="replicator-metrics">`core_source_lag_seconds`</InternalLink>, and <InternalLink version="molt" path="replicator-metrics">`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: Cut over application traffic

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

### Modify application code

In the application back end, make sure that the application now directs traffic to the CockroachDB cluster. For example:

```yml theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
env:
  - name: DATABASE_URL
    value: postgres://root@localhost:26257/defaultdb?sslmode=verify-full
```

### Resume application traffic

Scale up the Kubernetes deployment to the original number of replicas:

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

This ends downtime.

## 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 MySQL</InternalLink>
* <InternalLink version="molt" path="molt-replicator-troubleshooting">MOLT Replicator Troubleshooting for MySQL</InternalLink>
