Vol.042026年5月9日
Logged2 min · SoraEndo
Process

Sidekiq cron で予約公開と revalidate を統合する

予約時刻に DB の status を published に切替、即 ISR を再生成する。Sidekiq 8 builtin scheduler を使った 1 連鎖の実装。

SoSoraEndo2026年5月9日2 min645

やりたいこと

ブログサイトで「予約公開」を入れると、必ず付随で「予約時刻に Next.js の ISR を再生成する」が要る。これを別系統で組むと、publish と revalidate がズレる リスクが残る。Sidekiq の cron で 両方を 1 つの worker に集約するのが整然。

実装

# backend/config/sidekiq.yml
:scheduler:
  schedule:
    publish_scheduled_posts:
      cron: '* * * * *'  # 毎分
      class: PublishScheduledPostsWorker
# backend/app/workers/publish_scheduled_posts_worker.rb
class PublishScheduledPostsWorker
  include Sidekiq::Worker

  def perform
    Post.where(status: :scheduled)
        .where('published_at <= ?', Time.current)
        .find_each do |post|
      Post.transaction do
        post.update!(status: :published)
        RevalidatePostsWorker.perform_async(post.slug)
      end
    end
  end
end

毎分 1 回、scheduledpublished_at が過去になったものを published に遷移 + 即 ISR 再生成。トランザクションで囲むので、DB 更新と revalidate が必ずペアで走る。

RevalidatePostsWorker

class RevalidatePostsWorker
  include Sidekiq::Worker

  def perform(slug)
    post = Post.find_by(slug: slug)
    return unless post

    paths = [
      "/posts/#{post.slug}",
      '/',
      '/posts',
      "/categories/#{post.category.slug}",
    ]

    HTTP.headers('Authorization' => "Bearer #{ENV['NEXT_REVALIDATE_SECRET']}")
        .post("#{ENV['NEXT_PUBLIC_URL']}/api/revalidate", json: { paths: paths })
  end
end

revalidate するのは 記事ページ + トップ + 一覧 + カテゴリページ。新しい記事が出ると、最低この 4 つの URL は更新が必要。

Sidekiq 8 の cron 設定

Sidekiq 8 で cron スケジューラが builtin になった(Sidekiq.options[:scheduler])。これ以前は sidekiq-cron gem を別途入れる必要があった。

# backend/config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL') }
end

config/sidekiq.yml:scheduler: セクションだけで cron job が動く。

落とし穴

実運用で気付いた点 3 つ:

1. cron の頻度は「毎分」で十分

*/1 * * * * で毎分実行。1 秒以下の精度は不要(ブログの予約公開は分単位で十分)。

2. published_at は 必ず past timestamp に変える

scheduled → published に遷移する時、published_at を現在時刻にも更新するか、scheduled 時に設定した時刻のままにするか、で運用が変わる。

私は scheduled 時の時刻のまま にした。「2026-05-15 10

公開予定」が「実際には 10:01
に publish」だと、表示時刻がブレるのが嫌だったから。

3. tz は明示する

cron: '0 9 * * *' のような時刻指定の cron は、Sidekiq の動く環境の TZ で評価される。Docker で TZ=Asia/Tokyo を指定しておかないと UTC 9 時 = JST 18 時で走る。

# docker-compose.yml
sidekiq:
  environment:
    TZ: Asia/Tokyo

監視

毎分の cron job は 失敗を観測する仕組み が要る。私は Sentry に「PublishScheduledPostsWorker が 5 分以上 enqueue されていない」アラートを仕込んでいる:

# 別の cron job で
PublishScheduledPostsWorker.perform_async  # 必ず enqueue できる、失敗したら別の worker から検知

これがないと「気付いたら 1 週間 publish が動いていなかった」事故が起きる。

まとめ

Sidekiq cron で予約公開と revalidate を統合する:

  1. PublishScheduledPostsWorker が毎分 scheduled をチェック
  2. published に遷移したら同時に RevalidatePostsWorker を enqueue
  3. Next.js の /api/revalidate で ISR キャッシュが更新される

3 つの worker を 1 つのチェーンに繋げるだけで、「予約時刻ぴったりに Next.js の表示も切り替わる」体験が組める。

自動化は 1 連鎖で終わらせる。連鎖の途中に手動が混ざると壊れる。

Tags

Reaction

Share

X (Twitter)