|
| 1 | +Heroku Postgres to PlanetScale for Postgres via Bucardo asynchronous replication |
| 2 | +================================================================================ |
| 3 | + |
| 4 | +Heroku notably does not support logical replication, which has left many of its customers on outdated Postgres and without a convenient migration path to another provider. PlanetScale have tested a wide variety of plausible strategies and [Bucardo](https://bucardo.org/Bucardo/)'s trigger-based asynchronous replication has proven to be the most reliable option with the least downtime. |
| 5 | + |
| 6 | +There may be a variation of this strategy that uses the [`ff-seq.sh`](../postgres-direct/ff-seq.sh) tool from our logical replication strategy that can provide a true zero-downtime exit strategy from Heroku. [Get in touch ](mailto:[email protected]) if this is a requirement for you. |
| 7 | + |
| 8 | +Setup |
| 9 | +----- |
| 10 | + |
| 11 | +1. Launch an EC2 instance where you'll run Bucardo. It must run Linux and have network connectivity to both Heroku and PlanetScale. |
| 12 | + |
| 13 | +2. Install and configure Bucardo there: |
| 14 | + |
| 15 | + ```sh |
| 16 | + sh install.sh |
| 17 | + ``` |
| 18 | + |
| 19 | +3. Export two environment variables there: |
| 20 | + * `HEROKU`: URL-formatted Heroku Postgres connection information for the source database. |
| 21 | + * `PLANETSCALE`: Space-delimited PlanetScale for Postgres connection information for the `postgres` role (as shown on the Connect page for your database) for the target database. |
| 22 | + |
| 23 | +Bulk copy and replication |
| 24 | +------------------------- |
| 25 | + |
| 26 | +1. Sync table definitions outside of Bucardo (just like we'd do for logical replication and _before adding the databases to Bucardo_): |
| 27 | +
|
| 28 | + ```sh |
| 29 | + pg_dump --no-owner --no-privileges --no-publications --no-subscriptions --schema-only "$HEROKU" | psql "$PLANETSCALE" -a |
| 30 | + ``` |
| 31 | +
|
| 32 | +2. Connect the source Heroku Postgres database: |
| 33 | +
|
| 34 | + ```sh |
| 35 | + sudo -H -u "bucardo" bucardo add database "heroku" host="$(echo "$HEROKU" | cut -d "@" -f 2 | cut -d ":" -f 1)" user="$(echo "$HEROKU" | cut -d "/" -f 3 | cut -d ":" -f 1)" password="$(echo "$HEROKU" | cut -d ":" -f 3 | cut -d "@" -f 1)" dbname="$(echo "$HEROKU" | cut -d "/" -f 4 | cut -d "?" -f 1)" |
| 36 | + ``` |
| 37 | +
|
| 38 | +3. Connect the target PlanetScale for Postgres database: |
| 39 | +
|
| 40 | + ```sh |
| 41 | + sudo -H -u "bucardo" bucardo add database "planetscale" ${PLANETSCALE%%" ssl"*} |
| 42 | + ``` |
| 43 | +
|
| 44 | +4. Add all sequences: |
| 45 | +
|
| 46 | + ```sh |
| 47 | + sudo -H -u "bucardo" bucardo add all sequences --relgroup "planetscale_import" |
| 48 | + ``` |
| 49 | +
|
| 50 | +5. Add all tables: |
| 51 | +
|
| 52 | + ```sh |
| 53 | + sudo -H -u "bucardo" bucardo add all tables --relgroup "planetscale_import" |
| 54 | + ``` |
| 55 | +
|
| 56 | +6. Create a sync: |
| 57 | +
|
| 58 | + ```sh |
| 59 | + sudo -H -u "bucardo" bucardo add sync "planetscale_import" dbs="heroku,planetscale" onetimecopy=1 relgroup="planetscale_import" |
| 60 | + ``` |
| 61 | +
|
| 62 | +7. Start Bucardo replicating: |
| 63 | +
|
| 64 | + ```sh |
| 65 | + sudo -H -u "bucardo" bucardo reload |
| 66 | + ``` |
| 67 | +
|
| 68 | +Monitor progress |
| 69 | +---------------- |
| 70 | +
|
| 71 | +Bucardo status: |
| 72 | +
|
| 73 | +```sh |
| 74 | +sudo -H -u "bucardo" bucardo status |
| 75 | +``` |
| 76 | +
|
| 77 | +Bucardo's state will bounce between several descriptive values. It's not possible to confirm that your replication is caught up and keeping up based on these state values alone. Instead, you need to confirm that the complete data is present (e.g. by using the `count(*)` aggregation) before moving on. |
| 78 | +
|
| 79 | +Count rows to gauge how caught-up the asynchronous replication is (where `example` is one of your table names): |
| 80 | +
|
| 81 | +```sh |
| 82 | +psql "$HEROKU" -c "SELECT count(*) FROM example;"; psql "$PLANETSCALE" -c "SELECT count(*) FROM example;" |
| 83 | +``` |
| 84 | +
|
| 85 | +Run ad-hoc queries against the Bucardo metadata: |
| 86 | +
|
| 87 | +```sh |
| 88 | +sudo -H -u "bucardo" psql |
| 89 | +``` |
| 90 | +
|
| 91 | +Tail the Bucardo logs: |
| 92 | +
|
| 93 | +```sh |
| 94 | +tail -F "/var/log/bucardo/log.bucardo" |
| 95 | +``` |
| 96 | +
|
| 97 | +Switch traffic |
| 98 | +-------------- |
| 99 | +
|
| 100 | +Because Bucardo is replicating both table and sequence data, it's critical to stop write traffic at the source completely. Most likely, this can best be accomplished at the application level. However, it is possible to enforce at the database level, too: |
| 101 | + |
| 102 | +```sh |
| 103 | +psql "$HEROKU" -c "REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public FROM $(echo "$HEROKU" | cut -d "/" -f 3 | cut -d ":" -f 1);" |
| 104 | +``` |
| 105 | + |
| 106 | +Once writes have stopped reaching Heroku, it's safe to begin writes to PlanetScale via an application deploy or reconfiguration. |
| 107 | +
|
| 108 | +If you issued the `REVOKE` statement above and need to abort before switching traffic and return to service on Heroku, revert as follows: |
| 109 | +
|
| 110 | +```sh |
| 111 | +psql "$HEROKU" -c "GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO $(echo "$HEROKU" | cut -d "/" -f 3 | cut -d ":" -f 1);" |
| 112 | +``` |
| 113 | +
|
| 114 | +Cleanup |
| 115 | +------- |
| 116 | +
|
| 117 | +1. Stop and remove the Bucardo sync: |
| 118 | +
|
| 119 | + ```sh |
| 120 | + sudo -H -u "bucardo" bucardo remove sync "planetscale_import" |
| 121 | + ``` |
| 122 | +
|
| 123 | +2. Remove every table from Bucardo's management: |
| 124 | + |
| 125 | + ```sh |
| 126 | + sudo -H -u "bucardo" bucardo list tables | cut -d " " -f 3 | xargs sudo -H -u "bucardo" bucardo remove table |
| 127 | + ``` |
| 128 | + |
| 129 | +3. Remove every sequence from Bucardo's management: |
| 130 | +
|
| 131 | + ```sh |
| 132 | + sudo -H -u "bucardo" bucardo list sequences | cut -d " " -f 2 | xargs sudo -H -u "bucardo" bucardo remove sequence |
| 133 | + ``` |
| 134 | +
|
| 135 | +4. Remove intermediate Bucardo grouping objects: |
| 136 | +
|
| 137 | + ```sh |
| 138 | + sudo -H -u "bucardo" bucardo remove relgroup "planetscale_import" && sudo -H -u "bucardo" bucardo remove dbgroup "planetscale_import" |
| 139 | + ``` |
| 140 | +
|
| 141 | +5. Remove the PlanetScale database from Bucardo's management: |
| 142 | + |
| 143 | + ```sh |
| 144 | + sudo -H -u "bucardo" bucardo remove database "planetscale" |
| 145 | + ``` |
| 146 | + |
| 147 | +6. Remove the Heroku database from Bucardo's management: |
| 148 | +
|
| 149 | + ```sh |
| 150 | + sudo -H -u "bucardo" bucardo remove database "heroku" |
| 151 | + ``` |
| 152 | +
|
| 153 | +7. Stop Bucardo: |
| 154 | +
|
| 155 | + ```sh |
| 156 | + sudo -H -i -u "bucardo" bucardo stop |
| 157 | + ``` |
| 158 | +
|
| 159 | +8. Remove Bucardo metadata from the Heroku database: |
| 160 | +
|
| 161 | + ```sh |
| 162 | + psql "$HEROKU" -c "DROP SCHEMA bucardo CASCADE;" |
| 163 | + ``` |
| 164 | +
|
| 165 | +8. Optionally, terminate the EC2 instance that was hosting Bucardo. |
| 166 | +
|
| 167 | +9. When the migration is complete and validated, delete the source Heroku Postgres database. |
| 168 | +
|
| 169 | +See also |
| 170 | +-------- |
| 171 | +
|
| 172 | +* <https://bucardo.org/Bucardo/pgbench_example> |
| 173 | +* <https://gist.github.com/Leen15/da42bd23b363867e14a378d824f2064e> |
| 174 | +* <https://smartcar.com/blog/zero-downtime-migration> |
| 175 | +* <https://medium.com/@logeshmohan/postgresql-replication-using-bucardo-5-4-1-6e78541ceb5e> |
| 176 | +* <https://justatheory.com/2013/02/bootstrap-bucardo-mulitmaster/> |
| 177 | +* <https://www.porter.run/blog/migrating-postgres-from-heroku-to-rds> |
| 178 | +* <https://medium.com/hellogetsafe/pulling-off-zero-downtime-postgresql-migrations-with-bucardo-and-terraform-1527cca5f989> |
| 179 | +* <https://github.com/nxt-insurance/bucardo-terraform-archive> |
| 180 | +* <https://bucardo-general.bucardo.narkive.com/hznUofas/replication-of-tables-without-primary-keys> |
0 commit comments