Slonyを使ったPostgreSQLのアップグレード

このガイドでは、PostgreSQLデータベースを何時間もダウンタイムなしで最新版にアップグレードするための手順を説明します。 このガイドでは、2つのデータベースサーバがあることを想定しています。1つは古いバージョンのPostgreSQL(例えば9.2.18)が稼働しているデータベースサーバで、もう1つは新しいバージョン(例えば9.6.0)が稼働しているサーバです。

このプロセスでは、“Slony “と呼ばれるPostgreSQLレプリケーションツールを使用します。 Slonyは異なるバージョンのPostgreSQL間でレプリケーションを行うことができるため、最小限のダウンタイムでクラスターをアップグレードすることができます。

gitlab-psqlこのユーザは、PostgreSQLの様々なOSプロセスを実行するために使用されるユーザでなければなりません。 もし、別のユーザ(例えば、postgres)を使用している場合は、gitlab-psql をそのユーザ名に置き換えてください。 このガイドでは、データベースの名前をgitlabhq_productionと仮定しています。 もし、別のデータベース名を使用している場合は、それに応じて変更してください。

データベースのダンプ

Slonyはデータのみを複製し、スキーマの変更は行いません。 そのため、すべてのデータベースが同じデータベース構造を持つようにしなければなりません。

そのために、現在のデータベースのダンプを生成します。 このダンプには構造のみが含まれ、データは含まれません。 このダンプを生成するには、アクティブなデータベースサーバー上で以下のコマンドを実行します:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -h /var/opt/gitlab/postgresql -p 5432 -U gitlab-psql -s -f /tmp/structure.sql gitlabhq_production

GitLab の Omnibus パッケージを使用していない場合は、pg_dump と PostgreSQL のインストールディレクトリへのパスをあなたの設定に合わせて調整する必要があるかもしれません。

構造ダンプが生成されたら、schema_migrations テーブルのダンプも生成する必要があります。このテーブルには主キーがないので、Slony では簡単に複製できません。このダンプを生成するには、アクティブなデータベースサーバーで次のコマンドを実行します:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -h /var/opt/gitlab/postgresql/ -p 5432 -U gitlab-psql -a -t schema_migrations -f /tmp/migrations.sql gitlabhq_production

最も簡単な方法は、これらのファイルをローカルシステムにダウンロードすることです:

scp your-user@production-database-host:/tmp/*.sql /tmp

これで、/tmp ローカルシステムの/tmpディレクトリにあるすべてのSQLファイルがコピーされます。 コピー後は、データベースサーバーからファイルを安全に削除できます。

Slonyのインストール

Slonyは長いダウンタイムを必要とせずにデータベースをアップグレードするために使われます。 Slonyはhttps://www.slony.info/からダウンロードできます。 オペレーティングシステムのパッケージマネージャを使ってPostgreSQLをインストールしている場合、そのパッケージマネージャを使ってSlonyをインストールすることもできます。

Slonyをソースからコンパイルする場合、以下のコマンドを使用する必要があります:

./configure --prefix=/path/to/installation/directory --with-perltools --with-pgconfigdir=/path/to/directory/containing/pg_config/bin
make
make install

Omnibusユーザーは以下のコマンドを使用できます:

./configure --prefix=/opt/gitlab/embedded --with-perltools --with-pgconfigdir=/opt/gitlab/embedded/bin
make
make install

これは、GitLab を/opt/gitlabにインストールしていることを前提としています。

Slonyが正しくインストールされているかテストするには、以下のコマンドを実行してください:

test -f /opt/gitlab/embedded/bin/slonik && echo 'Slony installed' || echo 'Slony not installed'
test -f /opt/gitlab/embedded/bin/slonik_init_cluster && echo 'Slony Perl tools are available' || echo 'Slony Perl tools are not available'
/opt/gitlab/embedded/bin/slonik -v

これは、Slonyが/opt/gitlab/embeddedにインストールされたと仮定しています。 Slonyが正しくインストールされた場合、これらのコマンドの出力は次のようになります (slonikのバージョンは異なる可能性があります):

Slony installed
Slony Perl tools are available
slonik version 2.2.5

スロニーユーザー

次に、Slonyがデータベースを複製するために使用できるPostgreSQLユーザーを設定しなければなりません。 これを行うには、psql 、スーパーユーザーアカウントを使用して本番データベースにログインします。完了したら、次のSQLクエリを実行します:

CREATE ROLE slony WITH SUPERUSER LOGIN REPLICATION ENCRYPTED PASSWORD 'password string here';
ALTER ROLE slony SET statement_timeout TO 0;

パスワード文字列 “をユーザーの実際のパスワードに置き換えてください。 パスワードは必須です。 このユーザーは、新旧_両方の_データベース・サーバーで同じパスワードを使用して作成する必要があります。

ユーザーを作成したら、後で必要になるので、パスワードをメモしておいてください。

スローニーの設定

これでようやくSlonyの設定を始めることができます。 Slonyはほとんどの作業に設定ファイルを使うので、これを設定する必要があります。 この設定ファイルはログファイルをどこに置くか、Slonyがデータベースに接続する方法などを指定します。

まず、必要なディレクトリを作成し、正しい権限を設定する必要があります。 そのために、新旧両方のデータベースサーバーで以下のコマンドを実行します:

sudo mkdir -p /var/log/gitlab/slony /var/run/slony1 /var/opt/gitlab/postgresql/slony
sudo chown gitlab-psql:root /var/log/gitlab/slony /var/run/slony1 /var/opt/gitlab/postgresql/slony

ここで、gitlab-psql はPostgreSQLデータベースプロセスの実行に使用されるユーザです。別のユーザを使用している場合は、これをそのユーザ名に置き換えてください。

ディレクトリが整ったので、次はコンフィギュレーション・ファイルを作成します。 これには以下のテンプレートを使用します:

if ($ENV{"SLONYNODES"}) {
    require $ENV{"SLONYNODES"};
} else {
    $CLUSTER_NAME = 'slony_replication';
    $LOGDIR = '/var/log/gitlab/slony';
    $MASTERNODE = 1;
    $DEBUGLEVEL = 2;

    add_node(host => 'OLD_HOST', dbname => 'gitlabhq_production', port =>5432,
        user=>'slony', password=>'SLONY_PASSWORD', node=>1);

    add_node(host => 'NEW_HOST', dbname => 'gitlabhq_production', port =>5432,
        user=>'slony', password=>'SLONY_PASSWORD', node=>2, parent=>1 );
}

$SLONY_SETS = {
    "set1" => {
        "set_id"       => 1,
        "table_id"     => 1,
        "sequence_id"  => 1,
        "pkeyedtables" => [
            TABLES
        ],
    },
};

if ($ENV{"SLONYSET"}) {
    require $ENV{"SLONYSET"};
}

# Please do not add or change anything below this point.
1;

この設定ファイルを使用する前に、いくつかのプレースホルダーを置き換える必要があります。 以下のプレースホルダーを置き換える必要があります:

  • OLD_HOST旧データベースサーバーのアドレス。
  • NEW_HOST新しいデータベースサーバーのアドレス。
  • SLONY_PASSWORD先ほど作成したSlonyユーザーのパスワード。
  • TABLES複製するテーブル。

複製するテーブルのリストは、古いPostgreSQLデータベース上で以下のコマンドを実行することで生成できます:

sudo gitlab-psql gitlabhq_production -c "select concat('\"', schemaname, '.', tablename, '\",') from pg_catalog.pg_tables where schemaname = 'public' and tableowner = 'gitlab' and tablename != 'schema_migrations' order by tablename asc;" -t

Omnibusを使用していない場合は、gitlab-psqlpsql 実行ファイルへの適切なパスに置き換えてください。

上記のコマンドは、上記のコンフィギュレーション・ファイルに直接コピー・ペーストできる形式で、テーブルのリストを出力します。TABLES をこの出力に_置き換える_ようにしてください:

"pkeyedtables" => [
    "public.abuse_reports",
    "public.appearances",
    "public.application_settings",
    ... more rows here ...
]

設定ファイルを生成したら、それを古いデータベースと新しいデータベースの両方にインストールする必要があります。そのためには、/var/opt/gitlab/postgresql/slony/slon_tools.conf (先ほどディレクトリを作成しました)にファイルを置きます。

これで設定ファイルが整いましたので、_いよいよ_データベースの複製を開始することができます。 まず、新しいデータベースにスキーマを設定する必要があります。そのためには、先ほど生成したSQLファイルが新しいサーバの/tmpディレクトリにあることを確認してください。これらのファイルが整ったら、このサーバでpsqlセッションを開始します:

sudo gitlab-psql gitlabhq_production

次のコマンドを実行してください:

\i /tmp/structure.sql
\i /tmp/migrations.sql

構造が適切かどうかを確認するために、セッションを終了し、再度起動してから、\dを実行してください。 すべてがうまくいっていれば、以下のような出力が表示されるはずです:

                               List of relations
 Schema |                    Name                     |   Type   |    Owner
--------+---------------------------------------------+----------+-------------
 public | abuse_reports                               | table    | gitlab
 public | abuse_reports_id_seq                        | sequence | gitlab
 public | appearances                                 | table    | gitlab
 public | appearances_id_seq                          | sequence | gitlab
 public | application_settings                        | table    | gitlab
 public | application_settings_id_seq                 | sequence | gitlab
 public | approvals                                   | table    | gitlab
 ... more rows here ...

これで、Slony がレプリケーション処理に使用するテーブルなどを初期化することができます。 そのために、古いデータベースで以下を実行します:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_init_cluster --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik

すべてがうまくいけば、次のような結果になるでしょう:

<stdin>:10: Set up replication nodes
<stdin>:13: Next: configure paths for each node/origin
<stdin>:16: Replication nodes prepared
<stdin>:17: Please start a slon replication daemon for each node

次に、各サーバ上でレプリケーション・ノードを起動する必要があります。 そのためには、古いデータベース上で以下を実行します:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_start 1 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf

すべてがうまくいけば、次のような出力が得られます:

Invoke slon for node 1 - /opt/gitlab/embedded/bin/slon -p /var/run/slony1/slony_replication_node1.pid -s 1000 -d2  slony_replication 'host=192.168.0.7 dbname=gitlabhq_production user=slony port=5432 password=hieng8ezohHuCeiqu0leeghai4aeyahp' > /var/log/gitlab/slony/node1/gitlabhq_production-2016-10-06.log 2>&1 &
Slon successfully started for cluster slony_replication, node node1
PID [26740]
Start the watchdog process as well...

次に、_新しい_データベース・サーバー上で以下のコマンドを実行する必要があります:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_start 2 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf

うまくいけば、同じような出力が得られます。

次に、新しいデータベースサーバーに何をレプリケートすべきかを指示する必要があります。 これは、_新しい_データベースサーバー上で以下のコマンドを実行することで行うことができます:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_create_set 1 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik

すると、以下のような出力が得られるはずです:

<stdin>:11: Subscription set 1 (set1) created
<stdin>:12: Adding tables to the subscription set
<stdin>:16: Add primary keyed table public.abuse_reports
<stdin>:20: Add primary keyed table public.appearances
<stdin>:24: Add primary keyed table public.application_settings
... more rows here ...
<stdin>:327: Adding sequences to the subscription set
<stdin>:328: All tables added

最後に、_新しい_データベースサーバー上で以下を実行することで、レプリケーションプロセスを開始することができます:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slonik_subscribe_set 1 2 --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf | /opt/gitlab/embedded/bin/slonik

この結果、次のような出力が得られるはずです:

<stdin>:6: Subscribed nodes to set 1

この時点で、新しいデータベースサーバーは古いデータベースサーバーのデータの複製を開始します。 このプロセスには、数分から数時間、数日かかることもあります。 残念ながら、Slony自身は2つのデータベースの同期を知る方法を提供していません。 進捗の見積もりを得るには、次のシェルスクリプトを使用できます:

#!/usr/bin/env bash

set -e

user='slony'
pass='SLONY_PASSWORD'

function main {
    while :
    do
        local source
        local target

        source=$(PGUSER="${user}" PGPASSWORD="${pass}" /opt/gitlab/embedded/bin/psql -h OLD_HOST gitlabhq_production -c "select pg_size_pretty(pg_database_size('gitlabhq_production'));" -t -A)
        target=$(PGUSER="${user}" PGPASSWORD="${pass}" /opt/gitlab/embedded/bin/psql -h NEW_HOST gitlabhq_production -c "select pg_size_pretty(pg_database_size('gitlabhq_production'));" -t -A)

        echo "$(date): ${target} of ${source}" >> progress.log
        echo "$(date): ${target} of ${source}"

        sleep 60
    done
}

main

このスクリプトは1分ごとに新旧のデータベースのサイズを比較し、結果をSTDOUTに出力するとともにファイルに記録します。SLONY_PASSWORDOLD_HOSTNEW_HOST を正しい値に置き換えてください。

レプリケーションの停止

ある時点で二つのデータベースが同期します。 これが完了したら、数分間のダウンタイムを計画する必要があります。 この小さなダウンタイムは、レプリケーションプロセスを停止したり、両方のデータベースからSlonyのデータを削除したり、GitLabを再起動して新しいデータベースを使えるようにしたりするために使われます。

まず、GitLab をすべて停止させましょう。 Omnibus を使っている場合は、GitLab サーバー上で以下を実行します:

sudo gitlab-ctl stop unicorn
sudo gitlab-ctl stop sidekiq
sudo gitlab-ctl stop mailroom

PostgreSQLを使用している他のプロセスがあれば、それらも停止してください。

すべてが停止したら、構成設定やDNSレコードなどを更新して、新しいデータベースを指すようにします。

設定が終わったら、レプリケーション・プロセスを停止する必要があります。 この時点でデータベースに新しいデータを書き込まないことが重要です。

レプリケーションを停止するには、両方のデータベース・サーバで以下を実行します:

sudo -u gitlab-psql /opt/gitlab/embedded/bin/slon_kill --conf /var/opt/gitlab/postgresql/slony/slon_tools.conf

これは、コマンドが実行されたホスト上のすべてのSlonyプロセスを停止します。

シーケンスのリセット

上記のセットアップではデータベース・シーケンスは複製されないため、ターゲット・データベースで手動でリセットする必要があります。 これには以下のスクリプトを使用できます:

#!/usr/bin/env bash
set -e

function main {
    local fix_sequences
    local fix_owners

    fix_sequences='/tmp/fix_sequences.sql'
    fix_owners='/tmp/fix_owners.sql'

    # The SQL queries were taken from
    # https://wiki.postgresql.org/wiki/Fixing_Sequences
    sudo gitlab-psql gitlabhq_production -t -c "
    SELECT 'ALTER SEQUENCE '|| quote_ident(MIN(schema_name)) ||'.'|| quote_ident(MIN(seq_name))
           ||' OWNED BY '|| quote_ident(MIN(TABLE_NAME)) ||'.'|| quote_ident(MIN(column_name)) ||';'
    FROM (
        SELECT
            n.nspname AS schema_name,
            c.relname AS TABLE_NAME,
            a.attname AS column_name,
            SUBSTRING(d.adsrc FROM E'^nextval\\(''([^'']*)''(?:::text|::regclass)?\\)') AS seq_name
        FROM pg_class c
        JOIN pg_attribute a ON (c.oid=a.attrelid)
        JOIN pg_attrdef d ON (a.attrelid=d.adrelid AND a.attnum=d.adnum)
        JOIN pg_namespace n ON (c.relnamespace=n.oid)
        WHERE has_schema_privilege(n.oid,'USAGE')
          AND n.nspname NOT LIKE 'pg!_%' escape '!'
          AND has_table_privilege(c.oid,'SELECT')
          AND (NOT a.attisdropped)
          AND d.adsrc ~ '^nextval'
    ) seq
    GROUP BY seq_name HAVING COUNT(*)=1;
    " > "${fix_owners}"

    sudo gitlab-psql gitlabhq_production -t -c "
    SELECT 'SELECT SETVAL(' ||
           quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
           ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
           quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
    FROM pg_class AS S,
         pg_depend AS D,
         pg_class AS T,
         pg_attribute AS C,
         pg_tables AS PGT
    WHERE S.relkind = 'S'
        AND S.oid = D.objid
        AND D.refobjid = T.oid
        AND D.refobjid = C.attrelid
        AND D.refobjsubid = C.attnum
        AND T.relname = PGT.tablename
    ORDER BY S.relname;
    " > "${fix_sequences}"

    sudo gitlab-psql gitlabhq_production -f "${fix_owners}"
    sudo gitlab-psql gitlabhq_production -f "${fix_sequences}"

    rm "${fix_owners}" "${fix_sequences}"
}

main

このスクリプトを_ターゲット_サーバーにアップロードし、以下のように実行します:

bash path/to/the/script/above.sh

これにより、配列の所有権が修正され、id 列の次の値がリセットされます。

スローニーの削除

次に、Slony関連のデータをすべて削除する必要があります。 そのためには、_ターゲット_サーバー上で次のコマンドを実行します:

sudo gitlab-psql gitlabhq_production -c "DROP SCHEMA _slony_replication CASCADE;"

これが完了したら、Slony 関連のファイル (ログディレクトリなど) を安全に削除し、必要なら Slony をアンインストールします。 この時点で GitLab インスタンスを再び起動し、すべてがうまくいっていれば新しいデータベースサーバーを使えるようになっているはずです。