はじめに
「新機能を本番でいきなり全ユーザーに公開するのが怖い」——ダークローンチ(Dark Launch)で本番トラフィックをシャドウイングし、実データで新機能を検証してから段階的に展開する設計をClaude Codeに生成させる。
CLAUDE.mdに設計ルールを書く
## ダークローンチ設計ルール
### 段階的ロールアウト
- 0% → 1% → 5% → 10% → 25% → 50% → 100% の段階で展開
- 各段階で1時間以上観察してエラー率・レイテンシーを確認
- 問題検出時は即座に0%に戻す(フラグOFF)
- ユーザーIDのhashで決定的に割り振り(同じユーザーは常に同じ体験)
### シャドウイング
- 本番は旧実装、バックグラウンドで新実装も実行
- 新旧の差異・エラー・レイテンシーをRedisに記録
- 差異サンプルを100件まで保存して調査可能に
生成される実装(抜粋)
// ロールアウト判定(ユーザーIDハッシュで決定的)
isUserInRollout(userId: string, userEmail: string, config: RolloutConfig): boolean {
if (config.disabledUserIds?.includes(userId)) return false;
if (config.enabledForInternal && userEmail.endsWith('@company.com')) return true;
const hash = djb2Hash(userId);
return (hash % 100) < config.percentage; // 決定的に割り振り
}
// シャドウイング(本番は旧実装、バックグラウンドで新実装)
async runWithShadowing<T>(featureKey, oldFn, newFn, requestId): Promise<T> {
const oldResult = await oldFn(); // 本番: ブロッキング
setImmediate(async () => {
let newResult, newError;
try { newResult = await newFn(); }
catch (err) { newError = err.message; }
const hasDiff = newError !== undefined || !deepEqual(oldResult, newResult);
await redis.incr(`shadow:${featureKey}:total`);
if (hasDiff) {
await redis.incr(`shadow:${featureKey}:diff`);
await redis.lpush(`shadow:${featureKey}:samples`, JSON.stringify({ requestId, newError }));
await redis.ltrim(`shadow:${featureKey}:samples`, 0, 99);
}
});
return oldResult; // 本番レスポンスは旧実装
}
// 管理API
router.put('/admin/dark-launch/:key', requireAdmin, async (req, res) => {
await rolloutManager.setConfig({ featureKey: req.params.key, percentage: req.body.percentage });
res.json({ success: true }); // パーセンテージを変えるだけでロールアウト制御
});
まとめ
- CLAUDE.md に段階ロールアウト(0→1→5→10→25→50→100%)・ユーザーIDハッシュで決定的割り振り・即時ロールバック方針を明記
- シャドウイング で本番トラフィックを旧実装で処理しつつ新実装をsetImmediateで並行実行——ユーザー体験に影響なく新実装を検証
- 差異率トラッキング でRedisのtotal/diff比率を1日ウィンドウで監視——差異サンプルを100件まで保存して差異の内容を調査
- 管理APIで%を操作 ——問題発見時はpercentage:0に設定するだけで即座にロールバック完了
デプロイ設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*
みょうが (@myougatheaxo) — ウーパールーパーのVTuber。
Top comments (0)