CockroachDB

分散型SQLデータベース。強一貫性とACID特性を保ちながらグローバルスケール対応。PostgreSQL互換のSQL API提供。

データベースサーバー分散データベースSQLACID高可用性クラウドネイティブPostgreSQL互換自動スケーリング

データベースサーバー

CockroachDB

概要

CockroachDBは、クラウド向けに設計された分散SQLデータベースです。ゴキブリ(Cockroach)の名前の通り、障害に対する耐性と強さを持つ設計が特徴で、PostgreSQL互換のSQL APIを提供しながら、地理的に分散したクラスターでもACIDトランザクションを保証します。自動シャーディング、自動複製、自動修復機能により、手動での運用負荷を大幅に削減し、従来のRDBMSの信頼性とNoSQLの拡張性を両立させています。Cockroach Labsによって開発され、GoogleのSpannerにインスパイアされたアーキテクチャにより、世界規模のアプリケーションに対応できる分散データベースソリューションを提供します。

詳細

CockroachDB 2025年版は、数年間の継続的改良により、エンタープライズグレードの分散データベースとして成熟しています。最新のバージョンでは、Multi-Region SQLによる地理的分散の最適化、Serverless(CockroachDB Serverless)による自動スケーリング機能、Vector Indexingによる機械学習ワークロードサポートが強化されています。Raftコンセンサスアルゴリズムによる一貫性保証、Range分散による自動シャーディング、Gossipプロトコルによるノード間通信、強一貫性と地理的分散を両立するTrueTimeライクなハイブリッド論理クロックなど、分散システムの複雑な課題を透明に解決します。PostgreSQL wire protocolとの互換性により、既存のPostgreSQLアプリケーションを最小限の変更で移行可能です。

主な特徴

  • 強い一貫性: 地理的分散環境でもACIDトランザクションを完全保証
  • 自動スケーリング: データ量・負荷に応じたノードの自動追加・削除
  • PostgreSQL互換: 既存のPostgreSQLアプリケーションとの高い互換性
  • 自動運用: シャーディング、複製、障害復旧の自動化
  • マルチリージョン対応: 世界規模のデータセンター分散配置
  • ゼロダウンタイム: ローリングアップグレードと障害時自動切り替え

メリット・デメリット

メリット

  • 分散環境でも強一貫性を保ちながら水平スケーリングが可能
  • PostgreSQL互換により既存アプリケーションの移行が容易
  • 自動運用機能により大幅な運用負荷削減とDBA不要の実現
  • 障害に対する自動復旧とゼロダウンタイムアップグレード
  • クラウドネイティブ設計によりKubernetes環境での完全統合
  • Serverlessモードによる使用量ベースの柔軟な課金体系

デメリット

  • 複雑な分散システムのため学習コストと理解に時間が必要
  • 地理的に分散したトランザクションではレイテンシーが増加する可能性
  • PostgreSQL完全互換ではなく一部の機能制限や動作差異
  • 分散オーバーヘッドにより単一ノードPostgreSQLより性能劣る場合
  • エンタープライズ機能は商用ライセンス必須で高コスト
  • 新しい技術のため運用ノウハウが限られエコシステムが発展途上

参考ページ

書き方の例

インストールと基本セットアップ

# CockroachDBバイナリのダウンロード(Linux)
curl https://binaries.cockroachdb.com/cockroach-v24.2.0.linux-amd64.tgz | tar -xz
sudo cp -i cockroach-v24.2.0.linux-amd64/cockroach /usr/local/bin/

# macOSでのHomebrew経由インストール
brew install cockroachdb/tap/cockroach

# Dockerでの実行
docker pull cockroachdb/cockroach:v24.2.0
docker run -d \
  --name=cockroach-node1 \
  --hostname=cockroach-node1 \
  -p 26257:26257 -p 8080:8080 \
  -v cockroach-data:/cockroach/cockroach-data \
  cockroachdb/cockroach:v24.2.0 start-single-node \
  --insecure

# Docker Composeでの3ノードクラスター
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  cockroach-node1:
    image: cockroachdb/cockroach:v24.2.0
    command: start --insecure --join=cockroach-node1,cockroach-node2,cockroach-node3
    environment:
      - COCKROACH_CHANNEL=kubernetes-multiregion
    ports:
      - "26257:26257"
      - "8080:8080"
    volumes:
      - cockroach-data1:/cockroach/cockroach-data

  cockroach-node2:
    image: cockroachdb/cockroach:v24.2.0
    command: start --insecure --join=cockroach-node1,cockroach-node2,cockroach-node3
    environment:
      - COCKROACH_CHANNEL=kubernetes-multiregion
    volumes:
      - cockroach-data2:/cockroach/cockroach-data
    depends_on:
      - cockroach-node1

  cockroach-node3:
    image: cockroachdb/cockroach:v24.2.0
    command: start --insecure --join=cockroach-node1,cockroach-node2,cockroach-node3
    environment:
      - COCKROACH_CHANNEL=kubernetes-multiregion
    volumes:
      - cockroach-data3:/cockroach/cockroach-data
    depends_on:
      - cockroach-node1

volumes:
  cockroach-data1:
  cockroach-data2:
  cockroach-data3:
EOF

docker-compose up -d

# クラスター初期化
cockroach init --insecure --host=localhost:26257

# 管理UI確認
# http://localhost:8080 にアクセス

# CLIでの接続
cockroach sql --insecure --host=localhost:26257

基本的なSQL操作とデータベース管理

-- データベース作成
CREATE DATABASE company;
USE company;

-- ユーザーテーブル作成
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    created_at TIMESTAMP DEFAULT now(),
    updated_at TIMESTAMP DEFAULT now(),
    is_active BOOLEAN DEFAULT true
);

-- 部署テーブル作成
CREATE TABLE departments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) UNIQUE NOT NULL,
    description TEXT,
    manager_id UUID REFERENCES users(id),
    created_at TIMESTAMP DEFAULT now()
);

-- 従業員テーブル作成(外部キー制約付き)
CREATE TABLE employees (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    department_id UUID REFERENCES departments(id),
    employee_id VARCHAR(20) UNIQUE NOT NULL,
    position VARCHAR(100),
    salary DECIMAL(12,2),
    hire_date DATE,
    status VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT now()
);

-- インデックス作成
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_employees_dept ON employees(department_id);
CREATE INDEX idx_employees_status ON employees(status);

-- データ挿入
INSERT INTO users (username, email, password_hash, first_name, last_name) VALUES
('john_doe', '[email protected]', 'hashed_password_1', 'John', 'Doe'),
('jane_smith', '[email protected]', 'hashed_password_2', 'Jane', 'Smith'),
('bob_wilson', '[email protected]', 'hashed_password_3', 'Bob', 'Wilson');

INSERT INTO departments (name, description) VALUES
('Engineering', 'Software development and technical operations'),
('Sales', 'Customer acquisition and revenue generation'),
('Marketing', 'Brand promotion and customer engagement');

-- 複雑なクエリ例
-- 部署別従業員数と平均給与
SELECT 
    d.name as department_name,
    COUNT(e.id) as employee_count,
    AVG(e.salary) as avg_salary,
    MIN(e.hire_date) as earliest_hire,
    MAX(e.hire_date) as latest_hire
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
ORDER BY employee_count DESC;

-- ウィンドウ関数を使用した給与ランキング
SELECT 
    u.first_name || ' ' || u.last_name as full_name,
    d.name as department,
    e.salary,
    RANK() OVER (PARTITION BY d.name ORDER BY e.salary DESC) as salary_rank,
    DENSE_RANK() OVER (ORDER BY e.salary DESC) as overall_rank
FROM employees e
JOIN users u ON e.user_id = u.id
JOIN departments d ON e.department_id = d.id
WHERE e.status = 'active'
ORDER BY e.salary DESC;

-- JSON操作例
CREATE TABLE user_profiles (
    user_id UUID PRIMARY KEY REFERENCES users(id),
    profile_data JSONB,
    preferences JSONB DEFAULT '{}',
    created_at TIMESTAMP DEFAULT now()
);

INSERT INTO user_profiles (user_id, profile_data, preferences) VALUES
(
    (SELECT id FROM users WHERE username = 'john_doe'), 
    '{"skills": ["Python", "Go", "SQL"], "experience_years": 5, "certifications": ["AWS", "Kubernetes"]}',
    '{"theme": "dark", "notifications": true, "language": "ja"}'
);

-- JSONB操作
SELECT 
    u.username,
    up.profile_data->'skills' as skills,
    up.profile_data->>'experience_years' as experience,
    up.preferences->>'theme' as preferred_theme
FROM users u
JOIN user_profiles up ON u.id = up.user_id
WHERE up.profile_data ? 'skills'
    AND up.profile_data->'skills' ? 'Python';

クラスタリングとレプリケーション設定

-- クラスター情報確認
SHOW CLUSTER SETTING cluster.organization;
SHOW CLUSTER SETTING version;

-- レプリケーション設定
-- デフォルト3レプリカから5レプリカに変更
ALTER RANGE default CONFIGURE ZONE USING num_replicas = 5;

-- 特定データベースのレプリケーション設定
ALTER DATABASE company CONFIGURE ZONE USING num_replicas = 3, gc.ttlseconds = 86400;

-- 特定テーブルのレプリケーション設定
ALTER TABLE employees CONFIGURE ZONE USING 
    num_replicas = 5,
    constraints = '[+region=us-east-1]',
    lease_preferences = '[[+region=us-east-1]]';

-- マルチリージョン設定
-- リージョンをクラスターに追加
SET CLUSTER SETTING cluster.organization = 'Company Inc.';

-- データベースをマルチリージョンに設定
ALTER DATABASE company SET PRIMARY REGION "us-east-1";
ALTER DATABASE company ADD REGION "us-west-1";
ALTER DATABASE company ADD REGION "europe-west1";

-- テーブルの地理的分散設定
ALTER TABLE users SET LOCALITY GLOBAL;
ALTER TABLE departments SET LOCALITY REGIONAL BY TABLE IN "us-east-1";
ALTER TABLE employees SET LOCALITY REGIONAL BY ROW AS region;

-- パーティション設定例
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    customer_id UUID,
    region VARCHAR(20),
    order_date DATE,
    total_amount DECIMAL(12,2),
    status VARCHAR(20)
) PARTITION BY LIST (region) (
    PARTITION us_east VALUES IN ('us-east-1'),
    PARTITION us_west VALUES IN ('us-west-1'),
    PARTITION europe VALUES IN ('europe-west1')
);

-- パーティション別制約設定
ALTER PARTITION us_east OF TABLE orders 
    CONFIGURE ZONE USING constraints = '[+region=us-east-1]';
ALTER PARTITION us_west OF TABLE orders 
    CONFIGURE ZONE USING constraints = '[+region=us-west-1]';
ALTER PARTITION europe OF TABLE orders 
    CONFIGURE ZONE USING constraints = '[+region=europe-west1]';

パフォーマンス最適化とモニタリング

-- クラスター設定の最適化
SET CLUSTER SETTING sql.stats.automatic_collection.enabled = true;
SET CLUSTER SETTING sql.stats.histogram_collection.enabled = true;
SET CLUSTER SETTING kv.range_merge.queue_enabled = true;
SET CLUSTER SETTING kv.raft.command.max_size = '64MiB';

-- クエリ実行計画の確認
EXPLAIN (ANALYZE, DISTSQL) 
SELECT d.name, COUNT(*) as emp_count, AVG(e.salary) as avg_salary
FROM departments d
JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name;

-- スロークエリの確認
SELECT 
    application_name,
    query,
    exec_count,
    mean_exec_time,
    mean_rows,
    overhead_latency
FROM crdb_internal.statement_statistics
WHERE mean_exec_time > INTERVAL '100ms'
ORDER BY mean_exec_time DESC
LIMIT 10;

-- インデックス使用状況の確認
SELECT 
    table_name,
    index_name,
    total_reads,
    last_read
FROM crdb_internal.index_usage_statistics
WHERE database_name = 'company'
ORDER BY total_reads DESC;

-- トランザクション競合の確認
SELECT * FROM crdb_internal.cluster_contended_tables;
SELECT * FROM crdb_internal.cluster_contended_indexes;

-- レンジ分散状況確認
SELECT 
    range_id,
    start_key,
    end_key,
    replicas,
    lease_holder
FROM crdb_internal.ranges
WHERE database_name = 'company'
ORDER BY start_key;

-- ノード別負荷確認
SELECT 
    node_id,
    store_id,
    capacity,
    available,
    used,
    logical_bytes,
    range_count
FROM crdb_internal.kv_store_status;

-- メモリ使用量最適化
SET CLUSTER SETTING sql.distsql.temp_storage.workmem = '64MiB';
SET CLUSTER SETTING kv.bulk_io_write.concurrent_addsstable_requests = 5;
SET CLUSTER SETTING kv.bulk_io_write.max_rate = '500MB';

トランザクション管理と並行制御

-- 明示的トランザクション
BEGIN;

-- 分離レベル設定
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 複数テーブルの更新
UPDATE employees SET salary = salary * 1.1 
WHERE department_id = (SELECT id FROM departments WHERE name = 'Engineering');

UPDATE departments SET description = 'Advanced software development' 
WHERE name = 'Engineering';

-- セーブポイント使用
SAVEPOINT engineering_update;

INSERT INTO user_profiles (user_id, profile_data) 
VALUES ((SELECT user_id FROM employees WHERE employee_id = 'ENG001'), 
        '{"role": "senior", "updated": true}');

-- 条件によるロールバック
-- ROLLBACK TO SAVEPOINT engineering_update; -- エラー時

COMMIT;

-- 分散トランザクションの例
BEGIN;

-- 複数リージョンにまたがる更新
UPDATE users SET updated_at = now() 
WHERE id IN (
    SELECT user_id FROM employees 
    WHERE department_id IN (
        SELECT id FROM departments WHERE name IN ('Engineering', 'Sales')
    )
);

-- 条件付き挿入
INSERT INTO departments (name, description)
SELECT 'DevOps', 'Infrastructure and deployment'
WHERE NOT EXISTS (SELECT 1 FROM departments WHERE name = 'DevOps');

COMMIT;

-- 再試行可能なトランザクション例
-- アプリケーション側での実装例
-- RETRY_LOOP:
-- BEGIN;
-- 
-- SELECT account_balance FROM accounts WHERE id = $1 FOR UPDATE;
-- -- 残高チェック
-- UPDATE accounts SET balance = balance - $2 WHERE id = $1;
-- UPDATE accounts SET balance = balance + $2 WHERE id = $3;
-- 
-- COMMIT;
-- -- 再試行エラー(40001)の場合はRETRY_LOOPに戻る

-- デッドロック優先度設定
SET TRANSACTION PRIORITY HIGH;
-- または
-- SET TRANSACTION PRIORITY LOW;

バックアップ・復元とメンテナンス

-- データベースバックアップ
BACKUP DATABASE company TO 's3://backup-bucket/company-backup?AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy';

-- 特定テーブルのバックアップ
BACKUP TABLE company.employees TO 'nodelocal://1/backups/employees';

-- 増分バックアップ
BACKUP DATABASE company TO 's3://backup-bucket/company-incremental' 
AS OF SYSTEM TIME '-1m' 
WITH revision_history;

-- 復元
RESTORE DATABASE company FROM 's3://backup-bucket/company-backup';

-- 特定時点での復元
RESTORE DATABASE company FROM 's3://backup-bucket/company-backup' 
AS OF SYSTEM TIME '2024-01-15 10:00:00';

-- クラスターメンテナンス
-- ノード停止準備
ALTER RANGE default CONFIGURE ZONE USING num_replicas = 5;

-- ノード除名(decommission)
-- cockroach node decommission 4 --insecure --host=localhost:26257

-- 統計情報更新
ANALYZE TABLE employees;

-- 不要データのクリーンアップ
DELETE FROM user_profiles WHERE created_at < '2023-01-01';

-- バキューム(自動実行されるが手動でも可能)
-- CockroachDBは自動ガベージコレクションのため手動バキューム不要

-- クラスター設定のバックアップ
SHOW ALL CLUSTER SETTINGS;

アプリケーション統合例

# Python psycopg2での接続例
import psycopg2
from psycopg2.extras import RealDictCursor
import uuid
from datetime import datetime

class CockroachDBConnection:
    def __init__(self, connection_params):
        self.conn_params = connection_params
        self.conn = None
    
    def connect(self):
        """CockroachDBに接続"""
        try:
            self.conn = psycopg2.connect(**self.conn_params)
            self.conn.set_session(autocommit=False)
            print("CockroachDBに接続しました")
        except Exception as e:
            print(f"接続エラー: {e}")
            raise
    
    def execute_with_retry(self, query, params=None, max_retries=3):
        """再試行機能付きクエリ実行"""
        for attempt in range(max_retries):
            try:
                with self.conn.cursor(cursor_factory=RealDictCursor) as cur:
                    cur.execute(query, params)
                    if cur.description:  # SELECT文の場合
                        return cur.fetchall()
                    else:  # INSERT/UPDATE/DELETE文の場合
                        self.conn.commit()
                        return cur.rowcount
            except psycopg2.errors.SerializationFailure as e:
                print(f"再試行 {attempt + 1}/{max_retries}: {e}")
                self.conn.rollback()
                if attempt == max_retries - 1:
                    raise
            except Exception as e:
                self.conn.rollback()
                raise
    
    def create_user(self, username, email, first_name, last_name):
        """ユーザー作成"""
        query = """
        INSERT INTO users (username, email, password_hash, first_name, last_name)
        VALUES (%s, %s, %s, %s, %s)
        RETURNING id
        """
        password_hash = f"hashed_{username}"  # 実際はハッシュ化処理
        
        result = self.execute_with_retry(
            query, 
            (username, email, password_hash, first_name, last_name)
        )
        return result
    
    def get_user_with_department(self, user_id):
        """ユーザーと部署情報を取得"""
        query = """
        SELECT 
            u.id, u.username, u.email, u.first_name, u.last_name,
            d.name as department_name,
            e.position, e.salary, e.hire_date
        FROM users u
        LEFT JOIN employees e ON u.id = e.user_id
        LEFT JOIN departments d ON e.department_id = d.id
        WHERE u.id = %s
        """
        return self.execute_with_retry(query, (user_id,))
    
    def transfer_between_accounts(self, from_account, to_account, amount):
        """分散トランザクション例:口座間送金"""
        try:
            # 送金トランザクション
            self.execute_with_retry("""
                UPDATE accounts SET balance = balance - %s 
                WHERE id = %s AND balance >= %s
            """, (amount, from_account, amount))
            
            self.execute_with_retry("""
                UPDATE accounts SET balance = balance + %s 
                WHERE id = %s
            """, (amount, to_account))
            
            # トランザクション履歴記録
            self.execute_with_retry("""
                INSERT INTO transaction_logs (from_account, to_account, amount, timestamp)
                VALUES (%s, %s, %s, %s)
            """, (from_account, to_account, amount, datetime.now()))
            
            print(f"送金完了: {from_account} -> {to_account}, 金額: {amount}")
            
        except Exception as e:
            print(f"送金エラー: {e}")
            self.conn.rollback()
            raise
    
    def close(self):
        """接続クローズ"""
        if self.conn:
            self.conn.close()

# 使用例
if __name__ == "__main__":
    # 接続パラメータ
    conn_params = {
        'host': 'localhost',
        'port': 26257,
        'database': 'company',
        'user': 'root',
        'sslmode': 'disable'  # 本番環境では適切なSSL設定を使用
    }
    
    db = CockroachDBConnection(conn_params)
    
    try:
        db.connect()
        
        # ユーザー作成
        result = db.create_user(
            'alice_cooper', '[email protected]', 
            'Alice', 'Cooper'
        )
        print(f"ユーザー作成結果: {result}")
        
        # ユーザー情報取得
        user_info = db.get_user_with_department('some-uuid')
        print(f"ユーザー情報: {user_info}")
        
    except Exception as e:
        print(f"エラー: {e}")
    
    finally:
        db.close()

# Go言語での接続例(参考)
"""
package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/lib/pq"
    "github.com/cockroachdb/cockroach-go/v2/crdb/crdbpgx"
)

func main() {
    db, err := sql.Open("postgres", 
        "postgresql://root@localhost:26257/company?sslmode=disable")
    if err != nil {
        log.Fatal("数据库连接错误:", err)
    }
    defer db.Close()

    // 再試行機能付きトランザクション
    err = crdbpgx.ExecuteTx(context.Background(), db, pgx.TxOptions{}, 
        func(tx pgx.Tx) error {
            // トランザクション内の処理
            _, err := tx.Exec(context.Background(), 
                "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 
                100, "account1")
            return err
        })
    
    if err != nil {
        log.Fatal("トランザクションエラー:", err)
    }
}
"""