Docker LibraryのPostgreSQLで日本語がうまくソートされない
私はRedmineをDocker Libraryのイメージで立てており、DBには同じくDocker LibraryのPostgreSQLを使用しています。以前はMuSQLを使ってたんですが、PostgreSQLに移行してからソートしても日本語がうまくソートされない問題が発生するようになりました。
調べたところ、照合順序(lc_collate)が適切でないとこの問題が発生するとのこと。以下のページを参考にさせていただきました。
lc_collateがCだと文字コードのバイナリ順、ja_JP.UTF-8だと辞書順でのソートになるようですね。ポスグレコンテナに入って確認してみます。
ポスグレコンテナへの入り方はこんな感じ。
|  |  | 
ポスグレコンテナに入ったら、postgresユーザにスイッチしてからpsqlを叩きます。
|  |  | 
lc_collate=en_US.utf8ですね。Docker LibraryのPostgreSQLのデフォルトロケールはen_US.utf8みたいです。
これをCかja_JP.UTF-8に変更する必要があるのですが、このポスグレのイメージにはja_JP.UTF-8が入っていません。ja_JP.UTF-8をインストールするのも面倒な話なので、特別な理由が無い限りCでいいんじゃないでしょうか。Linuxのsortコマンドとかも同じ文字コード順だったはずなので馴染みが無い訳ではないと思います。ja_JP.UTF-8がよければDockerfileを作ってどうのこうのする必要があります。
ではこのlc_collateをどうやって変更するかというと、これはinitdbの--localeオプションで指定するものとのこと。
しかし、initdbはデータベースクラスタを作成する際の初期化コマンドですので、何も考えずに実行すると綺麗さっぱりデータがなくなっちゃいます。普通にインストールしたポスグレであれば、上記のページにあるようにダンプを取ってリストアとなるんでしょうが、こちとらDockerコンテナですので、そうは問屋がおろさない。クラスタを落とした時点でコンテナが落ちちゃう気がするし。試してないですけど。
とりあえずinitdbで指定するのはわかったとして、このDockerコンテナに対してどうやってこのオプションを渡すのかという話なんですが、Docker Hubのページを確認すると、POSTGRES_INITDB_ARGSという環境変数でinitdbのオプションを渡すことができるようです。まぁ、initdb自体、まっさらな状態からコンテナを初めて作るときしか実行されないんでしょうけど。
つまり、docker-compose.ymlではこんな感じで指定することになります。
|  |  | 
新しくRedmineを立てるのならこれで終わりです。しかし、私は既に運用を開始してしまっていますので、ここからは既存のポスグレのロケールを変更するのに四苦八苦していきます。
で、やっぱりダンプとってリストアしようかとも考えたのですが、Dockerとして運用しているポスグレにはなーんかそぐわない気がして。通常だとまっさらなポスグレにはRedmineがマイグレーションしてテーブルを作るわけですし。
そこで「これってMySQLからポスグレに移行するのと一緒やん?」と思いつきました。ポスグレ移行の際は以下のRedmine.JPの記事を参考にしたのですが、今回も同じようにyaml_dbを使って移行できるんじゃないかと。これなら文字コード関係ないし、実績あるし。
Redmineで使うデータベースを変更する | Redmine.JP Blog
そんなわけでこのような手順でやっていきます。もっとうまい方法はあると思いますけど許してください。
- 兎にも角にもデータボリュームをバックアップ
- Redmineにyaml_dbを入れる
- yaml_dbでダンプを取得
- ポスグレコンテナを初期化
- lc_collate=Cクラスタの作成とRedmineのマイグレーション
- yaml_dbでリストア
ポスグレロケール変更大作戦
ではやっていきましょう。ちなみに、Redmineのバージョンは3.4、PostgreSQLは9.6を使っています。いずれもDocker LibraryのDockerイメージです。
兎にも角にもデータボリュームをバックアップ
です。後にyaml_dbでダンプを取得しますが、これはバックアップではありません。やっとかないと絶対後悔します。
また、ハマりどころを後述しますが、ここでゴミテーブルがある場合は予め削除しておくことをおすすめします。
Redmineにyaml_dbを入れる
Redmine上のGemfileの適当なところに書きます。
Redmineコンテナへの入り方はこうですね。
|  |  | 
Gemfileにはこんな行を追加したい。
|  |  | 
ここでRedmineコンテナにvimが入ってない問題が発生。わざわざvimを入れるのもアホくさいのでdocker cpコマンドでホストOSにGemfileをコピーして編集しました。
|  |  | 
Gemfileの編集ができたので、改めてbundle installを実行してyaml_dbをインストールします。(Redmineコンテナで実行)
|  |  | 
yaml_dbでダンプを取得
yaml_dbがインストールできたのでダンプを取ります。(Redmineコンテナで実行)
|  |  | 
ダンプファイルはdb/data.ymlに出力されます。この後Redmineコンテナも再作成を行うので、永続化しているディレクトリにコピーしときます。(Redmineコンテナで実行)
|  |  | 
ポスグレコンテナを初期化
ホストOSに戻って、コンテナを停止・削除します。これもハマりどころとして後述しますが、無理にポスグレコンテナだけ作り直そうとせずRedmineコンテナも含めて削除します。
|  |  | 
docker-compose.ymlを編集します。前述の通り、--locale=Cです。
|  |  | 
それでは、initdbが実行されるように、永続化されているポスグレのデータボリュームを削除(またはmove)して空の状態にします。怖えぇぇー!!!
lc_collate=Cクラスタの作成とRedmineのマイグレーション
改めてコンテナを作成します。ポスグレはデータボリュームが無くなっているのでinitdbが実行され、空のクラスタがlc_collate=Cで作られる…はず。そしてRedmineはDBが空になっているのでマイグレーションしてくれる…はず。
|  |  | 
yaml_dbでリストア
Redmineコンテナが再作成されているので、もう一回Gemfileを配置~bundle installしてyaml_dbをインストールします。(割愛)
先程永続化されたディレクトリに退避したdata.ymlをdb/ディレクトリに戻して、ロードします。(Redmineコンテナで実行)
|  |  | 
これでlc_collate=Cに変更できました!
|  |  | 
ブラウザでRedmineを見てもそれっぽくソートできるようになっているはずです。やったね!
ハマりどころ
いくつかハマりどころがあったので解説。
ゴミテーブルがある場合は予め削除しておく
私は以前ナレッジベースプラグインを使っていたのですが、これを削除した際にVERSION=0でマイグレーションしていなかったようです。このため、プラグインは存在しないのにテーブルが残留している状態となっていました。
この状態でyaml_dbでダンプを取ると、当然ナレッジベースプラグインのテーブルデータもエクスポートされますが、その後のマイグレーションではプラグインがないためテーブルは作成されません。そうすると、yaml_dbでデータをロードする際にテーブルが存在しないエラーが発生してしまいます。こうなると、もう一度プラグインをインストールしてテーブルを作るか、data.ymlをシコシコ編集して該当データを削除する必要があります。
ですので、マイグレーションで作成されないテーブルが存在しないか、一度チェックしておくことをおすすめします。
チェック方法ですか?同じプラグイン配置された状態で空のRedmineインスタンスを立ててみて、DB上に作成されたテーブルを比較してみるとかですかね…。面倒ですがやったほうがいいと思います…。
PostgreSQLコンテナだけを削除しない
ポスグレのデータを削除してコンテナを再作成する際、Redmineコンテナも作り直す必要なくね?と思ったのですが、以下のようにコンテナ間のリンクがおかしくなってしまう様です。Docker Networkを修正することで対処できるかもしれませんが、面倒ですしRedmineコンテナも作り直すが無難かなと思います。
$ docker exec -it redmine_web_1 /bin/bash
Error response from daemon: Cannot link to a non running container: /redmine_db_1 AS /redmine_web_1/db