1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第29章|今さら学ぶ「非同期処理・メール送信(Active Job / Action Mailer)」

1
Last updated at Posted at 2026-03-02

第29章|今さら学ぶ「非同期処理・メール送信(Active Job / Action Mailer)」

📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ

この章でわかること

  • Active Jobとは — 「あとでやっておいて」と頼める仕組み
  • Solid Queue(Rails 8.0 標準)— Redis不要のジョブキュー
  • Action Mailerの流れ — アプリが自動で手紙を出す郵便局
  • 開発環境でのメール確認(letter_opener)
  • 本番環境でのメール配信(Resend)

🏠 たとえ話で掴む「非同期処理」

レストランで料理を注文したとき、ウェイターが厨房で料理ができあがるまでテーブルの前で立ち尽くしていたら困る。普通は注文を厨房に伝えたら、すぐ次のお客さんの注文を取りに行きます。

非同期処理 はまさにこれです。「メールを送る」「画像をリサイズする」「通知を作成する」といった時間のかかる処理を、 裏方(バックグラウンドジョブ) に任せて、ユーザーへのレスポンスを先に返します。

レストラン Railsアプリ
注文を厨房に伝える ジョブをキューに入れる
ウェイターは次の注文へ レスポンスを即座に返す
厨房で料理を作る バックグラウンドでジョブを実行
できたら運ぶ 完了したらメール送信等が実行される

非同期処理とは何か — 技術的な定義

同期処理 とは、処理が完了するまで次の処理に進まない方式です。Railsのコントローラは通常、上から順番に処理を実行して、すべて終わってからレスポンスを返します。

問題は「時間のかかる処理」がコントローラの中にあるときです。たとえばフォロワー100人に通知メールを送る処理が5秒かかるとすると、ユーザーは記事を投稿してから5秒間、画面が固まった状態で待つことになります。

非同期処理 は、この時間のかかる処理を キュー(待ち行列) に入れて、別のプロセス(ワーカー)に実行させる方式です。コントローラは「あとでやっておいて」とキューにジョブを積んだら、すぐにレスポンスを返します。

【同期処理】
リクエスト → コントローラ → 記事保存 → メール送信(5秒) → レスポンス
                                        ↑ ユーザーはここで待つ

【非同期処理】
リクエスト → コントローラ → 記事保存 → ジョブをキューに追加 → レスポンス(即座)
                                                              ↓
                                    ワーカーが裏でメール送信を実行

Railsではこの仕組みを Active Job (ジョブの標準インターフェース)と ジョブバックエンド (実際にキューを管理するライブラリ)の組み合わせで実現します。


⚙️ Active Job — ジョブの共通インターフェース

Active Job は、バックグラウンドジョブを書くためのRails標準のフレームワークです。ジョブの書き方を統一する「共通インターフェース」であり、裏側で使うバックエンド(Solid Queue、Sidekiq等)を差し替えても ジョブのコードは変更不要 です。

# app/jobs/notification_job.rb
class NotificationJob < ApplicationJob
  queue_as :default

  def perform(article)
    article.user.followers.find_each do |follower|
      Notification.create!(
        user: follower,
        notifiable: article,
        action_type: "published"
      )
    end
  end
end
# ジョブをキューに投入(即座にreturnされる)
NotificationJob.perform_later(@article)
# → ユーザーにはすぐレスポンスが返り、通知作成は裏で実行される

perform_later と perform_now

# 非同期実行(キューに入れて、ワーカーがあとで実行)← 推奨
NotificationJob.perform_later(@article)

# 同期実行(その場で即実行。キューを使わない)
NotificationJob.perform_now(@article)
# → テストやデバッグ時に使うことがある

リトライとエラーハンドリング

ジョブの実行中にエラーが起きることもあります(外部APIの一時障害など)。Active Jobにはリトライの仕組みがあります。

class NotificationJob < ApplicationJob
  queue_as :default
  retry_on Net::OpenTimeout, wait: 5.seconds, attempts: 3

  def perform(article)
    # ...
  end
end

retry_on を指定すると、指定したエラーが発生したとき自動でリトライします。


🔋 Solid Queue(Rails 8.0 標準)

Rails 8.0 では Solid Queue がデフォルトのジョブバックエンドです。従来のSidekiq + Redisの代わりに、 DBだけでジョブキューを管理 します。

# config/environments/production.rb
config.active_job.queue_adapter = :solid_queue
# → Redis不要!DBのテーブルでジョブを管理
従来(Sidekiq + Redis) Solid Queue(Rails 8.0)
追加ミドルウェア Redis サーバ必要 不要(DBに保存)
運用コスト Redisの管理が必要 DB だけで完結
処理速度 高速 十分に高速
設定 Gemfile + 設定ファイル Rails 8.0 でデフォルト有効

💡 Rails 7 との違い
Rails 7 では Sidekiq + Redis が主流でした。Rails 8.0 では Solid Queue がデフォルトなので、Redisのセットアップなしでバックグラウンドジョブが使えます。既存のSidekiqプロジェクトはそのまま使えますが、新規プロジェクトではSolid Queueがおすすめです。


📮 Action Mailer — アプリが自動で手紙を出す

Action Mailer は、Railsからメールを送信するための仕組みです。コントローラに似た構造で、ビュー(テンプレート)を使ってメール本文を組み立てます。

技術的には、Action Mailerは「メーラー(Mailer)」というクラスで送信処理を定義し、対応するビューテンプレート(app/views/メーラー名/)でメール本文のHTMLを作成します。コントローラが render でHTMLを返すのと同じ構造です。

メーラーの作成

$ rails generate mailer NotificationMailer
# app/mailers/notification_mailer.rb
class NotificationMailer < ApplicationMailer
  def new_article(user, article)
    @user = user
    @article = article
    mail(to: @user.email_address, subject: "新しい記事が投稿されました")
  end

  def password_reset(user, token)
    @user = user
    @token = token
    mail(to: @user.email_address, subject: "パスワード再設定のお知らせ")
  end
end
<%# app/views/notification_mailer/new_article.html.erb %>
<h1><%= @user.name %> さん</h1>
<p>フォロー中のユーザーが新しい記事を投稿しました。</p>
<p>
  <strong><%= @article.title %></strong><br>
  by <%= @article.user.name %>
</p>
<p>
  <%= link_to "記事を読む", article_url(@article) %>
</p>

メールの送信

# 同期送信(レスポンスが遅くなるので非推奨)
NotificationMailer.new_article(user, article).deliver_now

# 非同期送信(Active Job経由で裏側で送信 ← 推奨)
NotificationMailer.new_article(user, article).deliver_later

deliver_later を使えば、メール送信はバックグラウンドで処理され、ユーザーへのレスポンスは即座に返ります。


🔧 開発環境でのメール確認(letter_opener)

開発環境で実際にメールを送ると困るので、 letter_opener でブラウザ上にメールをプレビューします。

# Gemfile
group :development do
  gem "letter_opener_web"
end
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener_web
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
# config/routes.rb
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?

http://localhost:3000/letter_opener にアクセスすると、送信されたメールの一覧がブラウザで確認できます。実際にメールが送られることはないので安心です。


📬 本番環境でのメール配信(Resend)

本番環境では Resend を使います。APIがシンプルで、無料枠(月100通)があります。

# Gemfile
gem "resend"
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: "smtp.resend.com",
  port: 587,
  user_name: "resend",
  password: Rails.application.credentials.dig(:resend, :api_key),
  authentication: :plain,
  enable_starttls_auto: true
}
config.action_mailer.default_url_options = { host: "knowledgenote.example.com" }

💡 password に直接APIキーを書かず、credentials(→ 第20章)で管理している。環境変数(ENV["RESEND_API_KEY"])を使う方法もある。


🛠️ KnowledgeNoteでの具体例

KnowledgeNoteでは「記事が公開されたらフォロワーに通知+メール送信」を非同期で処理します。フォロワーへのリアルタイム通知(WebSocket)については(→ 第31章で詳しく扱います)。

# app/services/article_publish_service.rb([第25章](https://qiita.com/harapeco-mgn/items/dcbdf96e5c35d2249f46)の続き)
class ArticlePublishService
  def call
    ActiveRecord::Base.transaction do
      save_article!
      attach_tags!
    end
    # トランザクションの外で非同期処理を投入
    NotificationJob.perform_later(@article) if @article.published?
    true
  rescue ActiveRecord::RecordInvalid
    false
  end
end
# app/jobs/notification_job.rb
class NotificationJob < ApplicationJob
  queue_as :default
  retry_on Net::OpenTimeout, wait: 10.seconds, attempts: 3

  def perform(article)
    article.user.followers.find_each do |follower|
      Notification.create!(
        user: follower,
        notifiable: article,
        action_type: "published"
      )
      NotificationMailer.new_article(follower, article).deliver_later
    end
  end
end

ポイントは、NotificationJob の中で deliver_later を使っている点です。ジョブの中からさらにメール送信ジョブが作られるので、通知の作成とメール送信が並行して進みます。

また、非同期処理は トランザクションの外 で投入するのが重要です。トランザクション内で perform_later すると、ロールバック時にジョブだけが残ってしまう(DBにデータがないのにメールが送られる)危険があります。


💼 面接で聞かれたら?

Q:非同期処理はなぜ必要ですか?

「メール送信や通知作成など、時間のかかる処理をリクエストの中で同期的に実行すると、ユーザーへのレスポンスが遅くなります。Active Jobでこれらをバックグラウンドジョブとしてキューに入れれば、レスポンスを即座に返しつつ、処理は裏側で順次実行されます。Rails 8.0ではSolid QueueがデフォルトでRedis不要です。」

深掘りされたら:

  • 「deliver_now と deliver_later の違いは?」→ deliver_nowは同期送信でレスポンスが遅くなる。deliver_laterはActive Job経由の非同期送信で、レスポンスに影響しない。本番ではdeliver_laterを使う。
  • 「ジョブが失敗したらどうなる?」→ Active Jobの retry_on で自動リトライできる。リトライ回数を超えたら discard_on で破棄するか、Dead Letter Queue(失敗ジョブの保管場所)に入れて後で調査する。
  • 「トランザクション内でperform_laterを呼ぶとどうなる?」→ ロールバックしてもジョブは取り消されない。データが存在しないのにジョブが実行される危険があるため、トランザクションの外でジョブを投入すべき。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ 非同期処理は「注文を厨房に伝えて、ウェイターは次の注文へ」。レスポンスを即座に返し、重い処理は裏で実行する
  • ✅ Active Jobはジョブの共通インターフェース。バックエンド(Solid Queue / Sidekiq等)を差し替えてもコード変更不要
  • ✅ Solid Queue(Rails 8.0 標準)はRedis不要。DBだけでジョブキューを管理する
  • ✅ Action Mailerでメール送信。deliver_laterで非同期送信が推奨
  • ✅ 開発環境は letter_opener_web でブラウザプレビュー、本番は Resend
  • ✅ 非同期ジョブはトランザクションの外で投入し、データの整合性と非同期処理を両立する

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?