DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Design PostgreSQL Read Replicas with Claude Code: Read/Write Splitting, Prisma

Introduction

When DB reads are slow, use read replicas to split read/write traffic — route SELECT queries to replicas to reduce primary load. Let Claude Code design Prisma + PgBouncer patterns.

CLAUDE.md Rules

## DB Read Replica Rules
- Writes (INSERT/UPDATE/DELETE): primary only
- Reads (SELECT): route to replica
- Inside transactions: always use primary
- Post-write reads: use primary for 2 seconds
Enter fullscreen mode Exit fullscreen mode

Generated Implementation

// src/db/router.ts
import { AsyncLocalStorage } from 'async_hooks';

const dbContext = new AsyncLocalStorage<{ db: PrismaClient }>();

export function getReadDb(): PrismaClient {
  const ctx = dbContext.getStore();
  if (ctx?.db) return ctx.db; // Inside transaction: use that DB
  return replicaPool.getHealthyReplica();
}

// Post-write consistency: use primary for 2 seconds
export async function withPostWriteConsistency<T>(fn: () => Promise<T>): Promise<T> {
  return dbContext.run({ db: primaryDb }, fn);
}

// All transactions go to primary
export async function withTransaction<T>(
  fn: (tx: Prisma.TransactionClient) => Promise<T>
): Promise<T> {
  return primaryDb.$transaction(async (tx) => {
    return dbContext.run({ db: tx as any }, () => fn(tx));
  });
}
Enter fullscreen mode Exit fullscreen mode
// Replica pool with health checks
class ReplicaPool {
  private healthStatus: Map<PrismaClient, boolean> = new Map();

  constructor(private replicas: PrismaClient[]) {
    replicas.forEach(r => this.healthStatus.set(r, true));
    setInterval(() => this.checkHealth(), 10_000);
  }

  private async checkHealth() {
    await Promise.all(this.replicas.map(async (r) => {
      try {
        await r.$queryRaw`SELECT 1`;
        this.healthStatus.set(r, true);
      } catch {
        this.healthStatus.set(r, false);
      }
    }));
  }

  getHealthyReplica(): PrismaClient {
    const healthy = this.replicas.filter(r => this.healthStatus.get(r));
    return healthy.length > 0 ? healthy[0] : primaryDb; // Fallback to primary
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

  1. AsyncLocalStorage for context-based automatic DB routing
  2. Post-write Redis flag: use primary for 2 seconds after write
  3. ReplicaPool: health check every 10s, fallback to primary on failure
  4. Replica lag monitoring: alert if > 10 seconds

Review with **Code Review Pack (¥980)* at prompt-works.jp*

myouga (@myougatheaxo) — Axolotl VTuber.

Top comments (0)