CockroachDB
分散型SQLデータベース。強一貫性とACID特性を保ちながらグローバルスケール対応。PostgreSQL互換のSQL API提供。
データベースサーバー
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)
}
}
"""