JFrog Artifactory DB Migration

In this blog post, we will demonstrate how to migrate a JFrog Artifactory PostgreSQL Database managed by Crunchy Operator to a CloudNativePG Operator online without having an outage (the only outage could be produced by reconfiguring your application to point to the new database). For PostgreSQL logical replication, we based it on a blog post written by Percona.

Motivation

Why did we migrate Artifactory PostgreSQL DB from Crunchy Operator to CloudNativePG Operator?
Both, Crunchy and CloudNativePG Operators are very similar but there is one difference which motivated us to perform this migration - costs. While usage of CloudNativePG Operator is free of charge the Crunchy Operator requires a subscription.

There are exceptions where Crunchy Operator can be used without a subscription. For more details you can refer to Crunchy Data Terms Of Use.

Our Environment

For the sake of simplicity, we use a 2-pod setup for the current Artifactory DB managed by Crunchy Operator and 1 pod for the Target Artifactory DB managed by CloudNativePG. Once the migration is done, you can scale the Target Artifactory DB up to many pods as you require.

Pod Details Pod Name Namespace Operator PostgreSQL Version
Primary source-db-lp6h-0 postgres-db Crunchy 13.9
Secondary source-db-9d4m-0 postgres-db Crunchy 13.9
Target target-db-1 cnpg-db CloudNativePG 13.9

Migration

To perform the DB migration, an empty CloudNativePG DB Cluster for JFrog Artifactory is required. If you need assistance setting up a CloudNativePG PostgreSQL Cluster, please check the official CloudNativePG Documentation or contact us.

Step 1: Create a Migration User

Create a migration user on Primary and Target DB pod:

CREATE USER dbmigration WITH SUPERUSER PASSWORD '***';

The dbmigration user is only used for migration purposes. After the migration, this user should be deleted.

Step 2: Create a Publication

Log in as the dbmigration user to the artifactory DB on the Primary DB pod:

psql -U dbmigration -h source-db-lp6h-0 -d artifactory

Create a publication for all tables in the artifactory DB:

CREATE PUBLICATION artifactorypub FOR ALL TABLES;

Step 3: Create a Logical Replication Slot

On the Primary DB pod, create a logical replication slot to capture database changes:

SELECT pg_create_logical_replication_slot('artifactorypub_slot', 'pgoutput');

Verify the logical replication slot:

SELECT * FROM pg_replication_slots;

Step 4: Pause Replication

Log in as the postgres user to the Secondary DB:

psql -U postgres -h source-db-9d4m-0

Pause the replication between the Primary and Secondary DB:

SELECT pg_wal_replay_pause();

With paused replication we can make sure that no updates are written to the secondary DB.
Verify if the replication is paused:

SELECT pg_is_wal_replay_paused();

Step 5: Note the replay_lsn for replica

On the Primary DB pod note replay_lsn (Log Sequence Number). Later we will configure the replay_lsn on Target DB from which Log Sequence Number the replication should be continued:

SELECT client_addr, application_name, replay_lsn FROM pg_stat_replication;

The output should be similar to below

client_addr |   application_name    |  replay_lsn
------------+-----------------------+---------------
10.128.8.21 |   source-db-9d4m-0    | 12CB/9500BFA8
(1 row)

Step 6: Dump and restore artifactory DB

On target pod target-db-1 create an artifactory DB dump from Secondary DB pod:

pg_dump -d artifactory -U artifactory -h source-db-9d4m-0.source-pods.postgres-db.svc -Fc > artifactory_dmp.db

Restore artifactory DB on Target DB:

pg_restore -h target-db-1 -U artifactory -d artifactory artifactory_dmp.db

Step 7: Resume the replication

Since we have the current state of the Secondary DB restored to the Target DB and have noted the Log Sequence Number the replication between the Primary and the Secondary DB can be enabled again.

Log in as the postgres user to the Secondary DB:

psql -U postgres -h source-db-9d4m-0

resume the replication:

SELECT pg_wal_replay_resume();

Verify if the replication is resumed:

SELECT pg_is_wal_replay_paused();

Step 8: Create a subscription

Login as the dbmigration user to artifactory db on Target DB pod:

psql -U dbmigration -h target-db-1 -d artifactory

Create subscription pointing to Primary DB's service:

CREATE SUBSCRIPTION artifactorysub CONNECTION 'host=source-primary.postgres-db.svc port=5432 dbname=artifactory user=dbmigration password=***' PUBLICATION artifactorypub WITH (copy_data = false, create_slot=false, enabled=false, slot_name=artifactorypub_slot);

With a subscription the Target DB will receive updates from the publication we created earlier on Primary DB. Verify if the subscription was created:

SELECT * FROM pg_stat_subscription;

Step 9: Advance the replication

To advance the replication on the Target DB we require the identifier returned by pg_replication_origin:

SELECT roident, subname, roname FROM pg_subscription sub, pg_replication_origin ro WHERE 'pg_' || sub.oid = ro.roname;

The output should be similar to below

roident |    subname     |  roname
--------+----------------+----------
      1 | artifactorysub | pg_16400
(1 row)

Now update the replication origin with the roname value you got from above command and with replay_lsn gathered in step 5

SELECT pg_replication_origin_advance('pg_16400', '12CB/9500BFA8');

Step 10: Enable subscription

On the Target DB pod enable the subscription. Once enabled, the logical replication will start:

ALTER SUBSCRIPTION artifactorysub ENABLE;

Verify if the subscription is enabled:

SELECT * FROM pg_stat_subscription;

You should now see timestamps of send and received messages

subid |    subname     | pid  | relid | received_lsn  |      last_msg_send_time       |     last_msg_receipt_time     | latest_end_lsn |        latest_end_time
------+----------------+------+-------+---------------+-------------------------------+-------------------------------+----------------+-------------------------------
18167 | artifactorysub | 3117 |       | 12CB/A5001958 | 2024-11-22 07:53:41.152916+00 | 2024-11-22 07:53:41.153084+00 | 12CB/A5001958  | 2024-11-22 07:53:41.152916+00
(1 row)

Step 11: Verify Database Synchronization

To ensure data synchronization between the Primary and Target DBs, execute this query on both DB pods:

SELECT count(*) FROM access_tokens;

The results should match.

Step 12: Cleanup

Once your application points to the new Target DB, remove the migration setup:

Disable the subscription:

ALTER SUBSCRIPTION artifactorysub DISABLE;

Drop the subscription:

DROP SUBSCRIPTION artifactorysub;

Delete the migration user:

DROP USER dbmigration;

Conclusion

Before you run the DB migration on production environment you might repeat the above steps several times on your test environment to become more familiar with the migration. For this you can delete your Target DB Cluster and remove logical replication slot and publication on your Source DB. If you have questions or you require an assistance to migrate your PostgreSQL database do not hesitate to contact us.

Kristaps Radebahs

DevOps / Cloud Engineer

A highly competent and organized systems administrator with experience of software procurement, installation and administration.

Contact

Contact Us