本記事では、オンプレミス、あるいは、EC2のPostgreSQLをRDS for PostgreSQLにLondisteを用いて移行する方法を記載します。Londisteを使った移行は、DMSやネイティブのレプリケーションを利用できないPostgreSQLのニアゼロタウンタイム移行に便利です。
Londisteとは
Londisteとは、Pythonで書かれたトリガーベースのPostgreSQLレプリケーションツールです。PostgreSQLはネイティブでストリーミングレプリケーションやロジカルレプリケーションをサポートしていますし、AWSへの移行であればDMSが利用できますが、次の表のように、移行方式によっては利用できません。
PostgreSQLの移行方針によるツール対応表
移行元 | 移行先 | ストリーミングレプリケーション | ロジカルレプリケーション | DMS | Londiste |
---|---|---|---|---|---|
PostgreSQL 9.4未満 | RDS | ×(※1) | ×(※2) | ×(※3) | 〇 |
PostgreSQL 9.4未満 | EC2(メジャーバージョン同じ) | 〇 | ×(※2) | ×(※3) | 〇 |
PostgreSQL 9.4未満 | EC2(メジャーバージョン違い) | ×(※4) | ×(※2) | ×(※3) | 〇 |
PostgreSQL 9.4以上10未満 | RDS | ×(※1) | ×(※2) | 〇 | 〇 |
PostgreSQL 9.4以上10未満 | EC2(メジャーバージョン同じ) | 〇 | ×(※2) | 〇 | 〇 |
PostgreSQL 9.4以上10未満 | EC2(メジャーバージョン違い) | ×(※4) | ×(※2) | 〇 | 〇 |
PostgreSQL 10以上 | RDS | ×(※1) | 〇 | 〇 | 〇 |
PostgreSQL 10以上 | EC2(メジャーバージョン同じ) | 〇 | 〇 | 〇 | 〇 |
PostgreSQL 10以上 | EC2(メジャーバージョン違い) | ×(※4) | 〇 | 〇 | 〇 |
※1: 執筆時点でRDSはストリーミングレプリケーションに対応していません。
※2: ロジカルレプリケーションは10以降の機能です。
※3: 執筆時点でDMSは9.4未満に対応していません。
※4: ストリーミングレプリケーションは同メジャーバージョンでの動作のみ保証しています。
こうして見るとLondisteが万能ツールに見えますが、次のような特徴がありますため、基本的にネイティブレプリケーションもDMSも利用できない場合にのみ利用を検討する方が良いと思われます。
- 移行元先にレプリケーション用のテーブルやトリガが追加される
- 移行元に新規ツールを導入する必要がある
- DDL文はレプリケーションされない
参考
検証前提環境
導入検証は以下のような環境で行っています。
- 移行元OS
- AMI: CentOS 7 (x86_64) – with Updates HVM
- SELinux無効
- yum update実施
- 関連追加パッケージ
- postgresql 9.2.24-1.el7_5
- postgresql-server 9.2.24-1.el7_5
- postgresql-devel 9.2.24-1.el7_5
- postgresql-libs 9.2.24-1.el7_5
- gcc 4.8.5-39.el7
- python-devel 2.7.5-86.el7
- 検証データベース
- DB: testdb
- TABLE: test_table
- SEQUENCE: test_sequence
- 移行先RDS
- RDS for PostgreSQL 9.6.15
testdb=> create table test_table (col1 int, col2 int);
CREATE TABLE
testdb=> alter table test_table add primary key (col1);
ALTER TABLE
testdb=> create sequence test_sequence;
CREATE SEQUENCE
ツールの導入
移行元のサーバにツールを導入します。移行先のサーバにツールをインストールする必要はありませんが、移行先データベースにレプリケーション用のオブジェクトを作成する必要はあります。
Psycopg2の導入
Psycopg2を導入します。Londisteが内部で利用するため必要です。全てroot
ユーザーにて実施しています。
wget http://initd.org/psycopg/tarballs/PSYCOPG-2-7/psycopg2-2.7.1.tar.gz
tar xvf psycopg2-2.7.1.tar.gz
cd psycopg2-2.7.1
python setup.py install
Skytools2(Londiste)の導入
LondisteはSkytools2の一部です。最新版はSkytools3ですが、このバージョンはRDSに利用することができません。内部的にPostgreSQLのデータベースにC言語関数を作成する実装なのですが、現時点でRDSにはC言語関数を作成することができないためです。
また、残念ながらCentOS用のパッケージが存在していないため、make
でインストールしています。全てroot
ユーザーにて実施しています。
wget https://apt.postgresql.org/pub/projects/pgFoundry/skytools/skytools/2.1.13/skytools-2.1.13.tar.gz
tar xvf skytools-2.1.13.tar.gz
cd skytools-2.1.13
./configure --prefix=/opt/skytools-2.1.13
make
make install
python setup.py build
python setup.py install
cp -p ~postgres/.bash_profile ~postgres/.bash_profile.bk
cat <<'_eof_' >>~postgres/.bash_profile
export PATH=$PATH:/opt/skytools-2.1.13/bin
export PYTHONPATH=/opt/skytools-2.1.13/lib/python2.6/site-packages
export LD_LIBRARY_PATH=/usr/pgsql-9.1/lib:/usr/lib
_eof_
mkdir /opt/skytools-2.1.13/{cfg,log,pid}
chown -R postgres:postgres /opt/skytools-2.1.13
Londisteの導入
thickerの導入
この操作では、レプリケーションで利用するキューの情報を保持するために必要なテーブルを作成するために、主としてpgq.sql
が実行されているように見えます。
設定ファイルに記載する情報を用いて移行元データベースに接続できる必要がありますので、pg_hba.conf
で許可されている接続方法を記載します。今回、ローカルの認証をtrust
とした状態で、ローカル接続しています。
なお、iniファイルのサンプルはskytools-2.1.13/share/doc/skytools/conf/pgqadm.ini
にも記載されています。操作は全てpostgres
ユーザにて実施しています。
cat <<'_cfg_' >/opt/skytools-2.1.13/cfg/thicker.ini
[pgqadm]
# should be globally unique
job_name = testdb_job
db = dbname=testdb host=127.0.0.1 user=postgres
# how often to run maintenance [minutes]
maint_delay_min = 5
# how often to check for activity [secs]
loop_delay = 0.1
logfile = /opt/skytools-2.1.13/log/%(job_name)s.log
pidfile = /opt/skytools-2.1.13/pid/%(job_name)s.pid
use_skylog = 0
_cfg_
pgqadm.py /opt/skytools-2.1.13/cfg/thicker.ini install
pgqadm.py /opt/skytools-2.1.13/cfg/thicker.ini ticker -d
参考: pgq.sqlにより作成されるテーブル
pgq.queue – Queue configuration
pgq.consumer – Consumer names
pgq.subscription – Consumer registrations
pgq.tick – Per-queue snapshots (ticks)
pgq.event_* – Data tables
pgq.retry_queue – Events to be retried later
pgq.failed_queue – Events whose processing failed
pgq.sql
レプリケーションの設定
この操作では、実際のレプリケーションの状態や進捗を管理するテーブルを作成するために、主としてlondiste.sql
が内部的に実行されているように見えます。
設定ファイルには、移行元(provider_db)と移行先(subscriber_db)、両方のデータベースの接続情報を記載します。今回、移行先はRDSのエンドポイントを直接指定しています。
なお、iniファイルのサンプルはskytools-2.1.13/share/doc/skytools/conf/londiste.ini
にも記載されています。操作は全てpostgres
ユーザにて実施しています。
cat <<'_cfg_' >/opt/skytools-2.1.13/cfg/testdb-sync.ini
[londiste]
# should be unique
job_name = testdb_sync
# source queue location
provider_db = dbname=testdb host=127.0.0.1 user=postgres
# target database - it's preferable to run "londiste replay"
# on same machine and use unix-socket or localhost to connect
subscriber_db = dbname=testdb host=testdb.XXXXXXXXXX.ap-northeast-1.rds.amazonaws.com user=postgres password=XXXXXXXXXX
# source queue name
pgq_queue_name = testdb-queue
logfile = /opt/skytools-2.1.13/log/%(job_name)s.log
pidfile = /opt/skytools-2.1.13/pid/%(job_name)s.pid
# how often to poll event from provider
#loop_delay = 1
# max locking time on provider (in seconds, float)
#lock_timeout = 10.0
_cfg_
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini provider install
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini subscriber install
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini replay -d
レプリケーションオブジェクトの追加
ここまででレプリケーション対象のデータベースに必要なオブジェクトを作成できました。後は、実際にどのテーブルやシーケンスをレプリケーションするかを指定していきます。
なお、トリガベースのレプリケーションツールなので、「レプリケーション前の初期データはどうなるのか」という点が疑問になるかもしれませんが、最初に初期データも自動的に同期してくれます。
今回はtest_table
とtest_sequence
をレプリケーション対象としています。操作は全てpostgres
ユーザにて実施しています。
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini provider add test_table
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini subscriber add test_table
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini provider add-seq test_sequence
londiste.py /opt/skytools-2.1.13/cfg/testdb-sync.ini subscriber add-seq test_sequence
Londisteの動作確認
簡単にLondisteの動作を確認します。
Londisteによる初期データの同期
初期データ同期前
移行元にだけデータが存在する状況です。
# 移行元
testdb=# select * from test_table;
col1 | col2
------+-------
1 | 999
2 | 9999
3 | 99999
(3 rows)
testdb=# select last_value from test_sequence;
last_value
------------
4
(1 row)
# 移行先
testdb=> select * from test_table;
col1 | col2
------+------
(0 rows)
testdb=> select last_value from test_sequence;
last_value
------------
1
(1 row)
初期データ同期
(provider/subscriber) (add/add-seq)
を実行してレプリケーションオブジェクトを追加した状態です。ログからtest_table
の3行がコピーされたことが分かります。なお、シーケンスの同期完了ログは出力されないみたいです。これは残念です。
2020-01-11 03:40:15,706 2436 INFO storing state of public.test_table: copy:0 new_state:in-copy
2020-01-11 03:40:15,906 2436 INFO Table public.test_table status changed to 'in-copy'
2020-01-11 03:40:15,907 2436 INFO Launching copy process
2020-01-11 03:40:15,976 2439 INFO Resetting queue tracking on dst side
2020-01-11 03:40:18,979 2436 INFO {count: 0, ignored: 0, duration: 3.2890598774}
2020-01-11 03:41:16,092 2436 INFO {count: 0, ignored: 0, duration: 0.0113210678101}
2020-01-11 03:41:16,149 2439 INFO Starting full copy of public.test_table
2020-01-11 03:41:16,167 2439 INFO Dropping test_table_pkey
2020-01-11 03:41:16,168 2439 INFO public.test_table: truncating
2020-01-11 03:41:16,169 2439 INFO public.test_table: start copy
2020-01-11 03:41:16,173 2439 INFO public.test_table: copy finished: 21 bytes, 3 rows
初期データ同期後
初期データが同期されました。ここまで来たら、後は継続的なレプリケーション状態に入ります。
# 移行元
testdb=# select * from test_table;
col1 | col2
------+-------
1 | 999
2 | 9999
3 | 99999
(3 rows)
testdb=# select last_value from test_sequence;
last_value
------------
4
(1 row)
# 移行先
testdb=> select * from test_table;
col1 | col2
------+-------
1 | 999
2 | 9999
3 | 99999
(3 rows)
testdb=> select last_value from test_sequence;
last_value
------------
4
(1 row)
Londisteによるレプリケーション
移行元のデータ更新
初期データの同期が完了したので、データを更新してみました。
# 移行元
testdb=# insert into test_table values (nextval('test_sequence'), 99);
INSERT 0 1
testdb=# select nextval('test_sequence');
nextval
---------
5
(1 row)
移行元のデータレプリケーション
ログにもデータを1行コピーした旨の出力がされています。status changed to 'ok'
という出力があれば、そこでレプリケーションが終わったものと認識することができます。
2020-01-11 03:41:20,213 2439 INFO storing state of public.test_table: copy:1 new_state:wanna-sync:27
2020-01-11 03:41:20,215 2439 INFO Table public.test_table status changed to 'wanna-sync:27'
2020-01-11 03:41:23,136 2436 INFO storing state of public.test_table: copy:0 new_state:do-sync:28
2020-01-11 03:41:23,138 2436 INFO Table public.test_table status changed to 'do-sync:28'
2020-01-11 03:41:23,223 2439 INFO {count: 0, ignored: 0, duration: 3.01818203926}
2020-01-11 03:41:23,231 2439 INFO storing state of public.test_table: copy:1 new_state:ok
2020-01-11 03:41:23,232 2439 INFO Table public.test_table status changed to 'ok'
2020-01-11 03:41:23,235 2439 INFO Resetting queue tracking on dst side
2020-01-11 03:41:23,237 2439 INFO got SystemExit(0), exiting
移行先へのレプリケーション
移行元で挿入したデータがレプリケーションされていることが確認できました。実際の移行では、移行元の更新を停止後にマーカーとなるデータを挿入して、それが移行先に届くことで「移行が完了した」といったような確認ができると存じます。
testdb=> select * from test_table;
col1 | col2
------+-------
1 | 999
2 | 9999
3 | 99999
4 | 99
(4 rows)
testdb=> select last_value from test_sequence;
last_value
------------
5
(1 row)
詰まった点
パスワードに%を利用していると設定ファイルを読み取れない
基本的にツールさえインストールできてしまえば、後は順調に進むはずです。ただ、接続ユーザのパスワードに%
を利用していると、ConfigParser.py
の仕様で、iniファイルのパース時にエラーとなるようです。もし利用している際には、一時的にパスワードを変更したり、何らかの形でエスケープすると改善(こちらは未確認)するかと存じます。
エラー例
Traceback (most recent call last):
File "/bin/londiste.py", line 134, in <module>
script.start()
File "/bin/londiste.py", line 97, in start
self.script.start()
File "/usr/lib64/python2.7/site-packages/skytools/scripting.py", line 369, in start
run_single_process(self, self.go_daemon, self.pidfile)
File "/usr/lib64/python2.7/site-packages/skytools/scripting.py", line 98, in run_single_process
runnable.run()
File "/usr/lib64/python2.7/site-packages/londiste/setup.py", line 73, in run
self.admin()
File "/usr/lib64/python2.7/site-packages/londiste/setup.py", line 389, in admin
self.subscriber_install()
File "/usr/lib64/python2.7/site-packages/londiste/setup.py", line 579, in subscriber_install
dst_db = self.get_database('subscriber_db')
File "/usr/lib64/python2.7/site-packages/skytools/scripting.py", line 433, in get_database
connstr = self.cf.get(dbname)
File "/usr/lib64/python2.7/site-packages/skytools/config.py", line 54, in get
return self.cf.get(self.main_section, key)
File "/usr/lib64/python2.7/ConfigParser.py", line 623, in get
return self._interpolate(section, option, value, d)
File "/usr/lib64/python2.7/ConfigParser.py", line 691, in _interpolate
self._interpolate_some(option, L, rawval, section, vars, 1)
File "/usr/lib64/python2.7/ConfigParser.py", line 732, in _interpolate_some
"'%%' must be followed by '%%' or '(', found: %r" % (rest,))
ConfigParser.InterpolationSyntaxError: '%' must be followed by '%' or '(', found: '%xxxx'
最後に
今回はLondisteを移行に用いる記事となりましたが、ネイティブレプリケーションツールやDMSを利用できない際に、レプリケーションを行いたい時にも利用できるツールです。
デメリットもありますが、環境制約時の代替候補として頭の片隅に置いておくと、もしかしたら役に立つ場面も出てくるかもしれません。
コメント