CockroachDB
Distributed SQL Database
PostgreSQL 호환 분산 SQL 데이터베이스입니다. 자동 샤딩, 글로벌 분산, Serializable 격리 수준을 기본 제공하여 "생존 가능한(Survivable)" 데이터베이스를 구현합니다.
Distributed SQL Database
PostgreSQL 호환 분산 SQL 데이터베이스입니다. 자동 샤딩, 글로벌 분산, Serializable 격리 수준을 기본 제공하여 "생존 가능한(Survivable)" 데이터베이스를 구현합니다.
CockroachDB는 Google Spanner에서 영감을 받아 개발된 분산 SQL 데이터베이스입니다. 이름처럼 "바퀴벌레"의 생존력을 목표로, 노드 장애, 데이터센터 장애, 심지어 리전 장애에도 데이터를 보존하고 서비스를 지속합니다.
자동 샤딩과 리밸런싱: 데이터는 Range라는 단위로 자동 분할되어 여러 노드에 분산됩니다. 노드가 추가되거나 제거되면 데이터가 자동으로 리밸런싱됩니다. 개발자는 샤딩을 직접 관리할 필요가 없습니다.
강력한 일관성(Strong Consistency): CockroachDB는 기본적으로 Serializable 격리 수준을 제공합니다. 분산 환경에서도 단일 노드와 동일한 ACID 보장을 받을 수 있어 금융, 결제 등 정합성이 중요한 서비스에 적합합니다.
PostgreSQL 호환: PostgreSQL 와이어 프로토콜을 지원하여 기존 PostgreSQL 드라이버, ORM(Prisma, Sequelize, TypeORM 등)을 그대로 사용할 수 있습니다. 마이그레이션 비용이 낮습니다.
멀티 리전 기능: 데이터를 특정 리전에 고정(Region Survival), 글로벌 테이블(Global Table)로 전 세계에서 빠른 읽기, 사용자 위치 기반 데이터 배치 등 지오 분산(Geo-distribution) 기능을 제공합니다.
-- CockroachDB SQL 예제 (PostgreSQL 호환)
-- ============================================
-- 데이터베이스 및 테이블 생성
-- ============================================
CREATE DATABASE ecommerce;
USE ecommerce;
-- 사용자 테이블 (UUID 기본키 권장)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING UNIQUE NOT NULL,
name STRING NOT NULL,
region STRING DEFAULT 'ap-northeast-2',
created_at TIMESTAMPTZ DEFAULT now()
);
-- 주문 테이블 (해시 샤딩 인덱스)
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
total_amount DECIMAL(15,2) NOT NULL,
status STRING DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT now(),
-- 샤딩 힌트: user_id로 데이터 분산
INDEX orders_by_user (user_id) USING HASH
);
-- ============================================
-- 멀티 리전 설정
-- ============================================
-- 데이터베이스 리전 설정
ALTER DATABASE ecommerce PRIMARY REGION "ap-northeast-2";
ALTER DATABASE ecommerce ADD REGION "us-east-1";
ALTER DATABASE ecommerce ADD REGION "eu-west-1";
-- 리전별 생존 설정 (리전 장애에도 복구)
ALTER DATABASE ecommerce SURVIVE REGION FAILURE;
-- 글로벌 테이블 (전 세계에서 빠른 읽기)
CREATE TABLE config (
key STRING PRIMARY KEY,
value JSONB
) LOCALITY GLOBAL;
-- 리전 고정 테이블 (GDPR 준수 - 유럽 데이터는 유럽에)
CREATE TABLE eu_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING,
data JSONB
) LOCALITY REGIONAL BY TABLE IN "eu-west-1";
-- ============================================
-- 트랜잭션 (Serializable 기본)
-- ============================================
BEGIN;
-- 계좌 이체 (분산 트랜잭션도 ACID 보장)
UPDATE accounts SET balance = balance - 100
WHERE id = 'acc-001';
UPDATE accounts SET balance = balance + 100
WHERE id = 'acc-002';
-- 이체 기록
INSERT INTO transfers (from_account, to_account, amount)
VALUES ('acc-001', 'acc-002', 100);
COMMIT;
-- ============================================
-- 유용한 시스템 쿼리
-- ============================================
-- 클러스터 노드 상태 확인
SELECT node_id, address, is_live, locality
FROM crdb_internal.gossip_nodes;
-- Range 분포 확인
SELECT * FROM [SHOW RANGES FROM TABLE orders];
-- 실행 계획 (분산 쿼리 확인)
EXPLAIN ANALYZE SELECT * FROM orders
WHERE user_id = 'some-uuid';
-- 느린 쿼리 확인
SELECT * FROM crdb_internal.node_queries
ORDER BY elapsed DESC LIMIT 10;
// CockroachDB + Node.js (pg 드라이버)
const { Pool } = require('pg');
// CockroachDB 연결 (PostgreSQL 호환)
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
// CockroachDB Serverless 연결 시
ssl: {
rejectUnauthorized: true,
},
max: 20, // 커넥션 풀 크기
});
// ============================================
// 기본 CRUD
// ============================================
class OrderService {
// 주문 생성
async createOrder(userId, items) {
const client = await pool.connect();
try {
await client.query('BEGIN');
// 총액 계산
const total = items.reduce((sum, item) =>
sum + item.price * item.quantity, 0);
// 주문 생성
const { rows: [order] } = await client.query(`
INSERT INTO orders (user_id, total_amount, status)
VALUES ($1, $2, 'pending')
RETURNING *
`, [userId, total]);
// 주문 아이템 생성
for (const item of items) {
await client.query(`
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES ($1, $2, $3, $4)
`, [order.id, item.productId, item.quantity, item.price]);
}
// 재고 차감
for (const item of items) {
const { rowCount } = await client.query(`
UPDATE products
SET stock = stock - $1
WHERE id = $2 AND stock >= $1
`, [item.quantity, item.productId]);
if (rowCount === 0) {
throw new Error(`재고 부족: ${item.productId}`);
}
}
await client.query('COMMIT');
return order;
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
// 사용자별 주문 조회
async getOrdersByUser(userId, limit = 10) {
const { rows } = await pool.query(`
SELECT o.*,
json_agg(json_build_object(
'product_id', oi.product_id,
'quantity', oi.quantity,
'price', oi.price
)) as items
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.user_id = $1
GROUP BY o.id
ORDER BY o.created_at DESC
LIMIT $2
`, [userId, limit]);
return rows;
}
}
// ============================================
// 재시도 로직 (분산 DB 필수)
// ============================================
async function executeWithRetry(fn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
// CockroachDB 직렬화 실패 에러 (40001)
if (error.code === '40001' && attempt < maxRetries) {
console.log(`재시도 ${attempt}/${maxRetries}: 트랜잭션 충돌`);
// 지수 백오프
await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt)));
continue;
}
throw error;
}
}
}
// 사용 예시
const orderService = new OrderService();
await executeWithRetry(async () => {
return orderService.createOrder('user-123', [
{ productId: 'prod-1', quantity: 2, price: 10000 },
{ productId: 'prod-2', quantity: 1, price: 25000 }
]);
});
// ============================================
// Prisma에서 CockroachDB 사용
// ============================================
// schema.prisma
/*
datasource db {
provider = "cockroachdb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
email String @unique
orders Order[]
}
*/
# CockroachDB CLI 및 운영 명령어
# ============================================
# 설치 및 로컬 실행
# ============================================
# macOS 설치
brew install cockroachdb/tap/cockroach
# 로컬 단일 노드 실행 (개발용)
cockroach start-single-node --insecure --listen-addr=localhost:26257
# SQL 쉘 접속
cockroach sql --insecure --host=localhost:26257
# ============================================
# 클러스터 구성 (프로덕션)
# ============================================
# 노드 1 시작
cockroach start \
--insecure \
--store=node1 \
--listen-addr=localhost:26257 \
--http-addr=localhost:8080 \
--join=localhost:26257,localhost:26258,localhost:26259
# 노드 2 시작
cockroach start \
--insecure \
--store=node2 \
--listen-addr=localhost:26258 \
--http-addr=localhost:8081 \
--join=localhost:26257,localhost:26258,localhost:26259
# 노드 3 시작
cockroach start \
--insecure \
--store=node3 \
--listen-addr=localhost:26259 \
--http-addr=localhost:8082 \
--join=localhost:26257,localhost:26258,localhost:26259
# 클러스터 초기화 (최초 1회)
cockroach init --insecure --host=localhost:26257
# ============================================
# 클러스터 관리
# ============================================
# 클러스터 상태 확인
cockroach node status --insecure --host=localhost:26257
# 노드 decommission (제거)
cockroach node decommission 3 --insecure --host=localhost:26257
# DB Admin UI 접속 (브라우저)
# http://localhost:8080
# ============================================
# 백업 및 복구
# ============================================
# 전체 클러스터 백업 (S3)
BACKUP INTO 's3://bucket/backup?AUTH=implicit'
AS OF SYSTEM TIME '-10s';
# 특정 데이터베이스 백업
BACKUP DATABASE ecommerce INTO 's3://bucket/backup';
# 복구
RESTORE DATABASE ecommerce FROM LATEST IN 's3://bucket/backup';
# Point-in-Time 복구
RESTORE DATABASE ecommerce FROM LATEST IN 's3://bucket/backup'
AS OF SYSTEM TIME '2024-01-15 10:00:00';
# ============================================
# CockroachDB Cloud (서버리스)
# ============================================
# CLI로 클라우드 접속
cockroach sql --url "postgresql://user:password@host:26257/defaultdb?sslmode=verify-full"
# 연결 문자열 예시 (Node.js)
# DATABASE_URL="postgresql://user:pass@free-tier.gcp-asia-southeast1.cockroachlabs.cloud:26257/defaultdb?sslmode=verify-full"
# ============================================
# 성능 진단
# ============================================
# 실행 중인 쿼리 확인
cockroach sql --insecure -e "SELECT * FROM [SHOW QUERIES]"
# 세션 확인
cockroach sql --insecure -e "SELECT * FROM [SHOW SESSIONS]"
# 통계 수집
cockroach sql --insecure -e "ANALYZE orders"
"글로벌 서비스라 멀티 리전 필수인데요. CockroachDB 쓰면 PostgreSQL 문법 그대로 쓰면서 자동 샤딩, 리전 생존까지 됩니다. Spanner 쓰기엔 벤더 락인이 걱정되는데, CockroachDB는 오픈소스 코어도 있어요."
"PostgreSQL에서 CockroachDB로 옮기는 건 생각보다 쉬워요. pg_dump로 스키마 뽑고 IMPORT로 넣으면 됩니다. 다만 시퀀스 대신 UUID 쓰는 걸 권장하고, 일부 PostgreSQL 전용 기능은 호환 안 될 수 있어서 미리 체크해야 해요."
"노드 하나 죽었는데 서비스는 정상이에요. CockroachDB가 자동으로 다른 노드로 리더 선출하고 트래픽 라우팅했거든요. 다만 해당 노드 데이터가 언더 레플리케이션 상태니까 빨리 복구하거나 새 노드 투입해야 합니다."
AUTO_INCREMENT나 SERIAL 대신 UUID를 권장합니다. 순차 ID는 핫스팟을 유발하여 특정 노드에 쓰기가 집중됩니다.
Serializable 격리로 인해 트랜잭션 충돌(40001 에러)이 발생할 수 있습니다. 애플리케이션에서 재시도 로직을 반드시 구현하세요.
멀티 리전 환경에서 리전 간 쿼리는 네트워크 지연이 발생합니다. REGIONAL BY ROW로 데이터 지역성을 최적화하세요.
UUID 기본키 사용, 해시 인덱스로 핫스팟 방지, 재시도 로직 구현, 리전 근접 데이터 배치, 정기적 ANALYZE 실행.