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

# Migrate to Managed Postgres using PeerDB

> Learn how to migrate your PostgreSQL data to ClickHouse Managed Postgres using PeerDB

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

export const galaxyOnClick = eventName => () => {
  try {
    if (typeof window !== "undefined" && window.galaxy && eventName) {
      window.galaxy.track(eventName, {
        interaction: "click"
      });
    }
  } catch (e) {}
};

export const BetaBadge = ({link, galaxyTrack, galaxyEvent}) => {
  if (link) {
    return <a href={link} target="_blank" rel="noopener noreferrer" className="betaBadge" onClick={galaxyTrack && galaxyEvent ? galaxyOnClick(galaxyEvent) : undefined}>
                <Icon />
                <span>Beta</span>
            </a>;
  }
  return <div className="betaBadge">
            <Icon />
            <span>
                Beta feature. 
                <u>
                    <a href="/docs/beta-and-experimental-features#beta-features">
                        Learn more.
                    </a>
                </u>
            </span>
        </div>;
};

This guide provides step-by-step instructions on how to migrate your PostgreSQL database to ClickHouse Managed Postgres using PeerDB.

<h2 id="migration-peerdb-prerequisites">
  Prerequisites
</h2>

* Access to your source PostgreSQL database.
* A ClickHouse Managed Postgres instance where you want to migrate your data.
* PeerDB installed on a machine. You can follow the installation instructions on the [PeerDB GitHub repository](https://github.com/PeerDB-io/peerdb?tab=readme-ov-file#get-started). You just need to clone the repository and run `docker-compose up`. For this guide, we will be using **PeerDB UI**, which will be accessible at `http://localhost:3000` once PeerDB is running.

<h2 id="migration-peerdb-considerations-before">
  Considerations before migration
</h2>

Before starting your migration, keep the following in mind:

* **Database objects**: PeerDB will create tables automatically in the target database based on the source schema. However, certain database objects like indexes, constraints, and triggers won't be migrated automatically. You'll need to recreate these objects manually in the target database after the migration.
* **DDL changes**: If you enable continuous replication, PeerDB will keep the target database in sync with the source for DML operations (INSERT, UPDATE, DELETE) and will propagate ADD COLUMN operations. However, other DDL changes (like DROP COLUMN, ALTER COLUMN) aren't propagated automatically. More on schema changes support [here](/integrations/clickpipes/postgres/schema-changes)
* **Network connectivity**: Ensure that both the source and target databases are reachable from the machine where PeerDB is running. You may need to configure firewall rules or security group settings to allow connectivity.

<h2 id="migration-peerdb-create-peers">
  Create peers
</h2>

First, we need to create peers for both the source and target databases. A peer represents a connection to a database. In PeerDB UI, navigate to the "Peers" section by clicking on "Peers" in the sidebar. To create a new peer, click on the `+ New peer` button.

<h3 id="migration-peerdb-source-peer">
  Source peer creation
</h3>

Create a peer for your source PostgreSQL database by filling in the connection details such as host, port, database name, username, and password. Once you have filled in the details, click on the `Create peer` button to save the peer.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/source-peer.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=4417fc68e2fa792894a8d5fac973fbaf" alt="Source Peer Creation" size="md" border width="1682" height="1726" data-path="images/managed-postgres/peerdb/source-peer.png" />

<h3 id="migration-peerdb-target-peer">
  Target peer creation
</h3>

Similarly, create a peer for your ClickHouse Managed Postgres instance by providing the necessary connection details. You can get the [connection details](/products/managed-postgres/connection) for your instance from the ClickHouse Cloud console. After filling in the details, click on the `Create peer` button to save the target peer.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/target-peer.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=c58b5a6b341c9a5063a51b8a00c12c95" alt="Target Peer Creation" size="md" border width="1768" height="1806" data-path="images/managed-postgres/peerdb/target-peer.png" />

Now, you should see both the source and target peers listed in the "Peers" section.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/peers.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=260b92923d24e133f5042a275039ca63" alt="Peers List" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/peers.png" />

<h3 id="migration-peerdb-source-schema-dump">
  Obtain source schema dump
</h3>

To mirror the setup of the source database in the target database, we need to obtain a schema dump of the source database. You can use `pg_dump` to create a schema-only dump of your source PostgreSQL database:

<Accordion title="Installing pg_dump">
  **Ubuntu:**

  Update package lists:

  ```shell theme={null}
  sudo apt update
  ```

  Install PostgreSQL client:

  ```shell theme={null}
  sudo apt install postgresql-client
  ```

  **macOS:**

  Method 1: Using Homebrew (Recommended)

  Install Homebrew if you don't have it:

  ```shell theme={null}
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  ```

  Install PostgreSQL:

  ```shell theme={null}
  brew install postgresql
  ```

  Verify installation:

  ```shell theme={null}
  pg_dump --version
  ```
</Accordion>

```shell theme={null}
pg_dump -d 'postgresql://<user>:<password>@<host>:<port>/<database>'  -s > source_schema.sql
```

<h4 id="migration-peerdb-remove-constraints-indexes">
  Remove unique constraints and indexes from the schema dump
</h4>

Before applying this to the target database, we need to remove UNIQUE constraints and indexes from the dump file so that PeerDB ingestion to target tables is not blocked by these constraints. These can be removed using:

```shell theme={null}
# Preview
grep -n "CONSTRAINT.*UNIQUE" <dump_file_path>
grep -n "CREATE UNIQUE INDEX" <dump_file_path>
grep -n -E "(CONSTRAINT.*UNIQUE|CREATE UNIQUE INDEX)" <dump_file_path>

# Remove
sed -i.bak -E '/CREATE UNIQUE INDEX/,/;/d; /(CONSTRAINT.*UNIQUE|ADD CONSTRAINT.*UNIQUE)/d' <dump_file_path>
```

<h3 id="migration-peerdb-apply-schema-dump">
  Apply schema dump to target database
</h3>

After cleaning up the schema dump file, you can apply it to your target ClickHouse Managed Postgres database by [connecting](/products/managed-postgres/connection) via `psql` and running the schema dump file:

```shell theme={null}
psql -h <target_host> -p <target_port> -U <target_username> -d <target_database> -f source_schema.sql
```

Here on the target side, we do not want PeerDB ingestion to be blocked by foreign key constraints. For this, we can alter the target role (used above in the target peer) to have `session_replication_role` set to `replica`:

```sql theme={null}
ALTER ROLE <target_role> SET session_replication_role = replica;
```

<h2 id="migration-peerdb-create-mirror">
  Create a mirror
</h2>

Next, we need to create a mirror to define the data migration process between the source and target peers. In PeerDB UI, navigate to the "Mirrors" section by clicking on "Mirrors" in the sidebar. To create a new mirror, click on the `+ New mirror` button.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/create-mirror.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=00c6febdf9779ef693c6c7d2d6867bf0" alt="Create Mirror" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/create-mirror.png" />

1. Give your mirror a name that describes the migration.
2. Select the source and target peers you created earlier from the dropdown menus.
3. Make sure that:

* Soft delete is OFF.
* Expand `Advanced settings`. Make sure that the **Postgres type system is enabled** and **PeerDB columns are disabled**.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/settings.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=850d756b4455137cee303bcd0eaaae2f" alt="Mirror Settings" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/settings.png" />

4. Select the tables you want to migrate. You can choose specific tables or select all tables from the source database.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/table-picker.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=d62ec3ce8730ba7ce83ca134dd0b5c17" alt="Table Picker" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/table-picker.png" />

<Info>
  **Selecting tables**

  Make sure the destination table names are the same as the source table names in the target database, as we have migrated the schema as is in the earlier step.
</Info>

5. Once you have configured the mirror settings, click on the `Create mirror` button.

You should see your newly created mirror in the "Mirrors" section.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/mirrors.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=c2475877c05c50ff051cbf656a546a83" alt="Mirrors List" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/mirrors.png" />

<h2 id="migration-peerdb-initial-load">
  Wait for the initial load
</h2>

After creating the mirror, PeerDB will start the initial data load from the source to the target database. You can click on the mirror and click on the **Initial load** tab to monitor the progress of the initial data migration.

<Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/rF8ZX2ZZNpnwXrqH/images/managed-postgres/peerdb/initial-load.png?fit=max&auto=format&n=rF8ZX2ZZNpnwXrqH&q=85&s=aa5b12c9f2d298f37d53154ff07022c9" alt="Initial Load Progress" size="md" border width="3680" height="2392" data-path="images/managed-postgres/peerdb/initial-load.png" />

Once the initial load is complete, you should see a status indicating that the migration is finished.

<h2 id="migration-peerdb-monitoring">
  Monitoring initial load and replication
</h2>

If you click on the source peer, you can see a list of running commands which PeerDB is running. For instance:

1. Initially we run a COUNT query to estimate the number of rows in each table.
2. Then we run a partitioning query using NTILE to break down large tables into smaller chunks for efficient data transfer.
3. We then do FETCH commands to pull data from the source database and then PeerDB syncs them to the target database.

<h2 id="migration-peerdb-considerations">
  Post-migration tasks
</h2>

<Note>
  These steps may vary based on your specific use case and application requirements. The key is to ensure data consistency, minimize downtime, and validate the integrity of the migrated data before fully switching over to the new system.
</Note>

After the migration is complete:

* **Run pre-cutover validation checks**

Compare key tables between source and target before switching traffic:

```sql theme={null}
-- Row count comparison for critical tables
SELECT 'public.orders' AS table_name, COUNT(*) AS row_count FROM public.orders;
SELECT 'public.customers' AS table_name, COUNT(*) AS row_count FROM public.customers;

-- Spot-check latest records in high-activity tables
SELECT MAX(updated_at) FROM public.orders;
SELECT MAX(id) FROM public.orders;
```

* **Stop writes on the source system**

Pause application writes first. As an additional safeguard, set the source database to read-only during cutover:

```sql theme={null}
ALTER DATABASE <source_db> SET default_transaction_read_only = on;
```

If rollback is needed, you can re-enable writes:

```sql theme={null}
ALTER DATABASE <source_db> SET default_transaction_read_only = off;
```

* **Confirm replication is fully caught up**

Check that the latest row in one or more high-write tables matches on source and target:

```sql theme={null}
-- Run on both source and target and compare results
SELECT MAX(id) AS latest_id, MAX(updated_at) AS latest_ts FROM public.orders;
```

* **Recreate and enable constraints, indexes, and triggers**

If you removed or deferred constraints/indexes for ingestion, re-apply them now. Also reset the replication role on target if you previously set it to `replica`:

```sql theme={null}
ALTER ROLE <target_role> SET session_replication_role = origin;
```

```shell theme={null}
# Example: apply a SQL file containing constraints/indexes/triggers
psql -h <target_host> -p <target_port> -U <target_user> -d <target_db> -f post_migration_objects.sql
```

* **Reset sequences on target tables**

After data load, align sequences with current table values:

```sql theme={null}
-- Generic sequence reset for all serial/identity-backed columns in non-system schemas
DO $$
DECLARE r RECORD;
BEGIN
    FOR r IN
        SELECT
            n.nspname AS schema_name,
            c.relname AS table_name,
            a.attname AS column_name,
            pg_get_serial_sequence(format('%I.%I', n.nspname, c.relname), a.attname) AS seq_name
        FROM pg_class c
        JOIN pg_namespace n ON n.oid = c.relnamespace
        JOIN pg_attribute a ON a.attrelid = c.oid
        WHERE c.relkind = 'r'
            AND a.attnum > 0
            AND NOT a.attisdropped
            AND n.nspname NOT IN ('pg_catalog', 'information_schema')
    LOOP
        IF r.seq_name IS NOT NULL THEN
            EXECUTE format(
                'SELECT setval(%L, COALESCE((SELECT MAX(%I) FROM %I.%I), 0) + 1, false)',
                r.seq_name, r.column_name, r.schema_name, r.table_name
            );
        END IF;
    END LOOP;
END $$;
```

* **Cut over application traffic**

Once validation passes and sequences/constraints are in place:

1. Point read traffic to ClickHouse Managed Postgres.
2. Point write traffic to ClickHouse Managed Postgres.
3. Monitor application errors, constraint violations, and database health.

* **Clean up resources**

Once you're satisfied with the migration and have switched your application to use ClickHouse Managed Postgres, you can delete the mirror and peers in PeerDB.

<Info>
  **Replication slots**

  If you enabled continuous replication, PeerDB will create a **replication slot** on the source PostgreSQL database. Make sure to drop the replication slot manually from the source database after you're done with the migration to avoid unnecessary resource usage.
</Info>

<h2 id="migration-peerdb-references">
  References
</h2>

* [ClickHouse Managed Postgres Documentation](/products/managed-postgres/overview)
* [PeerDB guide for CDC creation](https://docs.peerdb.io/mirror/cdc-pg-pg)
* [Postgres ClickPipe FAQ (holds true for PeerDB as well)](/integrations/clickpipes/postgres/faq)

<h2 id="migration-pgdump-pg-restore-next-steps">
  Next steps
</h2>

Congratulations! You have successfully migrated your PostgreSQL database to ClickHouse Managed Postgres using pg\_dump and pg\_restore. You're now all set to explore Managed Postgres features and its integration with ClickHouse. Here's a 10 minute quickstart to get you going:

* [Managed Postgres Quickstart Guide](/products/managed-postgres/quickstart)
