PostgreSQLからsqliteの移行をRailsを使って行う

この記事は

Ruby on Railsのカレンダー | Advent Calendar 2021 - Qiita 18日目の記事です。
昨日に引き続き、空いていたので参加してみました。
 

背景

昨日、Advent Calender Ranking 2021New RelicをRailsアプリにinstallしたところ、DB部分で速度が出ていないことが分かりました。
 
当初、このRailsアプリはHerokuにデプロイし、postgres add-onn を利用していましたが、レコードの上限数を超えたため、とりあえず無料で使えるElephant SQLへ乗り換えていました。
(この辺の話は以下に記載)

herokuのRailsアプリでDBをElephantSQLにしてみた - rochefort's blog

 
こいつが若干遅いということは分かっていたのですが、今はHerokuからVPSに移行しているので、今回ElephantSQLからDBの移行を行いました。

以下、本題。

DBの選択

PostgreSQLMySQLサービスを使っても良いのですが、メモリ使うし、
まぁ、sqlite使って壊れても割と簡単にデータ復旧できるし問題ないだろうという判断でsqliteへの移行を行うことにしました。

ElephantSQLからのdump取得

これは簡単で、consoleからボタンひとつで取り出せます。

sqliteへの取り込み

ここが今回はまりました。

dumpはsql形式なので、ちょこっと編集すればsqliteに取り込むのも簡単だろうと思っていましたが、dumpはcopy句を使っていて、これが現在のsqliteでは使えません。

他にもPostgreSQLのdumpをsqliteに変換できるJava系のツールも公開されていたので試してみましたが、古いようで動きませんでした。

後は、csvに変換して取り込む方法ですが、NULLが空白になっちゃうし(アプリケーション的には問題ないけど、なんとなく嫌)、他の方法を思索したところ、 Rails6のMultiple Databaseの機能を使って移行できるのではないかという考えが思いつきました。

ご参考)
Active Record で複数のデータベース利用 - Railsガイド

やってみる

事前準備として、ElephantSQLのdumpを移行元PostgreSQLとしてimportしておきます。
また、移行先のsqliteも必要ですので、一時的にdatabase.ymlを変更し新しいDBを作って、rails db:migrate しておきます。

multiple DB設定

config/database.yml に移行先(sqlite)、移行元(PostgreSQL)の定義をしておきます。

development:
  # 移行先
  primary:
    database: db/production.sqlite3
    adapter: sqlite3
    pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

  # 移行元
  primary_replica:
    adapter: postgresql
    encoding: unicode
    database: postgres-rails-app
    pool: 5
    username: rochefort
    password:

ApplicationRecordに primary、primary_replicaのデータベースロール(writing, reading)を設定するだけ。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

移行

後はrails consoleで読み書きするだけです。
書き込みは、Rails6から利用できる insert_all を活用。

def copy_table(klass)
  list = nil
  ActiveRecord::Base.connected_to(role: :reading)  { list = klass.all }
  ActiveRecord::Base.connected_to(role: :writing)  { klass.insert_all(list.map(&:attributes)) }
end

# example
copy_table(Author)

感想

という感じで、サクッとできました。
当初考えていたdumpのsqlを手動で変更する手間もないですし、 csv経由でやるより簡単な印象です。
 
こんなことのためにMulti DB使ってるのは私ぐらいでしょうが、RailsのMulti DB自体は設定も簡単なので良いですね。

See Also

Advent Calendar Ranking 2021リリース(Rails7対応済)

この記事は

Ruby on Railsのカレンダー | Advent Calendar 2021 - Qiita 17日目の記事です。
さっき見たら、スカスカだったので参加してみました。
 
Rails7出たのにさびれていて悲しい。Rails peopleはどこに行ったんだ。
 

作ったもの

Advent Calendarをhatebu数で並べて見れるサイト です。 去年作成したものを少し変更して、2021年版として公開しています。
昨年作成した当時の記事はこちら。
 
年末年始にじっくり見ようかと思います。

やったことざっくり

  • Rails7 Upgrade
  • Ruby3 Upgrade
  • CSS Framework変更 (ink -> bootstrap5)
  • 2021年のadvent calendarに対応  

こんな感じです。
細かいところだと、同一entryを複数のカレンダーに登録している人が複数名いて、それらを除外する処理を入れたり、Rails6で入った upsert_all で高速化したりということをやっていました。

大体すんなり行きましたが、Rails7のところはハマったところもあったので、少し細かく書くことにします。

Rails 7 Upgrade

昨日リリースしたてのRails7に対応。
 
Rails7では新しいフロントエンドの仕組みとして3つ提供されています。
ざっくり書くと、importmap、jsbundling-rails(esbuild, webpack, rollup)、Webpacker です。

もともとRails6だったので、Webpackerを使っていましたが、折角なので新しいものを使ってみようと思い、 jsbundling-rails(esbuild)を利用しました。
 

importmapは良さそうなのですが、まだ課題がありそうなのと、CSS Frameworkをnodeで管理したかったため、今回は見送り。
jsbundling-rails でjs、cssbundling-railscss を利用しています。 開発は、foreman使ってesbuildとsassで自動でbuildして、その結果をasset pipelineでpublic以下に出力するという仕組み。
Webpackerよりは体感早くなって良い気がしますが、少し分かりにくい感じがします。  
その結果、ハマった箇所もありました。
 

ライブラリのassetsの扱い

bootstrap-icons を利用しているのですが、sassのbuildではそこまで面倒みてくれず、対処法がよく分からなかっため、asset pipelineに載せる形で vendor/assets 以下に配置しています。

フォルダ構成も配布元のcssをbaseにしたりと、Rails3とか4ぐらいの時にやったようなことを再びやっていて、少し悲しくなりました。
webpackにしておけば、苦労はなかったかも。

sass-railsのhelper

image-url、asset-url みたいなsass-railsが提供していたものをどうするのかというところですが、 sassでbuildし(cssにして)、それをsprocketsで処理するという流れなので、sass-railsが出てきません。
ということでsprocketsにその処理を任せる方法が紹介されていました。
 

# config/initializers/asset_url_processor.rb
class AssetUrlProcessor
  def self.call(input)
    context = input[:environment].context_class.new(input)
    data = input[:data].gsub(/asset-url\(["']?(.+?)["']?\)/) do |_match|
      "url(#{context.asset_path($1)})"
    end
    {data: data}
  end
end

Sprockets.register_postprocessor "text/css", AssetUrlProcessor

コメントされているのは、あのRailsCastsのryanbさんですね。
Referencing assets in CSS · Issue #22 · rails/cssbundling-rails
 
productionにデプロイして初めて気付くという、結構残念なところでもありました。
ここもRails3/4で味わったような感じがします。

Ransack

Ransackでハマった件は、以下に記載。
Rails7(alpha2)ではransackの更新が必要 | Rochefort.dev

おわりに

Rails7少しハマりましたが、開発の体験自体はwebpacker時代よりは上がっている気がするので、まぁ良いかなと。
sprocketsの代わりにpropshaftが利用できるようになったり、importmapも普及してくると状況がまた変わってくるかもしれません。

See Also