Upstash Redis2026-07-03

【パフォーマンス】サーバーレスマイクロサービスにおける分散ロックの実装:Upstash Redisによるデータ整合性の確保


サーバーレス環境特有の「データ整合性」の難しさ

従来のモノリシックなWebアプリケーションでは、データベースのトランザクションロックや、アプリケーションメモリ内での排他制御(Mutex)を使用して、複数の処理が同時に同じデータ(例: 商品在庫、口座残高、ユーザー登録枠など)を書き換えるのを比較的容易に防ぐことができました。

しかし、クラウドネイティブな サーバーレスアーキテクチャ(Next.js API Routes, AWS Lambda) においては、以下のような特性からデータ競合が非常に発生しやすくなります。

  1. 完全な分散実行: 各APIリクエストは地理的・システム的に完全に隔離された別々のコンテナ(インスタンス)で並行処理されます。そのため、ローカルメモリ上での変数ロックは一切機能しません。
  2. 高速なスケールアウト: 数ミリ秒のうちに数十〜数百のコンテナが同時に立ち上がり、同じデータベースに向けて一斉に書き込みクエリを実行します。
  3. リトライ機構: 途中でネットワークエラーが発生すると自動リトライされ、すでに実行完了している処理が再度呼び出される「二重実行」のリスクが高まります。

この分散環境で、すべての処理の順序と排他を正しく守る仕組みが 「分散ロック(Distributed Lock)」 です。 超高速かつグローバルに共有可能なメモリデータストアである Upstash Redis を用いた、軽量で堅牢なロックアルゴリズム(Redlock簡略版)の実装を解説します。


2. 課題解決のビフォー・アフター

項目 ロック制御なし(Before) Upstash Redis 分散ロック(After)
同時アクセス ほぼ同じ瞬間に注文が入ると在庫数がマイナスになる 最初の処理が完了するまで次の処理を安全に待機させる
API応答速度 データベース側のテーブルロックで全体の応答が低下 メモリベースの高速判定によりミリ秒単位で処理完了
エラー時のスタック クラッシュした際、ロックがかかったまま解放されない 生存時間(TTL)の自動設定により、強制解放され復旧
開発コスト 複雑なRDBの分離レベル(Isolation Level)の調整が必要 SETNXベースのシンプルな排他制御APIで実装可能

3. 具体的な実装例:Redisを用いたアトミックな分散ロック

リソースを一時的にロックし、処理完了後に確実にアンロックする、Next.js API Route等で再利用可能なヘルパークラスの実装コード例です。

import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

export class DistributedLock {
  private lockKey: string;
  private lockValue: string;
  private ttlSeconds: number;

  constructor(resourceName: string, ttlSeconds = 10) {
    this.lockKey = `lock:dist:${resourceName}`;
    // ランダムな値で自分自身のロックであることを証明する (デッドロック・誤解放防止)
    this.lockValue = Math.random().toString(36).substring(2) + Date.now().toString(36);
    this.ttlSeconds = ttlSeconds;
  }

  // ロックの取得を試みる
  async acquire(): Promise<boolean> {
    // NX: キーが存在しない時のみセット, EX: 指定秒数後に自動消滅
    const result = await redis.set(this.lockKey, this.lockValue, {
      nx: true,
      ex: this.ttlSeconds,
    });
    return result !== null;
  }

  // ロックを安全に解除する(自分が取得したロックのみを削除)
  async release(): Promise<boolean> {
    // Luaスクリプトを使用してアトミックに「値の確認と削除」を実行する
    const luaScript = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;

    const result = await redis.eval(luaScript, [this.lockKey], [this.lockValue]);
    return result === 1;
  }
}

使用例:二重決済を防止するチェックフロー

export async function POST(req: Request) {
  const { paymentId } = await req.json();
  
  const lock = new DistributedLock(`payment:${paymentId}`, 15); // 15秒間ロック
  const isAcquired = await lock.acquire();

  if (!isAcquired) {
    return NextResponse.json({
      error: "Conflict",
      message: "ただいま決済処理中です。二重送信を防ぐためしばらくお待ちください。",
    }, { status: 409 });
  }

  try {
    // 決済処理の実行...
    return NextResponse.json({ success: true });
  } finally {
    // 確実にロックを解放する
    await lock.release();
  }
}

4. HadayaLabの分散システムアーキテクチャ設計

HadayaLabでは、サーバーレス環境ならではのデータ競合やパフォーマンス低下に悩む企業様に向けて、最新のクラウドインフラ設計を提供しています。

  • 高スケーラブルな分散ロックの実装: 数千リクエストが同時に集中してもデータ破綻しない堅牢な設計。
  • 冪等性(Idempotency)の担保: 同じリクエストがネットワーク都合で二重に送られても、一度しか実行されない安全なAPI設計。
  • Upstash Redis / Cloud Databaseのハイブリッド設計: 静的な情報はキャッシュ、書き込みはトランザクション制御という最適なデータ配置。

「予約システムやカート決済におけるデータ不整合を撲滅したい」「サーバーレスインフラの正しいデータ排他制御を学びたい」というエンジニアリング担当の方は、ぜひHadayaLabへお問い合わせください。