Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsでワンタイムスクリプトを書く3つの方法と使い分けのポイント

CSVファイルからアプリに大量のデータをインポートしたいと思ったことはありませんか?あるいは、顧客レビューの中の文字化けした文字を修正する必要があったり、Redisでのデータ保存方針が変わり、古い形式から新しい形式へ全データを移行しなければならなかった経験もあるかもしれません。

Avvoでは、こうしたタスクを「アドホックタスク」と呼んでいました。つまり、おそらく一度しか実行する必要がないタスクです。では、Railsでアドホックタスクを処理する最良の方法は何でしょうか?

1. データベースマイグレーションとして書く

データベース内のデータ構造を変更する必要がある場合、マイグレーションはうまく機能します。タスクが実行済みかどうかを追跡し、変更を他の環境にも引き継げる——これはまさにマイグレーションが本来の目的のために設計された機能です。おそらく皆さんもすでにその用途で使っていることでしょう。

データ変更を同時に行う場合、マイグレーションはうまく機能するかもしれません。ただし、いくつか注意点があります。

マイグレーション内で Permissions.create(...) のようなモデル呼び出しを行うと、トラブルの原因になり得ます。モデル側に変更が入っていた場合、マイグレーション実行時点でモデルが利用できず壊れることがあります。また、マイグレーションを書いた時点から実際に実行される時点までの間に、モデルが変更されている可能性もあるのです。回避策は存在しますが、エラーが起きやすく、奇妙な形で失敗することもあります。

さらに、タスクがActiveRecordを一切使わないものであれば、マイグレーションのメリットは薄れます。

これらは致命的な欠点ではありません。しかし筆者は、マイグレーション内で大量のデータをインポート・変更することは避ける傾向にあります。より良い選択肢があるからです。

2. rakeタスクとして書く

実行したいタスクがあり、おそらく一度だけ走らせたい。そして、ローカル環境でテストした上で本番環境でも実行できるようにしたい——そんなニーズには、rakeタスクが非常によく適合します。Railsならrakeタスクの雛形も自動生成できます:

$ be rails g task locations import
      create  lib/tasks/locations.rake

生成されたファイルにコードを書いていきます:

lib/tasks/locations.rake

namespace :locations do
  desc "TODO"
  task import: :environment do

  end
end

taskブロックの中では、アプリのすべてのモデルやその他のコードを自由に使えます。Railsコンソールを開いているかのようにコードを書けるため、データのインポートや変更がとても簡単です。

タスクを書いたら、rake locations:import で実行できます。Herokuを使っているなら heroku run rake locations:import、Capistranoを使っているなら capistrano-rake gem で実行可能です。ただし、さらに便利な選択肢もあります。

3. sidekiq-schedulerでスケジュールジョブとして書く

アプリがある程度の規模になっていれば、おそらくすでにSidekiqやResqueなどを使っているはずです。

これらのバックグラウンドジョブプロセッサの多くは、ジョブを後日実行するようスケジュールできます。たとえばSidekiqには sidekiq-scheduler というgemがあります。そして、このgemにはちょっとしたテクニックがあります。

「自動的には自分自身をスケジュールしないが、手動でいつでもスケジュールできるジョブ」があったらどうでしょうか? 後でもう一度実行したいかもしれない「一回限り」のジョブや、UI経由で実行したいジョブにぴったりです。

sidekiq-schedulerでは、ジョブを遥か未来の日付にスケジュールし、無効状態に設定しておくことでこれが実現できます:

sidekiq.yml

:schedule:
  location_importer:
    class: LocationImporterWorker
    at: '3001/01/01'
    enabled: false

こうしておくと、sidekiq-webにアクセスした際に、ジョブを手動でエンキューするボタンが表示されます:

Railsでワンタイムスクリプトを書く3つの方法と使い分けのポイント

これにより、開発環境でも本番環境でも、準備が整ったタイミングでいつでもジョブを実行できます。再実行が必要になったときも、UI上にいつでも待機しています。

ただし、危険性の高いジョブには不向きです。誤ってボタンをクリックしてしまうリスクが高すぎます。また、完了まで時間のかかるジョブにも向いていません。Sidekiqはジョブが短時間で終わるときに最も性能を発揮します。長時間ジョブがワーカーを占有すると、ジョブ完了までSidekiqを安全に再起動できなくなります。しかし、ジョブが高速で複数回実行しても安全なら、この方法は very well 機能します。 クリーンアップ系のタスクなら、むしろ定期的に実行したくなるかもしれません。

スケジューリングと実行トリガーだけに集中したい場合や、ワンタイムスクリプトでパラメータを柔軟に設定したい場合は、読者のDmitryさんが紹介してくれた sidekiq-enqueuer というgemもあります。sidekiq-enqueuerを使えば、ジョブのスケジュール登録もパラメータ設定も、すべてSidekiqのWeb UIから完結できます。

本番サーバーにSSH接続してRailsコンソールにコードを貼り付ける

……というのは、もちろん冗談です。絶対にやめましょう。

どの方法を選ぶべきか?

筆者はこれらすべての方法でワンタイムタスクを実行してきましたが、通常はまずrakeタスクを選びます。確実に動作し、誤って実行される心配が少なく、役目を終えたら削除も簡単だからです。とはいえ、毎回rakeタスクが正解というわけではありません。

マイグレーションを選ぶのが適しているケース:

  • データベーススキーマ変更の一環として、SQLでデータを修正するジョブの場合
  • カラム値の変更や数件のレコード追加など、ごくシンプルなデータ操作で済む場合
  • 実行済みかどうかを確実に追跡し、二度と実行されないようにしたい場合

Sidekiqジョブを選ぶのが適しているケース:

  • 将来的に再実行する可能性があると感じる場合
  • 自分以外の人間が実行する必要がある場合(相手はボタンをクリックするだけで済みます)
  • 短時間で終わるデータインポートやデータクリーンアップのジョブの場合(最初は想定していなくても、結局定期的に実行することになりがちです)

あなたはどうしていますか?別の選択肢を持っていたり、違う判断基準で選んだりしているなら、ぜひコメントで教えてください!

  1. AWS LambdaでRailsアプリを動かす方法!Lambyを使ったデプロイ手順を徹底解説

    サーバーレスコンピューティングは、サーバーの管理やプロビジョニングの作業をクラウドプロバイダーにオフロードできる仕組みとして、多くのテックチームで急速に標準になりつつあります。その代表格がAWS Lambdaです。LambdaはNode.js、Java、Python、Rubyといった主要なプログラミング言語を幅広くサポートしています。しかし、言語そのものだけでなく、それらの言語で構築されたフレームワークの機能に依存するサーバーレス関数を実行したいケースもあるでしょう。 本記事では、RailsアプリケーションをAWS Lambda上で実行する方法を解説します。サーバーレスコンピューティングとAW

  2. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた