macOS/Homebrewで複数バージョンのPostgreSQLを共存させる方法(2018年版)

複数の PostgreSQL クラスタ(インスタンス)を起動するで書いたように Ubuntu 環境では、普通に apt-get でインストールするだけで複数バージョンのPostgresqlを共存させる状況が整います。

しかし、macOS/Homebrew 環境ではちょっとした準備作業が必要となります。以下、その概略を説明します。

【重要な注意】 以下の手順では、いずれのコマンドも sudo を付けずに実行してください。

【参考資料】 Macで複数のバージョンのPostgresqlを共存させる -- itmammoth 氏による 2016 年 3 月の記事。

インストール済みの PostgreSQL を無効化

まず、現在の環境に PostgreSQL がインストールされているかどうかを確認します。

$ brew list search postgresql

リストの中にある postgresql の右に ✔ アイコンが付いていればインストール済みです。次のコマンドで無効化します。

$ brew unlink postgresql

リポジトリの追加

リポジトリ petere/homebrew-postgresql を追加します。

$ brew tap petere/homebrew-postgresql

postgresql-common のインストール

$ brew install postgresql-common

PostgreSQL のインストール

インストール可能な PostgreSQL のバージョン番号は brew list search postgresql コマンドで調べられます。表示されたリストの中に petere/postgresql/postgresql@X という項目があれば、バージョン X の PostgreSQL がインストール可能です。

例として、バージョン 9.6 と 10 をインストールしてみましょう。

$ brew install postgresql-9.6
$ brew install postgresql-10

もう一度 brew list search postgresql コマンドを実行し、petere/postgresql/postgresql@9.6 および petere/postgresql/postgresql@10 の右側に ✔ アイコンが付いていることを確認してください。

クラスタの作成

ここから先は Ubuntu 環境の場合とほぼ同じです。ただし、データディレクトリ、設定ファイルの置き場所、データベース postgres の owner など、細かな違いがあります。繰り返しになりますが、すべてのコマンドを sudo なしで実行してください(重要)。

さて、PostgreSQL 用語の「クラスタ」とは、ひとつの PostgreSQL サーバーインスタンスによって管理される複数のデータベースの集合体を意味します。ひとつの macOS 上で複数個のクラスタを同時に稼働させることができます。

PostgreSQL 9.6 のクラスタ mainbackup を作成します。

$ pg_createcluster 9.6 main
$ pg_createcluster 9.6 backup

さらに、PostgreSQL 10 のクラスタ main を作成します。

$ pg_createcluster 10 main

合計で 3 個のクラスタが作られたことになります。クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます(一行が長いので一部省略しています)。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 down   kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

3番目の Port 列に着目してください。それぞれのクラスタに対応するサーバーインスタンスがどのポートを listen するかが書かれています。なお、5番目の Owner 列には、現在 macOS にログイン中のユーザー名が表示されます。

クラスタの起動

PostgreSQL 9.6 のクラスタ main を起動します。

$ pg_ctlcluster 9.6 main start

クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 online kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

1 行目の Satus 列の値が down から online に変化しています。

同様に、他の 2 個のクラスタも起動します。

$ pg_ctlcluster 9.6 backup start
$ pg_ctlcluster 10 main start

psql で接続確認

PostgreSQL 10 のクラスタ mainpsql で接続してみましょう。

$ psql --port=5435 postgres

--port オプションでポート番号を、接続先データベースとして postgres を指定しています。初期状態で存在する唯一のデータベースが postgres です。オーナーは pg_createcluster を実行したユーザーです。

次のように出力されれば、成功です。

psql (10.2 (Homebrew petere/postgresql))
Type "help" for help.

postgres=#

クラスタの停止

PostgreSQL 9.6 のクラスタ main を停止するには、次のコマンドを実行します。

$ pg_ctlcluster 9.6 main stop

ポート番号などの変更

クラスタの設定ファイルは /usr/local/etc/postgresql ディレクトリの下にあります。PostgreSQL 10 のクラスタ main の設定ファイルは、/usr/local/etc/postgresql/10/main ディレクトリにあります。

ポート番号を変更したい場合は、エディタで postgresql.conf を開き、port = で始まる行を探して値を書き換えてください。設定を反映させるには、クラスタの再起動が必要です。

PostgreSQL: template1 のエンコーディングを UTF-8 に変更する

PostgreSQL におけるデータベース作成とは、既存のデータベースの複製を作ることです。デフォルトでは template1 という名前のデータベースが複製元となります。

さて、PostgreSQLのインストール直後に template1エンコーディングSQL_ASCII になっていることがあります。

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | SQL_ASCII | C           | C           | 

Web アプリケーションのバックエンドとして使う場合、エンコーディングとして UTF-8 を採用することが多いので、このままだと不便です。

psql のコンソールで次のステートメントを発行すれば、template1エンコーディングUTF-8 に(ついでに、ロケールをに ja_JP に)変更できます。

UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
DROP DATABASE template1;
CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UTF8'
  LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8';
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';

結果は、この通り:

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | UTF8      | ja_JP.UTF-8 | ja_JP.UTF-8 | 

Ubuntu: lsyncd によるディレクトリのリアルタイム同期(追記あり)

目標

  • Ubuntu 16.04 Server がインストールされた 2 台のサーバー host1host2 がある。
  • host1host2 の間で /home/foo/share ディレクトリの内容を常に同期する。
  • 一般ユーザー(root 以外のユーザー)で rsync コマンドを実行する。

準備作業

RSA鍵ペアの作成

foo ユーザーで host1host2ssh でログインし、それぞれ次のコマンドを実行する。

$ ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa.rsync

RSA鍵公開鍵を設置

  • host1~/.ssh/id_rsa.rsync.pub の中身を host2~/.ssh/authorized_keys の末尾に貼り付ける。
  • host2~/.ssh/id_rsa.rsync.pub の中身を host1~/.ssh/authorized_keys の末尾に貼り付ける。

注意: ~/.sshパーミッションが 700 であり、~/.ssh/authorized_keysパーミッションが 400 または 600 であることを確認すること。

接続確認

$ ssh -i ~/.ssh/id_rsa.rsync host2 # host1 から
$ ssh -i ~/.ssh/id_rsa.rsync host1 # host2 から

rsync コマンドの動作確認

host1 で次のコマンドを群を実行する。

$ mkdir ~/share
$ echo TEST ~/share/test.txt
$ rsync -av -e ssh ~/share/ foo@host2:/home/foo/share

~/share/ の末尾のスラッシュ(/)は必須である。

host2/home/foo/share ディレクトリに test.txt というファイルができていればOK。

lsyncd のセットアップ

ソフトウェアのインストール

$ sudo apt-get install -y lsyncd

設定ファイルの作成

/etc/lsyncd/lsyncd.conf.lua を作成。

settings {
        logfile    = "/var/log/lsyncd.log",
        statusFile = "/tmp/lsyncd.stat",
        delay        = 1
}

sync {
        default.rsync,
        source="/home/foo/share/",
        target="foo@host2:/home/foo/share/",
        delete=false,
        rsync = {
          rsh = "/usr/bin/ssh -i /home/foo/.ssh/id_rsa.rsync -o UserKnownHostsFile=/home/foo/.ssh/known_hosts"
        }
}

注意事項:

  • delay の値(デフォルト: 15)は、ファイル更新イベント発生から rsync 実行までの遅延時間(秒単位)である。リアルタイム性を追求するなら値は小さいほうがよいが、ファイル更新イベントが頻発するサーバーでこの値を小さくし過ぎると、かえって更新に時間がかかるかもしれない。
  • source の末尾のスラッシュ(/)は必須である。
  • host2 側の lsyncd.conf.lua では、target の値を "foo@host1:/home/foo/share/" とする。
  • delete=false については本稿末尾の「備考」を参照。
  • -o UserKnownHostsFile-... の記述を省略すると、Host Key Verification Failed というエラーが発生する。

lsyncd の起動

lsyncd を起動。

$ sudo systemctl restart lsyncd

動作確認

  • host1/home/foo/shared ディレクトリに新しいファイルを追加し、host2/home/foo/shared ディレクトリにそのファイルが転送されることを確認。
  • host2/home/foo/shared ディレクトリに新しいファイルを追加し、host1/home/foo/shared ディレクトリにそのファイルが転送されることを確認。

うまく行かない場合は、/var/log/lsyncd.log を見て調べる。

備考

本稿で使用した lsyncd の設定では delete=false オプションを使用しているため、host1 または host2 でファイルが削除されても、もう一方の側でそのファイルは削除されない。

ファイルが削除される可能性のある環境で lsyncd による「双方向同期」を使用する場合の注意点については、lsyncdで双方向同期するなら、delete='running' がいいを参照せよ。


追記

「備考」で触れたが、lsyncd で双方向同期を行う場合、ファイル削除の扱いが難しい。

host1 がダウンしている間に host2 に追加されたファイル X があるとき、host1 の再起動時に X が削除される可能性がある、ということだ。

この問題を回避するため、参照したブログ記事では「lsyncd の起動時にファイルの同期をしない(delete="running")」という設定を勧めている。

しかし、そもそもなぜファイルを削除するのかを考えると、この回避策でよいだろうか。

host1 がダウンしている間に host2 でファイル Y が削除されたとする。「lsyncd の起動時にファイルの同期をしない」設定の場合、host1 の再起動後も host1 上でファイル Y が残り続けることになる。

おそらく、ファイル Y を削除する目的は、ディスクスペースの節約かファイル Y の内容を残したくない(内容がまずい)かのどちらかだろう。

つまり、「lsyncd の起動時にファイルの同期をしない」設定では、この目的が達成されない。

とすれば、正しい回避策は、「lsyncd の起動時にファイルの同期をするが、ファイルの削除はしない(delete=false)」設定にしておいて、ファイル Y を削除したいときにはファイル Y を空にする(ファイルサイズを 0 にする)運用をすることではないだろうか。これならば、host1 が復活したときに、host1 上のファイル Y が消える。

ちなみに、サイズが 0 のファイルも一定のディスクスペースを占めるし、あまりに数が多いと「inode枯渇」という別の問題を引き起こす。

だから、ファイル削除が頻繁に行われる環境では、サイズが 0 のファイルを削除する仕組みを別途考えたほうがいいだろう。

例えば、毎朝 4:01 に host1host2 の両方が動いていることを確かめた上で、4:00 以前に書き換えられたサイズ 0 のファイルをすべて削除するような処理を cron にやらせればいい。

なお、この「追記」の内容は筆者が頭の中で考えたことに過ぎず、実績があるわけではない。参考にされる方は、この点に留意していただきたい。

Ubuntu: init system を systemd から upstart に変える

Ubuntu 16.04 がインストールされている私の職場PCがハードディスクの故障で動かなくなり、修復のためにいろいろやっている過程で、

  • ブート時のメニューで「Ubuntu」を選んでも起動しない。
  • 「Advanced options for Ubuntu」を選んでから「upstart」という文字を含むエントリーを選ぶと起動する。

という現象に遭遇した。init system を systemd から upstart に変えればよさそうだ。

GRUBのメニューを自分で書き換えればいいのかなと思ったが、正しい手順は以下の通りであった。

sudo apt-get install upstart-sysv
sudo update-initramfs -u

参考資料:

PostgreSQL: キャッシュした計画は結果型を変更してはなりません

PostgreSQL を用いた Rails アプリケーション開発で

キャッシュした計画は結果型を変更してはなりません

あるいは

cached plan must not change result type

というエラーメッセージに遭遇することがある。

解決法は簡単で、Rails サーバーを再起動すればよい。

なぜこのエラーが出るのか知りたい方は、Qiitaの次の記事を読むとよい。

cached plan must not change result type なんてエラーが出たら

簡単に言えば、Rails サーバーを起動したままマイグレーションを実行すると、この現象が出る。

Phusion Passenger: permission denied で ruby を実行できない問題

問題

Rails あるいは Sinatra を Phusion Passenger で動かしている状況で、permission denied により ruby を実行できない。

解決策 (1)

Rails なら config/environment.rbSinatra なら config.ru の持ち主が ruby を実行できるユーザーかどうかを確認し、そうでなければ直す。

デフォルトで Phusion Passengerはこのユーザーとして起動する。ただし root ユーザーが持ち主の場合は nobody ユーザーで動く。

解決策(2)

ps auwx | grep Passenger で Phusion Passenger の実行ユーザーを調べて、そのユーザーが ruby を実行できるようにする。

例えば、そのユーザーが apache で、ruby/home/app/.rbenv ディレクトリの下にあり、/home/app の user:group が app:app なら、sudo gpasswd -a apache appapacheapp グループに追加する。また、chmod g+rx /home/app も実行する。

解決策 (3)

PhusionPassenger の設定ファイルで PassengerUser を設定する。

Rails: send_data/send_file でデータの中身がテキストとして表示される問題

問題

次のようなRailsのアクションにより files/example.pdf の中身をユーザーにダウンロードさせたい。

class FilesController < ApplicationController
  def show
    filename = 'example.pdf'
    path = Rails.root.join('files', filename)
    send_file(path, type: 'application/pdf', filename: filename)
  end
end

しかし、ブラウザでこのアクションにアクセスするとファイルの中身がテキストとして表示される。

解決法

<a> タグに data-turbolinks="false" 属性を付ける。例えば、

<%= link_to "example.pdf", file_path %>

<%= link_to "example.pdf", file_path, 
  data: { turbolinks: false } %>

に変える。

参考資料