prisma-boost Svelte Themes

Prisma Boost

Prisma query builder UI. Construct and run queries in browser.

prisma-boost

Caching layer for Prisma on Cloudflare Workers. Handles invalidation graphs, edge consistency, and multi-tenant isolation.

The Problem

Caching at the edge is easy. Consistency at the edge is hard.

When your cache is replicated across 300 locations:

  1. Stale reads after writes — Update a post, refresh, see old data
  2. Cross-model staleness — Update a user's name, comments still show the old one
  3. Transaction leakage — Commit succeeds, next read returns pre-commit cache
  4. Thundering herd — Cache expires, 1000 requests hammer your database
  5. FK blindness — Move a comment to a different post, old post's cache is stale

This module addresses these at different levels depending on your tier.


Feature Comparison

This Module Prisma Accelerate Redis + Manual
Consistency
Read-your-writes guarantee ✓ Pro tier Manual
Monotonic reads ✓ Pro tier Manual
Security
DB stays in private network ✗ Requires endpoint Depends
Tenant data isolation ✓ Pro + DO Manual
Invalidation
Automatic on write ✓ Model-level (free), PK-level (pro) ✗ Manual calls ✗ Manual
FK relationship traversal ✓ Pro tier
FK change detection (old + new) ✓ Pro tier
Nested relation write detection ✓ Pro tier
Field-aware (skip irrelevant) ✓ Pro tier
Instant logical eviction ✓ Epoch-based ✗ TTL-based ✓ Pub/sub
Budget controls
Resilience
Request deduplication Manual
Circuit breaker Manual
Retry with graceful degradation ✓ Pro tier Manual
Caching
Two-tier (L1 + L2) Manual
Composite PK support Manual
Scale
Per-tenant capacity ✓ Pro + DO ✗ Shared pool ✗ Shared
Hot tenant detection ✓ Pro + DO Manual
Infrastructure
Connection pooling ✗ (use Hyperdrive) ✓ Built-in
Managed service ✗ Self-host Varies
Edge-native ✓ Cloudflare ✓ Multi-cloud
Works outside Cloudflare
Operational
Setup complexity Low (free) to Medium (pro) Low High

Tier Comparison

Behavior Free Pro
Invalidation scope Model-level only PK-level precision
What gets invalidated All Post cache entries Only Post:42 cache entries
Read-your-writes
Monotonic reads
Transaction awareness
FK traversal Model-level PK-level with before/after diff
Tenant isolation (DO) Optional
Request deduplication
Circuit breaker
Bulk concurrency 3 6
Max bulk rows 150 600
Max cached value size 10 KB 128 KB

Free tier is appropriate for: Read-heavy workloads with infrequent writes, simple data models, development/staging environments.

Pro tier is appropriate for: Write-heavy workloads, complex relational data, multi-tenant SaaS, applications requiring strong consistency.


Setup Guide

Level 1: Free Tier with D1 (Simplest)

Best for getting started, development, or read-heavy apps with simple invalidation needs.

What you get: Cached reads, model-level invalidation on writes, request deduplication, circuit breaker.

What you don't get: PK-level precision, read-your-writes, FK traversal to specific records.

wrangler.toml:

name = "my-app"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"

[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"

schema.prisma:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

generator cache {
  provider = "prisma-boost"
  output   = "./generated"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model Post {
  id        Int       @id @default(autoincrement())
  title     String
  content   String
  published Boolean   @default(false)
  comments  Comment[]
}

model Comment {
  id      Int    @id @default(autoincrement())
  content String
  post    Post   @relation(fields: [postId], references: [id])
  postId  Int
}

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1';
import { createCacheExtension } from 'prisma-boost';
import { createKVAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  DB: D1Database;
  CACHE: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const adapter = new PrismaD1(env.DB);
    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: createKVAdapter({ kv: env.CACHE }),
        config: cacheConfig,
        version: 'v1',
        accountTier: 'free',
        executionCtx: ctx,
      }),
    );

    // Cached read
    const posts = await prisma.post.findMany({
      where: { published: true },
    });

    // Write triggers model-level invalidation
    // ALL Post and Comment caches are invalidated, not just specific records
    await prisma.comment.create({
      data: { content: 'Nice!', postId: 1 },
    });

    return Response.json(posts);
  },
};

What happens on write:

await prisma.post.update({
  where: { id: 42 },
  data: { title: 'New Title' },
});
// Invalidates: ALL Post cache entries (not just Post:42)
// Related Comment caches also invalidated at model level

Level 2: Free Tier with External PostgreSQL

Same features as Level 1, but with your existing PostgreSQL database via Hyperdrive.

Additional wrangler.toml:

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "your-hyperdrive-id"

schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';
import { createCacheExtension } from 'prisma-boost';
import { createKVAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  HYPERDRIVE: Hyperdrive;
  CACHE: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const pool = new Pool({
      connectionString: env.HYPERDRIVE.connectionString,
    });
    const adapter = new PrismaPg(pool);

    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: createKVAdapter({ kv: env.CACHE }),
        config: cacheConfig,
        version: 'v1',
        accountTier: 'free',
        executionCtx: ctx,
      }),
    );

    // Same behavior as Level 1
  },
};

Level 3: Pro Tier with PK-Level Invalidation

Enables precision invalidation: updating Post:42 only invalidates Post:42, not all Posts.

What you gain over free tier:

  • PK-scoped invalidation
  • Read-your-writes guarantee
  • Monotonic reads
  • Transaction awareness
  • FK traversal extracts actual IDs, not just model names
  • Before/after diffing for FK changes

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1';
import { createCacheExtension } from 'prisma-boost';
import { createKVAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  DB: D1Database;
  CACHE: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const adapter = new PrismaD1(env.DB);
    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: createKVAdapter({ kv: env.CACHE }),
        config: cacheConfig,
        version: 'v1',
        accountTier: 'pro',
        executionCtx: ctx,
      }),
    );

    // Update with precision invalidation
    await prisma.post.update({
      where: { id: 42 },
      data: { title: 'New Title' },
    });
    // Invalidates: Only Post:42, not all Posts

    // Read-your-writes: guaranteed to see the update
    const post = await prisma.post.findUnique({ where: { id: 42 } });
    // Returns { title: 'New Title' }, not stale cache

    // FK change detection
    await prisma.comment.update({
      where: { id: 99 },
      data: { postId: 43 }, // was postId: 42
    });
    // Invalidates: Comment:99, Post:42 (old parent), Post:43 (new parent)

    return Response.json(post);
  },
};

Transaction awareness:

await prisma.$transaction(async (tx) => {
  await tx.user.update({ where: { id: 1 }, data: { balance: 100 } });
  await tx.order.create({ data: { userId: 1, total: 50 } });

  // Reads inside transaction to written scopes bypass cache
  const user = await tx.user.findUnique({ where: { id: 1 } });
  // Returns fresh data, not pre-transaction cache
});

// After commit, subsequent reads see committed state
const user = await prisma.user.findUnique({ where: { id: 1 } });

Level 4: Pro Tier with Two-Tier Caching

Combines fast per-colo Cache API (L1) with durable global KV (L2).

What you gain: Faster reads (~1ms from L1 vs ~20ms from L2), with KV as fallback.

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1';
import { createCacheExtension } from 'prisma-boost';
import {
  createTwoTierAdapter,
  createCFCacheAdapter,
  createKVAdapter,
} from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  DB: D1Database;
  CACHE: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const adapter = new PrismaD1(env.DB);

    const cacheAdapter = createTwoTierAdapter({
      l1: createCFCacheAdapter({
        baseUrl: 'https://cache.your-domain.com',
      }),
      l2: createKVAdapter({ kv: env.CACHE }),
      l1TTLRatio: 0.1, // L1 TTL = 10% of L2 TTL
      promoteOnGet: true, // L2 hits get promoted to L1
    });

    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: cacheAdapter,
        config: cacheConfig,
        version: 'v1',
        accountTier: 'pro',
        executionCtx: ctx,
      }),
    );

    // Read path:
    // 1. Check L1 (Cache API, ~1ms, per-colo)
    // 2. Miss → Check L2 (KV, ~20ms, global)
    // 3. L2 hit → return + promote to L1 via waitUntil
    // 4. L2 miss → database query
  },
};

Level 5: Pro Tier with Tenant Isolation (Durable Objects)

For multi-tenant SaaS where tenant cache state must be isolated.

What you gain: Per-tenant Durable Objects, physical isolation of cache metadata, tenant-specific capacity.

wrangler.toml:

name = "my-app"
compatibility_date = "2024-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"

[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"

[[durable_objects.bindings]]
name = "EPOCH_COORDINATOR"
class_name = "EpochCoordinator"

[[migrations]]
tag = "v1"
new_classes = ["EpochCoordinator"]

src/coordinator.ts:

export { EpochCoordinator } from 'prisma-boost/coordinator';

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1';
import { createCacheExtension } from 'prisma-boost';
import { createKVAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  DB: D1Database;
  CACHE: KVNamespace;
  EPOCH_COORDINATOR: DurableObjectNamespace;
}

// Your auth middleware provides this
function getSession(request: Request): { userId: string; tenantId: string } {
  // ... your auth logic
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const session = getSession(request);
    const adapter = new PrismaD1(env.DB);

    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: createKVAdapter({ kv: env.CACHE }),
        config: cacheConfig,
        version: 'v1',
        accountTier: 'pro',
        executionCtx: ctx,
        coordinator: {
          namespace: env.EPOCH_COORDINATOR,
        },
        getPrivateContext: () => ({
          userId: session.userId,
          tenantId: session.tenantId,
        }),
      }),
    );

    // Tenant A's cache operations use DO "epoch-tenant-a"
    // Tenant B's cache operations use DO "epoch-tenant-b"
    // Complete isolation
  },
};

Level 6: Pro Tier with Multi-Tenant Sharding

For high-traffic tenants that need more than 1,000 req/s.

What you gain: Per-tenant shard configuration, hot tenant detection, capacity recommendations.

src/index.ts:

import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1';
import { createCacheExtension } from 'prisma-boost';
import { createKVAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from '../prisma/generated/cacheConfig';

interface Env {
  DB: D1Database;
  CACHE: KVNamespace;
  EPOCH_COORDINATOR: DurableObjectNamespace;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const session = getSession(request);
    const adapter = new PrismaD1(env.DB);

    const prisma = new PrismaClient({ adapter }).$extends(
      createCacheExtension({
        adapter: createKVAdapter({ kv: env.CACHE }),
        config: cacheConfig,
        version: 'v1',
        accountTier: 'pro',
        executionCtx: ctx,
        coordinator: {
          namespace: env.EPOCH_COORDINATOR,
          sharding: {
            defaultShards: 32, // 32k req/s for user/PK scopes
            publicShards: 4, // 4k req/s for public data
            tenantShards: {
              'acme-corp': 8, // 8k req/s dedicated
              'widgets-inc': 4, // 4k req/s dedicated
              // Other tenants get 1 DO each (1k req/s)
            },
          },
        },
        getPrivateContext: () => ({
          userId: session.userId,
          tenantId: session.tenantId,
        }),
      }),
    );
  },
};

Hot tenant detection (in admin/monitoring code):

import { PrivateShardRouter } from 'prisma-boost/coordinator';

// In your monitoring endpoint
const router = new PrivateShardRouter(shardingConfig);

const hotTenants = router.detectHotTenants(60_000);
// { 'acme-corp': 2847, 'startup-xyz': 1523 }

const recommended = router.recommendShardCount('startup-xyz');
// 2 (based on observed traffic)

// Add to config: tenantShards: { 'startup-xyz': 2 }

Remote KV Adapter (Node.js Development)

For local development or running Prisma in traditional Node.js servers while caching in Cloudflare KV.

When to use:

  • Local development without wrangler dev
  • CI/CD testing against real Cloudflare KV
  • Hybrid architectures (Node.js API server + Cloudflare cache)
  • E2E testing invalidation behavior

Limitations compared to Workers:

  • Higher latency (~50-200ms vs ~1-5ms in Workers)
  • No Cache API (L1) support
  • Requires Cloudflare API credentials
  • Subject to API rate limits

Setup:

1. Get Cloudflare credentials:

# Account ID from dashboard or:
wrangler whoami

# Create API token with KV read/write permissions:
# https://dash.cloudflare.com/profile/api-tokens
# Template: Edit Cloudflare Workers
# Permissions: Account.Workers KV Storage (Edit)

# Get KV namespace ID:
wrangler kv:namespace list

2. Environment variables (.env.local):

CF_ACCOUNT_ID=your-account-id
CF_NAMESPACE_ID=your-kv-namespace-id
CF_API_TOKEN=your-api-token
DATABASE_URL=file:./dev.db

3. Node.js application:

import { PrismaClient } from '@prisma/client';
import { createCacheExtension } from 'prisma-boost';
import { createRemoteCloudflareAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from './prisma/generated/cacheConfig';

const adapter = createRemoteCloudflareAdapter({
  accountId: process.env.CF_ACCOUNT_ID!,
  namespaceId: process.env.CF_NAMESPACE_ID!,
  apiToken: process.env.CF_API_TOKEN!,
  namespace: 'dev-cache', // Optional key prefix
  operationTimeout: 15000, // 15s timeout for API calls
  generatedConfig: cacheConfig,
  d1Binding: null, // Not needed for remote adapter
});

// Mock execution context for Node.js
const mockExecutionCtx = {
  waitUntil: (promise: Promise<any>) => {
    promise.catch((err) => console.error('Background task failed:', err));
  },
  passThroughOnException: () => {},
};

const prisma = new PrismaClient().$extends(
  createCacheExtension({
    adapter,
    executionCtx: mockExecutionCtx,
    version: 'v1',
    config: cacheConfig,
    accountTier: 'pro', // or 'free'
    invalidationPolicy: {
      strategy: 'epoch',
      allowIncomplete: true,
    },
  }),
);

// Use normally
const posts = await prisma.post.findMany();

// Manual invalidation works
await prisma.$invalidateCache('Post');

4. Testing with Vitest:

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { createCacheExtension } from 'prisma-boost';
import { createRemoteCloudflareAdapter } from 'prisma-boost/adapters';
import * as cacheConfig from './prisma/generated/cacheConfig';

describe('Cache invalidation', () => {
  let prisma: ReturnType<typeof createCachedClient>;

  function createCachedClient() {
    const baseClient = new PrismaClient();
    const adapter = createRemoteCloudflareAdapter({
      accountId: process.env.CF_ACCOUNT_ID!,
      namespaceId: process.env.CF_NAMESPACE_ID!,
      apiToken: process.env.CF_API_TOKEN!,
      namespace: 'test-cache',
      operationTimeout: 15000,
      generatedConfig: cacheConfig,
      d1Binding: null,
    });

    const mockExecutionCtx = {
      waitUntil: (promise: Promise<any>) => promise.catch(() => {}),
      passThroughOnException: () => {},
    };

    return baseClient.$extends(
      createCacheExtension({
        adapter,
        executionCtx: mockExecutionCtx,
        version: 'v1-test',
        config: cacheConfig,
        accountTier: 'pro',
        invalidationPolicy: { strategy: 'epoch' },
      }),
    );
  }

  beforeAll(() => {
    prisma = createCachedClient();
  });

  it('should invalidate cache on update', async () => {
    const user = await prisma.user.create({
      data: { email: '[email protected]', name: 'Test' },
    });

    // Prime cache
    const before = await prisma.user.findUnique({ where: { id: user.id } });
    expect(before?.name).toBe('Test');

    // Update
    await prisma.user.update({
      where: { id: user.id },
      data: { name: 'Updated' },
    });

    // Wait for KV propagation
    await new Promise((resolve) => setTimeout(resolve, 1000));

    // Should see updated data
    const after = await prisma.user.findUnique({ where: { id: user.id } });
    expect(after?.name).toBe('Updated');
  });
});

Performance characteristics:

Operation Workers (KV binding) Node.js (Remote API)
Cache read 1-5ms 50-200ms
Cache write 1-5ms 50-200ms
Epoch bump 1-5ms 50-200ms
Invalidation list 5-20ms 100-500ms

When NOT to use:

  • Production workloads (deploy to Workers instead)
  • High-throughput scenarios (>100 req/s)
  • Latency-sensitive applications (<100ms p99)

Best practices:

// Enable circuit breaker for API failures
const adapter = createRemoteCloudflareAdapter({
  // ... config
  enableCircuitBreaker: true, // Default: true
  operationTimeout: 10000, // Fail fast on slow API
});

// Use longer TTLs to reduce API calls
createCacheExtension({
  adapter,
  defaultTTL: 3600, // 1 hour vs 5 min in Workers
  // ...
});

// Batch cleanup in tests
afterAll(async () => {
  // Clean up test data
  const { keys } = await adapter.list('test-cache:', { limit: 1000 });
  if (keys.length > 0) {
    await adapter.deleteMany(keys);
  }
});

Field-Aware Invalidation (Pro Tier)

Skip invalidation when writes don't affect cached fields.

createCacheExtension({
  accountTier: 'pro',
  // ... other config
  fieldTracking: {
    Post: {
      enabled: true,
      neverInvalidateOn: ['viewCount', 'lastViewedAt', 'impressions'],
      alwaysInvalidateOn: ['status', 'publishedAt', 'deletedAt'],
    },
    User: {
      enabled: true,
      neverInvalidateOn: ['lastLoginAt', 'loginCount'],
    },
  },
});

How it works:

// Cache contains: { id: 1, title: 'Hello', content: '...' }
await prisma.post.update({
  where: { id: 1 },
  data: { viewCount: { increment: 1 } },
});
// viewCount is in neverInvalidateOn
// Cached fields don't include viewCount
// → Invalidation skipped

await prisma.post.update({
  where: { id: 1 },
  data: { status: 'archived' },
});
// status is in alwaysInvalidateOn
// → Invalidation triggered regardless of cached fields

Budget Management

Fallback strategies:

  • epoch-model: Bump the entire model's epoch (coarsest, safest)
  • epoch-prefix: Bump epoch for the specific prefix being invalidated
  • allow-incomplete: Stop and mark result as incomplete

Observing results:

createCacheExtension({
  // ... other config
  onInvalidate: (model, result) => {
    console.log({
      model,
      deleted: result.deleted,
      incomplete: result.incomplete,
      costEstimate: result.costEstimate,
      fallbackReason: result.details?.fallbackReason,
    });
  },
  onError: (error, operation) => {
    console.error(`Cache error in ${operation}:`, error);
  },
});

Performance Analytics & Auto-Tuning

Safe Auto-Caps (Budget Protection): Uses Cloudflare Analytics Engine invalidation telemetry to keep KV usage inside free-tier / budget constraints without overreacting to spikes:

  • Aggregates invalidation cost/ops per day (using _sample_interval weighting) to avoid single-run noise
  • Computes tail estimates from winsorized quantiles (outliers clipped) rather than raw P99 alone
  • Applies EMA smoothing over recent days to produce stable caps (tighten/relax gradually)
  • Uses confidence gating (minimum event count + consistency checks) before changing caps
  • Enforces floors/ceilings + safety buffers so caps can't collapse to unusable values
  • Falls back to safer modes under stress (e.g., epoch-only invalidation, then read-through only) to prevent quota exhaustion

Affinity-Aware Sharding (Coordination Hints): Learns which model mutations tend to invalidate other models and uses that as a hint to reduce cross-shard coordination:

  • Records pairwise edges (mutatedModel → targetModel) as individual events (not comma-joined lists)
  • Builds a time-decayed weighted graph from recent history (recent edges matter more than old ones)
  • Promotes only top-K high-confidence edges per model (ignores weak / noisy correlations)
  • Uses consistent hashing for baseline routing, then applies small, bounded affinity overrides to co-locate strongly coupled models when it reduces invalidation fan-out
  • Includes hot-shard safeguards (caps on override strength, backoff when a shard gets overloaded)
  • Always remains safe: if affinity hints don't help, routing behaves like standard sharding

Hit Rate Monitoring: Collects cache performance metrics per request:

  • Hit/miss counters aggregated by Prisma model
  • Read/write/invalidation latency tracking
  • Submits to Analytics Engine via ctx.waitUntil() (zero request latency)

Setup

1. Create Analytics Engine Datasets

wrangler analytics-engine create-dataset cacheInvalidationEvents
wrangler analytics-engine create-dataset cachePerformance

2. Bind in wrangler.toml

[[analytics_engine_datasets]]
binding = "CACHE_INVALIDATION_EVENTS"
dataset = "cacheInvalidationEvents"

[[analytics_engine_datasets]]
binding = "CACHE_ANALYTICS"
dataset = "cachePerformance"

3. Configure Extension

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const cached = await env.DB.$extends(
      createCacheExtension({
        adapter: cloudflareKVAdapter(env.CACHE),
        config: generatedConfig,
        version: 'v1',
        executionCtx: ctx,

        analytics: {
          dataset: env.CACHE_ANALYTICS,
          enabled: true,
        },
      }),
    );

    const result = await cached.user.findMany({});
    ctx.waitUntil(cached.$cleanup());
    return Response.json(result);
  },
};

4. Enable Auto-Caps (Optional)

Set credentials for generator to query production metrics:

# Required for auto-tuning
CF_ACCOUNT_ID=your-account-id
CF_API_TOKEN=your-api-token  # Analytics Engine read permission
CF_ANALYTICS_DATASET=cacheInvalidationEvents

# Optional: Monthly budget enforcement
CF_MAX_MONTHLY_BUDGET_USD=5.00  # Disable caching if exceeded

Generator queries Analytics Engine during prisma generate:

npx prisma generate

Budget Protection

If CF_MAX_MONTHLY_BUDGET_USD is set:

90% spent: Automatically degrades to epoch-only invalidation (no KV deletes)

WARN: Degrading to epoch-only: spent $4.50 of $5.00 monthly limit

100% spent: Disables cache invalidation entirely (all DB queries, no KV writes)

ERROR: Monthly budget EXCEEDED: $5.03 of $5.00 - DISABLING cache

Budget resets monthly. Spent amount persists in isolate state across requests.

Analytics Queries

Model hit rates:

SELECT
  model,
  SUM(modelHits) as hits,
  SUM(modelMisses) as misses,
  ROUND(AVG(modelHitRate) * 100, 1) as hit_rate_pct
FROM cachePerformance
WHERE timestamp > NOW() - INTERVAL '7' DAY
GROUP BY model
ORDER BY hits + misses DESC;

Invalidation costs:

SELECT
  DATE(timestamp) as day,
  SUM(double9) as total_cost_usd,
  SUM(double2) as rows_deleted,
  SUM(double5) as list_ops
FROM cacheInvalidationEvents
WHERE timestamp > NOW() - INTERVAL '30' DAY
GROUP BY day
ORDER BY day DESC;

Budget exceeded events:

SELECT
  index1 as model,
  SUM(CASE WHEN blob3 = '1' THEN _sample_interval ELSE 0 END) as exceeded,
  SUM(_sample_interval) as total,
  ROUND(exceeded * 100.0 / total, 1) as exceeded_pct
FROM cacheInvalidationEvents
WHERE timestamp > NOW() - INTERVAL '7' DAY
GROUP BY model
HAVING exceeded_pct > 5;

Limitations

  • Auto-caps require 25+ invalidation events over 30 days minimum
  • Affinity seeding requires private coordinator (Durable Objects)
  • Monthly budget is approximate (30-day rolling window)
  • Hit rate tracking adds ~50 bytes per request to Analytics Engine

No additional KV operations - metrics submit via waitUntil() after response sent.


Automatic Behaviors

Things that happen without configuration:

Scenario What happens
Query needs PK for invalidation but user only selected other fields PK fields silently added to select
100 concurrent identical queries Deduplicated to 1 database call
Cache backend failing Circuit breaker opens, requests go direct to DB
Cached value exceeds size limit Skipped, not cached
Epoch read times out Retry, then treat as epoch 0 (cache miss)
Background cache write fails Logged, response unaffected
Tenant ID is reserved word Rejected with error
FK changes from Post:42 to Post:43 Both Post:42 and Post:43 invalidated

Limitations

  • Connection pooling — Use Hyperdrive for external databases
  • Non-Cloudflare deployment — Requires KV and Cache API
  • Query optimization — Caches results, doesn't optimize queries
  • Free tier consistency — No read-your-writes or monotonic reads

Installation

npm install prisma-boost
generator cache {
  provider = "prisma-boost"
  output   = "./generated"
}
npx prisma generate

Top categories

Loading Svelte Themes