DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Claude Codeで読み後整合性(Read-After-Write)を設計する:DBレプリカ・モノトニック読み取り

はじめに

「投稿したのに一覧に出てこない」——読み取りレプリカの遅延が原因。Read-After-Write整合性(自分が書いたものは自分が読める)とモノトニック読み取り(時間が戻らない)を保証する設計をClaude Codeに生成させる。


CLAUDE.mdに設計ルールを書く

## 読み後整合性設計ルール

- Read-After-Write: ユーザーが書き込んだ直後のクエリはプライマリDBで実行
- 書き込み後15秒以内: プライマリで読み取り、以降はレプリカに移行
- Redisにwrite_timestampを保存(TTL: 15秒)
- クリティカルなエンドポイントは常にプライマリ
Enter fullscreen mode Exit fullscreen mode

生成される実装(抜粋)

// DBルーター
export class DBRouter {
  async markWrite(userId: string): Promise<void> {
    await redis.set(`raw:${userId}`, Date.now().toString(), { PX: 15_000 });
  }

  async getReadClient(userId: string | undefined): Promise<PrismaClient> {
    const writeTime = userId ? await redis.get(`raw:${userId}`) : null;
    return writeTime ? primaryDB : replicaDB; // TTL内はプライマリ
  }
}

// Expressミドルウェア
export function trackWrite(req, res, next) {
  const originalJson = res.json.bind(res);
  res.json = (body) => {
    if (req.method !== 'GET' && res.statusCode < 400 && req.userId) {
      dbRouter.markWrite(req.userId).catch(() => {});
    }
    return originalJson(body);
  };
  next();
}

// モノトニック読み取り: レプリカラグをpg_stat_replicationで確認
async function canUseReplica(sessionId: string): Promise<boolean> {
  const lastRead = await tracker.getLastReadTime(sessionId);
  const replicaLag = await getReplicaLagMs(); // SELECT EXTRACT(EPOCH FROM replay_lag)*1000
  return Date.now() - replicaLag >= lastRead;
}
Enter fullscreen mode Exit fullscreen mode

まとめ

  1. CLAUDE.md に書き込み後15秒はプライマリ読み取り・モノトニック読み取り保証・Redisでwrite_timestamp管理を明記
  2. trackWriteミドルウェア がレスポンス成功時に自動でRedisフラグを設定——アプリコードに整合性ロジックを書かなくてよい
  3. attachReadDB がユーザーのwrite履歴を見てプライマリ/レプリカを自動選択——TTL15秒で自然にレプリカへ移行
  4. pg_stat_replication でレプリカラグをリアルタイム監視——ラグが大きい場合はプライマリへ自動フォールバック

DB設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*

prompt-works.jp

みょうが (@myougatheaxo) — ウーパールーパーのVTuber。

Top comments (0)