пятница, 13 июля 2018 г.

Обновление версии PostgreSQL в Amazon RDS без даунтайма

Цель: обновить инстанс PostgreSQL, который работает на версии 9.4 до 9.5.

RDS не позволяет обновлять PostgreSQL на несколько мажорных версий, поэтому если нужно обновиться например с 9.4 до 10, то итерацию придется повторять несколько раз.

Также важно то, чтобы были доступы для rds_superuser, без этого не получится настроить репликацию.

Пример как это все работает на тестовом стенде я описал ниже.

Создаем наш RDS-инстанс:
export PGUSERNAME=admin_rds
export PGPASSWORD=admin_rds_pass

aws rds create-db-instance \
--db-instance-identifier upgrade-tests \
--allocated-storage 5 \
--db-instance-class db.t2.micro \
--engine postgres \
--master-username ${PGUSERNAME} \
--master-user-password ${PGPASSWORD} \
--engine-version 9.4.15

Заполним нашу базу тестовыми данными:
PGENDPOINT=$(aws rds describe-db-instances --db-instance-identifier upgrade-tests | grep Address | awk '{print $4}')
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} postgres -c "CREATE DATABASE sample;"

wget http://pgfoundry.org/frs/download.php/527/world-1.0.tar.gz
tar xzf world-1.0.tar.gz

psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample < dbsamples-0.1/world/world.sql

Снимаем снапшот с базы:
aws rds create-db-snapshot \
--db-snapshot-identifier upgrade-tests-snapshot \
--db-instance-identifier upgrade-tests

Убеждаемся что, снапшот в статусе available:
aws rds describe-db-snapshots --db-snapshot-identifier upgrade-tests-replica-snapshot | grep Status | awk '{print $4}'
available

Создаем второй мастер, на основе ранее сделанного снапшота:
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier upgrade-tests-2 \
--db-snapshot-identifier upgrade-tests-snapshot \
--db-instance-class db.t2.micro \
--engine postgres

Делать это нужно в той же зоне, что и первый инстанс и в той же Security Group, для того чтобы не гонять трафик через интернет.

Проверим статус нашего второго инстанса:
PGENDPOINT2=$(aws rds describe-db-instances --db-instance-identifier upgrade-tests-2 | grep Address | awk '{print $4}')
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} postgres -c "select version();"
 PostgreSQL 9.4.15 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit

Обновим версию PostgreSQL, это занимает довольно много времени:
aws rds modify-db-instance \
--db-instance-identifier upgrade-tests-2 \
--allow-major-version-upgrade \
--engine-version 9.5.12 \
--apply-immediately

После обновления проверим версию PostgreSQL:
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} postgres -c "select version();"
 PostgreSQL 9.5.12 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit

Далее нужно настроить мастер-мастер репликацию между ними.
Тут описаны различные тулзы для этого.

Для себя я выделил:
  • BDR - нужно перекомпиливать PostgreSQL с BDR-плагином
  • PgCluster - тоже
  • Rubyrep - проект скорее мертв чем жив
  • Bucardo - похоже на правду

Включаем реплику на обеих нодах:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} postgres -c "SET session_replication_role = replica; CREATE EXTENSION plperl;"
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} postgres -c "SET session_replication_role = replica; CREATE EXTENSION plperl;"

Создаем инстанс в том же регионе и той же Security Group, чтобы общаться с RDS по интранету.
На инстансе устанавливаем bucardo:
apt update && apt install bucardo postgresql-plperl-9.5 -y
sed -i 's/ENABLED=0/ENABLED=1/' /etc/default/bucardo
mkdir /var/run/bucardo

Устанавливаем пароль для юзера postgres:
sudo -u postgres psql postgres
postgres=# \password postgres

Создаем базу:
export PGPASSWORD=bucardo
psql -h localhost -U postgres postgres -c "CREATE USER bucardo WITH LOGIN SUPERUSER ENCRYPTED PASSWORD 'bucardo';"
psql -h localhost -U postgres postgres -c "CREATE DATABASE bucardo;" 

bucardo install

Добавляем оба инстанса  в bucardo:
export SOURCE_HOST=upgrade-tests.cge9boeglav8.eu-west-1.rds.amazonaws.com
export SOURCE_PORT=5432
export SOURCE_DATABASE=sample
export SOURCE_USERNAME=admin_rds
export SOURCE_PASSWORD=admin_rds_pass

export DEST_HOST=upgrade-tests-2.cge9boeglav8.eu-west-1.rds.amazonaws.com 
export DEST_PORT=5432  
export DEST_DATABASE=sample
export DEST_USERNAME=admin_rds
export DEST_PASSWORD=admin_rds_pass

bucardo add db source_db dbhost=$SOURCE_HOST dbport=$SOURCE_PORT dbname=$SOURCE_DATABASE dbuser=$SOURCE_USERNAME dbpass=$SOURCE_PASSWORD
bucardo add db dest_db dbhost=$DEST_HOST dbport=$DEST_PORT dbname=$DEST_DATABASE dbuser=$DEST_USERNAME dbpass=$DEST_PASSWORD

bucardo list database
Database: dest_db    Status: active  Conn: psql -p 5432 -U admin_rds -d sample -h upgrade-tests-2.cge9boeglav8.eu-west-1.rds.amazonaws.com
Database: source_db  Status: active  Conn: psql -p 5432 -U admin_rds -d sample -h upgrade-tests.cge9boeglav8.eu-west-1.rds.amazonaws.com

Добавляем все таблицы и последовательности в группу:
bucardo add table all --db=source_db --herd=sample_herd
Creating relgroup: sample_herd
Added table public.city to relgroup sample_herd
Added table public.country to relgroup sample_herd
Added table public.countrylanguage to relgroup sample_herd
New tables added: 3

bucardo list tables
1. Table: public.city             DB: source_db  PK: id (integer)
2. Table: public.country          DB: source_db  PK: code (bpchar)
3. Table: public.countrylanguage  DB: source_db  PK: countrycode|language (bpchar|text)

bucardo list herd
Relgroup: sample_herd  DB: source_db  Members: public.city, public.country, public.countrylanguage

bucardo add sequence all --db=source_db --herd=sample_herd
Sorry, no sequences were found
New sequences added: 0

Создаем группу репликации:
bucardo add dbgroup mydb_servers_group
bucardo add dbgroup mydb_servers_group source_db:source
bucardo add dbgroup mydb_servers_group dest_db:source
bucardo add sync mydb_sync herd=sample_herd dbs=mydb_servers_group

bucardo list sync
Sync "mydb_sync"  Relgroup "sample_herd" [Active]
  DB group "mydb_servers_group" dest_db:source source_db:source

После этого можно запускать bucardo:
bucardo start

Тестируем репликацию (CRUD).

Проверим версии мастеров:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} postgres -c "select version();"
 PostgreSQL 9.4.15 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit

psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} postgres -c "select version();"
 PostgreSQL 9.5.12 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit

Insert

Пишем в мастер1, проверяем на мастер2:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "INSERT INTO city (id, name, countrycode, district, population) VALUES (4080, 'Newtown', 'UKR', 'Kiev', 100);"

psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4080;"
  id  |    name    | countrycode | district | population 
------+------------+-------------+----------+------------
 4080 | Newtown    | UKR         | Kiev     |        100

Пишем в мастер2, проверяем на мастер1:
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "INSERT INTO city (id, name, countrycode, district, population) VALUES (4081, 'Newcity', 'DEU', 'Berlin', 5);"

psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4081;"
  id  |    name    | countrycode | district | population 
------+------------+-------------+----------+------------
 4081 | Newcity    | DEU         | Berlin   |          5

Update

Обновляем на мастер1, проверяем на мастер2:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "UPDATE city SET population=101 WHERE id=4080;"

psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4080;"
  id  |    name    | countrycode | district | population 
------+------------+-------------+----------+------------
 4080 | Newtown    | UKR         | Kiev     |        101

Обновляем на мастер2, проверяем на мастер1:
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "UPDATE city SET district='Hamburg' WHERE id=4081;"

psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4081;"
  id  |    name    | countrycode | district | population 
------+------------+-------------+----------+------------
 4081 | Newcity    | DEU         | Hamburg  |          5

Delete

Удаляем на мастер1, проверяем на мастер2:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "DELETE FROM city WHERE id=4080;"

psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4080;"
 id | name | countrycode | district | population 
----+------+-------------+----------+------------

Удаляем на мастер2, проверяем на мастер1:
psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "DELETE FROM city WHERE id=4081;"

psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT * FROM city WHERE id=4081;"
  id  |    name    | countrycode | district | population 
------+------------+-------------+----------+------------

Все тесты пройдены успешны.

Как проверить статус реплики и оставание одного мастера от другого.

С помощью bucardo:
bucardo delta source_db
      Total deltas across all targets: 0
  Total deltas for database source_db: 0

bucardo delta dest_db
    Total deltas across all targets: 0
  Total deltas for database dest_db: 0

bucardo status mydb_sync
======================================================================
Last good                : Jul 12, 2018 12:47:04 (time to run: 1s)
Rows deleted/inserted    : 1 / 1
Sync name                : mydb_sync
Current state            : Good
Source relgroup/database : sample_herd / source_db
Tables in sync           : 3
Status                   : Active
Check time               : None
Overdue time             : 00:00:00
Expired time             : 00:00:00
Stayalive/Kidsalive      : Yes / Yes
Rebuild index            : No
Autokick                 : Yes
Onetimecopy              : No
Post-copy analyze        : Yes
Last error:              : 
======================================================================

С помощью SQL:
psql -h ${PGENDPOINT} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT pg_last_xlog_receive_location() AS receive, pg_last_xlog_replay_location() AS replay , COALESCE(ROUND(EXTRACT(epoch FROM now() - pg_last_xact_replay_timestamp())),0) AS seconds;"
 receive | replay | seconds 
---------+--------+---------
         |        |       0

psql -h ${PGENDPOINT2} -p 5432 -U ${PGUSERNAME} sample \
-c "SELECT pg_last_xlog_receive_location() AS receive, pg_last_xlog_replay_location() AS replay , COALESCE(ROUND(EXTRACT(epoch FROM now() - pg_last_xact_replay_timestamp())),0) AS seconds;"
 receive | replay | seconds 
---------+--------+---------
         |        |       0
Собственно говоря тут все, переключаем приложение на второй мастер, первый мастер тушим, работаем с новой версией PostgreSQL.

Комментариев нет: