Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Audit and fix Convex performance issues: hot reads, OCC conflicts, subscription cost, and function limits.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/occ-conflicts.md
1# OCC Conflict Resolution23Use these rules when insights, logs, or dashboard health show OCC (Optimistic4Concurrency Control) conflicts, mutation retries, or write contention on hot5tables.67## Core Principle89Convex uses optimistic concurrency control. When two transactions read or write10overlapping data, one succeeds and the other retries automatically. High11contention means wasted work and increased latency.1213## Symptoms1415- OCC conflict errors in deployment logs or health page16- Mutations retrying multiple times before succeeding17- User-visible latency spikes on write-heavy pages18- `npx convex insights --details` showing high conflict rates1920## Common Causes2122### Hot documents2324Multiple mutations writing to the same document concurrently. Classic examples:25a global counter, a shared settings row, or a "last updated" timestamp on a26parent record.2728### Broad read sets causing false conflicts2930A query that scans a large table range creates a broad read set. If any write31touches that range, the query's transaction conflicts even if the specific32document the query cared about was not modified.3334### Fan-out from triggers or cascading writes3536A single user action triggers multiple mutations that all touch related37documents. Each mutation competes with the others.3839Database triggers (e.g. from `convex-helpers`) run inside the same transaction40as the mutation that caused them. If a trigger does heavy work, reads extra41tables, or writes to many documents, it extends the transaction's read/write set42and increases the window for conflicts. Keep trigger logic minimal, or move43expensive derived work to a scheduled function.4445### Write-then-read chains4647A mutation writes a document, then a reactive query re-reads it, then another48mutation writes it again. Under load, these chains stack up.4950## Fix Order5152### 1. Reduce read set size5354Narrower reads mean fewer false conflicts.5556```ts57// Bad: broad scan creates a wide conflict surface58const allTasks = await ctx.db.query("tasks").collect();59const mine = allTasks.filter((t) => t.ownerId === userId);60```6162```ts63// Good: indexed query touches only relevant documents64const mine = await ctx.db65.query("tasks")66.withIndex("by_owner", (q) => q.eq("ownerId", userId))67.collect();68```6970### 2. Split hot documents7172When many writers target the same document, split the contention point.7374```ts75// Bad: every vote increments the same counter document76const counter = await ctx.db.get(pollCounterId);77await ctx.db.patch(pollCounterId, { count: counter!.count + 1 });78```7980```ts81// Good: shard the counter across multiple documents, aggregate on read82const shardIndex = Math.floor(Math.random() * SHARD_COUNT);83const shardId = shardIds[shardIndex];84const shard = await ctx.db.get(shardId);85await ctx.db.patch(shardId, { count: shard!.count + 1 });86```8788Aggregate the shards in a query or scheduled job when you need the total.8990### 3. Move non-critical work to scheduled functions9192If a mutation does primary work plus secondary bookkeeping (analytics,93non-critical notifications, cache warming), the bookkeeping extends the94transaction's lifetime and read/write set.9596```ts97// Bad: canonical write and derived work happen in the same transaction98await ctx.db.patch(userId, { name: args.name });99await ctx.db.insert("userUpdateAnalytics", {100userId,101kind: "name_changed",102name: args.name,103});104```105106```ts107// Good: keep the primary write small, defer the analytics work108await ctx.db.patch(userId, { name: args.name });109await ctx.scheduler.runAfter(0, internal.users.recordNameChangeAnalytics, {110userId,111name: args.name,112});113```114115### 4. Combine competing writes116117If two mutations must update the same document atomically, consider whether they118can be combined into a single mutation call from the client, reducing round119trips and conflict windows.120121Do not introduce artificial locks or queues unless the above steps have been122tried first.123124## Related: Invalidation Scope125126Splitting hot documents also reduces subscription invalidation, not just OCC127contention. If a document is written frequently and read by many queries, those128queries re-run on every write even when the fields they care about have not129changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated130fields") for that pattern.131132## Verification1331341. OCC conflict rate has dropped in insights or dashboard1352. Mutation latency is lower and more consistent1363. No data correctness regressions from splitting or scheduling changes1374. Sibling writers to the same hot documents were fixed consistently138