復旧設計とフォールバック戦略
復旧設計とフォールバック戦略
Section titled “復旧設計とフォールバック戦略”障害から自動/手動で安全に戻れる設計を詳しく解説します。
Graceful Degradation
Section titled “Graceful Degradation”外部依存が落ちたらキャッシュ・スタブで縮退運転する。
# ✅ 良い例: キャッシュによるフォールバック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 ) endendなぜ重要か:
- 可用性の向上: 外部依存が落ちても、最低限のサービスを提供可能
- ユーザー体験: 完全なエラーではなく、スタブデータを返すことでユーザー体験を維持
再起動安全性
Section titled “再起動安全性”再起動時に中途状態をリカバリ可能にする(例:処理キューの再読込)。
# ✅ 良い例: 再起動時に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 endend
# アプリケーション起動時に実行# config/initializers/outbox_recovery.rbRails.application.config.after_initialize do OutboxRecoveryService.recover_pending_eventsendなぜ重要か:
- データの整合性: 再起動時に中途状態のデータをリカバリ可能
- 自動復旧: 手動介入なしで自動的に復旧可能
バックオフ戦略
Section titled “バックオフ戦略”Exponential Backoff とJitterでセルフDDoSを防止する。
# ✅ 良い例: Exponential Backoff + Jitterclass 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 endendバックオフの計算:
リトライ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でサーバーへの負荷を段階的に軽減
手動オペ対応
Section titled “手動オペ対応”フラグ切替・一時停止がコード修正なしで可能な設計。
# ✅ 良い例: 機能フラグによる制御class FeatureFlag < ApplicationRecord validates :key, presence: true, uniqueness: trueend
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) endend
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 endend
# 管理画面で機能フラグを切り替え可能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' } endendなぜ重要か:
- 迅速な対応: コード修正なしで、機能を一時的に無効化可能
- リスクの軽減: 問題が発生した場合、すぐに機能を無効化して影響を最小化
復旧設計とフォールバック戦略のポイント:
- Graceful Degradation: 外部依存が落ちたらキャッシュ・スタブで縮退運転
- 再起動安全性: 再起動時に中途状態をリカバリ可能にする
- バックオフ戦略: Exponential Backoff + JitterでセルフDDoSを防止
- 手動オペ対応: フラグ切替・一時停止がコード修正なしで可能な設計
これらの設計により、障害から自動/手動で安全に戻れます。