Skip to content

復旧設計とフォールバック戦略

復旧設計とフォールバック戦略

Section titled “復旧設計とフォールバック戦略”

障害から自動/手動で安全に戻れる設計を詳しく解説します。

外部依存が落ちたらキャッシュ・スタブで縮退運転する。

# ✅ 良い例: キャッシュによるフォールバック
class ProductService
def get_product(product_id)
# 1. キャッシュから取得を試みる
cached = Rails.cache.read("product:#{product_id}")
return cached if cached
# 2. データベースから取得を試みる
begin
product = Product.find(product_id)
# キャッシュに保存(TTL: 1時間)
Rails.cache.write("product:#{product_id}", product, expires_in: 1.hour)
product
rescue ActiveRecord::RecordNotFound => e
# 3. データベースが落ちている場合、外部APIから取得を試みる
begin
response = HTTParty.get("https://external-api.example.com/products/#{product_id}")
product = Product.new(JSON.parse(response.body))
# キャッシュに保存(次回はキャッシュから取得可能)
Rails.cache.write("product:#{product_id}", product, expires_in: 1.hour)
product
rescue => api_error
# 4. すべて失敗した場合、スタブデータを返す
Rails.logger.warn("All data sources failed, returning stub data", {
product_id: product_id,
db_error: e.message,
api_error: api_error.message
})
create_stub_product(product_id)
end
end
end
private
def create_stub_product(product_id)
# スタブデータ(最低限の情報のみ)
Product.new(
id: product_id,
name: 'Product information temporarily unavailable',
price: 0
)
end
end

なぜ重要か:

  • 可用性の向上: 外部依存が落ちても、最低限のサービスを提供可能
  • ユーザー体験: 完全なエラーではなく、スタブデータを返すことでユーザー体験を維持

再起動時に中途状態をリカバリ可能にする(例:処理キューの再読込)。

# ✅ 良い例: 再起動時にOutboxを再処理
class OutboxRecoveryService
def self.recover_pending_events
# アプリケーション起動時に、PENDING状態のイベントを再処理
pending_events = OutboxEvent.where(status: 'PENDING')
Rails.logger.info("Recovering #{pending_events.count} pending outbox events")
pending_events.find_each do |event|
# リトライ回数が上限を超えていない場合のみ再処理
if event.retry_count < 3
process_outbox_event(event)
else
Rails.logger.warn("Outbox event exceeded retry limit", {
event_id: event.id,
retry_count: event.retry_count
})
# 手動対応が必要な状態としてマーク
event.update!(status: 'MANUAL_REVIEW_REQUIRED')
end
end
end
end
# アプリケーション起動時に実行
# config/initializers/outbox_recovery.rb
Rails.application.config.after_initialize do
OutboxRecoveryService.recover_pending_events
end

なぜ重要か:

  • データの整合性: 再起動時に中途状態のデータをリカバリ可能
  • 自動復旧: 手動介入なしで自動的に復旧可能

Exponential Backoff とJitterでセルフDDoSを防止する。

# ✅ 良い例: Exponential Backoff + Jitter
class PaymentService
def charge_payment(order_id, amount, max_retries: 3)
retries = 0
begin
response = HTTParty.post(
'https://payment-api.example.com/charge',
body: {
order_id: order_id,
amount: amount
}.to_json,
headers: { 'Content-Type' => 'application/json' },
timeout: 3
)
JSON.parse(response.body)
rescue Net::ReadTimeout, Net::OpenTimeout => e
retries += 1
if retries < max_retries
# Exponential Backoff + Jitter
base_delay = 2 ** retries * 1000 # 1s, 2s, 4s
jitter = rand(1000) # 0-1s
delay = base_delay + jitter
sleep(delay / 1000.0)
retry
else
raise PaymentTimeoutError, "Payment API timeout after #{max_retries} retries: #{e.message}"
end
end
end
end

バックオフの計算:

リトライ1: 1000ms + random(0-1000ms) = 1000-2000ms
リトライ2: 2000ms + random(0-2000ms) = 2000-4000ms
リトライ3: 4000ms + random(0-4000ms) = 4000-8000ms

なぜ重要か:

  • セルフDDoS防止: すべてのクライアントが同時にリトライしないよう、Jitterでランダム化
  • サーバー負荷の軽減: Exponential Backoffでサーバーへの負荷を段階的に軽減

フラグ切替・一時停止がコード修正なしで可能な設計。

# ✅ 良い例: 機能フラグによる制御
class FeatureFlag < ApplicationRecord
validates :key, presence: true, uniqueness: true
end
class FeatureFlagService
def self.enabled?(key)
flag = FeatureFlag.find_by(key: key)
flag&.enabled || false
end
def self.enable(key)
flag = FeatureFlag.find_or_initialize_by(key: key)
flag.update!(enabled: true)
end
def self.disable(key)
flag = FeatureFlag.find_or_initialize_by(key: key)
flag.update!(enabled: false)
end
end
class OrderService
def create_order(order_data)
order = Order.create!(order_data)
# 機能フラグで外部API呼び出しを制御
if FeatureFlagService.enabled?('payment-api.enabled')
begin
PaymentService.new.charge_payment(order.id, order_data[:amount])
rescue => e
# 外部APIが無効化されている場合、エラーをログに記録するが処理は続行
Rails.logger.warn("Payment API disabled, skipping payment", {
order_id: order.id,
error: e.message
})
end
else
Rails.logger.info("Payment API disabled by feature flag")
end
order
end
end
# 管理画面で機能フラグを切り替え可能
class Admin::FeatureFlagsController < ApplicationController
def enable
FeatureFlagService.enable(params[:key])
render json: { message: 'Feature flag enabled' }
end
def disable
FeatureFlagService.disable(params[:key])
render json: { message: 'Feature flag disabled' }
end
end

なぜ重要か:

  • 迅速な対応: コード修正なしで、機能を一時的に無効化可能
  • リスクの軽減: 問題が発生した場合、すぐに機能を無効化して影響を最小化

復旧設計とフォールバック戦略のポイント:

  • Graceful Degradation: 外部依存が落ちたらキャッシュ・スタブで縮退運転
  • 再起動安全性: 再起動時に中途状態をリカバリ可能にする
  • バックオフ戦略: Exponential Backoff + JitterでセルフDDoSを防止
  • 手動オペ対応: フラグ切替・一時停止がコード修正なしで可能な設計

これらの設計により、障害から自動/手動で安全に戻れます。