RDS for PostgreSQLにLondisteを用いてニアゼロタウンタイムで移行する

Londiste AWS
AWS PostgreSQL RDS データベース

本記事では、オンプレミス、あるいは、EC2のPostgreSQLをRDS for PostgreSQLにLondisteを用いて移行する方法を記載します。Londisteを使った移行は、DMSやネイティブのレプリケーションを利用できないPostgreSQLのニアゼロタウンタイム移行に便利です。

スポンサーリンク

Londisteとは

Londisteとは、Pythonで書かれたトリガーベースのPostgreSQLレプリケーションツールです。PostgreSQLはネイティブでストリーミングレプリケーションやロジカルレプリケーションをサポートしていますし、AWSへの移行であればDMSが利用できますが、次の表のように、移行方式によっては利用できません。

PostgreSQLの移行方針によるツール対応表

移行元移行先ストリーミングレプリケーションロジカルレプリケーションDMSLondiste
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
PATH=$PATH:/opt/skytools-2.1.13/bin
export PATH
PYTHONPATH=/opt/skytools-2.1.13/lib/python2.6/site-packages
export PYTHONPATH
LD_LIBRARY_PATH=/usr/pgsql-9.1/lib:/usr/lib
export LD_LIBRARY_PATH
_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_tabletest_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を利用できない際に、レプリケーションを行いたい時にも利用できるツールです。

デメリットもありますが、環境制約時の代替候補として頭の片隅に置いておくと、もしかしたら役に立つ場面も出てくるかもしれません。

コメント

タイトルとURLをコピーしました