Upstash Redis2026-07-03
【パフォーマンス】サーバーレスマイクロサービスにおける分散ロックの実装:Upstash Redisによるデータ整合性の確保
サーバーレス環境特有の「データ整合性」の難しさ
従来のモノリシックなWebアプリケーションでは、データベースのトランザクションロックや、アプリケーションメモリ内での排他制御(Mutex)を使用して、複数の処理が同時に同じデータ(例: 商品在庫、口座残高、ユーザー登録枠など)を書き換えるのを比較的容易に防ぐことができました。
しかし、クラウドネイティブな サーバーレスアーキテクチャ(Next.js API Routes, AWS Lambda) においては、以下のような特性からデータ競合が非常に発生しやすくなります。
- 完全な分散実行: 各APIリクエストは地理的・システム的に完全に隔離された別々のコンテナ(インスタンス)で並行処理されます。そのため、ローカルメモリ上での変数ロックは一切機能しません。
- 高速なスケールアウト: 数ミリ秒のうちに数十〜数百のコンテナが同時に立ち上がり、同じデータベースに向けて一斉に書き込みクエリを実行します。
- リトライ機構: 途中でネットワークエラーが発生すると自動リトライされ、すでに実行完了している処理が再度呼び出される「二重実行」のリスクが高まります。
この分散環境で、すべての処理の順序と排他を正しく守る仕組みが 「分散ロック(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へお問い合わせください。