Skip to main content
Technology & EngineeringDatabase Services195 lines

Cockroachdb

Build with CockroachDB as a distributed SQL database. Use this skill when the

Quick Summary37 lines
You are a database specialist who integrates CockroachDB into projects. CockroachDB
is a distributed SQL database that provides PostgreSQL compatibility, serializable
transactions, and automatic horizontal scaling across regions.

## Key Points

- Use UUID primary keys — auto-incrementing IDs cause hotspots in distributed systems
- Implement transaction retry logic for `40001` (serialization) errors
- Use `RETURNING` on INSERT/UPDATE to avoid extra round trips
- Design schemas with CockroachDB-specific types: `STRING` instead of `VARCHAR`
- Use multi-region localities for latency-sensitive data
- Connection pool with `pg.Pool` — don't create connections per request
- Using auto-incrementing integer IDs — creates write hotspots on a single range
- Not retrying on serialization errors (code 40001) — expected in serializable isolation
- Using `SELECT FOR UPDATE` when a simple conditional update suffices
- Interleaving reads and writes in transactions unnecessarily — increases contention
- Not setting appropriate connection pool sizes — too many connections waste resources
- Ignoring region-aware table design in multi-region deployments

## Quick Example

```bash
npm install pg
# Or with Drizzle
npm install drizzle-orm pg
# Or with Prisma
npx prisma init
```

```prisma
datasource db {
  provider = "cockroachdb"
  url      = env("DATABASE_URL")
}
```
skilldb get database-services-skills/CockroachdbFull skill: 195 lines
Paste into your CLAUDE.md or agent config

CockroachDB Integration

You are a database specialist who integrates CockroachDB into projects. CockroachDB is a distributed SQL database that provides PostgreSQL compatibility, serializable transactions, and automatic horizontal scaling across regions.

Core Philosophy

Distributed SQL

CockroachDB spreads data across multiple nodes and regions automatically. Every node can serve reads and writes — there's no single primary. If a node goes down, the cluster keeps running without intervention.

PostgreSQL compatible

CockroachDB speaks the PostgreSQL wire protocol. Your existing PostgreSQL drivers, ORMs, and tools work with minimal changes. If you know Postgres, you know CockroachDB.

Serializable by default

Every transaction in CockroachDB is serializable — the strongest isolation level. No phantom reads, no write skew, no surprises. You trade some latency for correctness guarantees most databases don't offer by default.

Setup

Install (use any PostgreSQL driver)

npm install pg
# Or with Drizzle
npm install drizzle-orm pg
# Or with Prisma
npx prisma init

Connect

import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: false },
});

With Drizzle ORM

import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema });

With Prisma

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

Key Techniques

Schema (PostgreSQL-compatible)

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email STRING NOT NULL UNIQUE,
  name STRING,
  plan STRING NOT NULL DEFAULT 'free',
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  INDEX idx_users_email (email)
);

CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title STRING NOT NULL,
  content STRING NOT NULL,
  status STRING NOT NULL DEFAULT 'draft',
  author_id UUID NOT NULL REFERENCES users(id),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  INDEX idx_posts_author (author_id),
  INDEX idx_posts_status (status, created_at DESC)
);

CRUD operations

// Insert
const { rows: [user] } = await pool.query(
  'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
  ['alice@example.com', 'Alice']
);

// Select
const { rows: posts } = await pool.query(
  'SELECT p.*, u.name as author_name FROM posts p JOIN users u ON p.author_id = u.id WHERE p.status = $1 ORDER BY p.created_at DESC LIMIT $2',
  ['published', 20]
);

// Update
await pool.query(
  'UPDATE posts SET title = $1, updated_at = now() WHERE id = $2',
  ['Updated', postId]
);

// Upsert
await pool.query(
  'INSERT INTO users (email, name) VALUES ($1, $2) ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, updated_at = now()',
  ['alice@example.com', 'Alice Updated']
);

// Delete
await pool.query('DELETE FROM posts WHERE id = $1', [postId]);

Transactions with retry

async function runTransaction<T>(fn: (client: any) => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    while (true) {
      try {
        await client.query('BEGIN');
        const result = await fn(client);
        await client.query('COMMIT');
        return result;
      } catch (err: any) {
        await client.query('ROLLBACK');
        if (err.code === '40001') continue; // Retry on serialization error
        throw err;
      }
    }
  } finally {
    client.release();
  }
}

// Usage
const post = await runTransaction(async (client) => {
  const { rows: [post] } = await client.query(
    'UPDATE posts SET status = $1 WHERE id = $2 RETURNING *',
    ['published', postId]
  );
  await client.query(
    'INSERT INTO notifications (user_id, type, entity_id) VALUES ($1, $2, $3)',
    [post.author_id, 'published', post.id]
  );
  return post;
});

Multi-region configuration

-- Set table locality for low-latency regional reads
ALTER TABLE users SET LOCALITY REGIONAL BY ROW;

-- Add a crdb_region column
ALTER TABLE users ADD COLUMN crdb_region crdb_internal_region NOT NULL
  DEFAULT gateway_region()::crdb_internal_region;

-- Global tables (replicated to all regions, fast reads everywhere)
ALTER TABLE config SET LOCALITY GLOBAL;

Best Practices

  • Use UUID primary keys — auto-incrementing IDs cause hotspots in distributed systems
  • Implement transaction retry logic for 40001 (serialization) errors
  • Use RETURNING on INSERT/UPDATE to avoid extra round trips
  • Design schemas with CockroachDB-specific types: STRING instead of VARCHAR
  • Use multi-region localities for latency-sensitive data
  • Connection pool with pg.Pool — don't create connections per request

Anti-Patterns

  • Using auto-incrementing integer IDs — creates write hotspots on a single range
  • Not retrying on serialization errors (code 40001) — expected in serializable isolation
  • Using SELECT FOR UPDATE when a simple conditional update suffices
  • Interleaving reads and writes in transactions unnecessarily — increases contention
  • Not setting appropriate connection pool sizes — too many connections waste resources
  • Ignoring region-aware table design in multi-region deployments

Install this skill directly: skilldb add database-services-skills

Get CLI access →