データベース
Riak KV
概要
Riak KVは、高可用性、耐障害性、および運用の簡素性を重視して設計された分散型NoSQLキー・バリューデータベースです。Amazon DynamoペーパーとCAP定理の原則に強く影響を受け、Erlangで実装されています。マスターレスアーキテクチャを採用し、すべてのノードが読み書きリクエストに対応可能で、クラスタ全体でデータを自動分散・レプリケーションします。
詳細
Riak KVは2009年にBasho Technologiesによって開発され、現在はRiak社が開発・サポートを継続しています。分散システムの「Ring」アーキテクチャを採用し、160ビットのキー空間でデータを論理的に分散配置します。各ノードは複数のVirtual Nodes(vnodes)を持ち、データの断片を担当します。
Riak KVの主な特徴:
- マスターレスアーキテクチャ(全ノードが平等)
- 自動データ分散とレプリケーション
- Vector Clocksによる因果関係追跡
- 調整可能な一貫性レベル(R/W値)
- 複数のストレージバックエンド対応
- Gossipプロトコルによるクラスタ状態共有
- HTTP APIとProtocol Buffers API
- ネットワーク分断耐性
- 水平スケーラビリティ
- 結果整合性(Eventually Consistent)
ストレージバックエンドとしてBitcask(デフォルト)、LevelDB、Memory、Leveledなどを選択できます。
メリット・デメリット
メリット
- 高可用性: 24/7運用環境に最適、複数ノード障害でも動作継続
- 水平スケーラビリティ: ノード追加による線形的な性能向上
- 運用簡易性: マスターレス設計で管理が容易、シングルポイントオブフェイラー無し
- 耐障害性: 自動フェイルオーバーとデータ復旧
- 柔軟な一貫性: R/W値による一貫性レベル調整
- マルチデータセンター対応: 地理的に分散した環境をサポート
- 結果整合性: CAP定理のA(可用性)とP(分断耐性)を優先
デメリット
- 複雑なクエリ不可: SQL式のSELECT文やJOINは不可能
- 小規模環境には過剰: 最低5ノード構成が推奨されコストが高い
- 強一貫性不適: ACID特性が必要な用途には不向き
- 大きなオブジェクト不向き: 1MB以下のデータに最適化
- 学習コスト: 分散システムの概念理解が必要
- 開発の複雑化: 競合解決をアプリケーション側で実装
主要リンク
書き方の例
インストール・セットアップ
# Ubuntu/Debian
curl -s https://packagecloud.io/install/repositories/basho/riak/script.deb.sh | sudo bash
sudo apt-get install riak
# Red Hat/CentOS
curl -s https://packagecloud.io/install/repositories/basho/riak/script.rpm.sh | sudo bash
sudo yum install riak
# macOS (Homebrew)
brew tap basho/riak
brew install riak
# Docker
docker run -d --name riak-container -p 8087:8087 -p 8098:8098 basho/riak-kv
# サービス開始
sudo systemctl start riak
sudo systemctl enable riak
# 動作確認
curl http://127.0.0.1:8098/ping
# Expected: OK
基本操作(HTTP API)
# データ保存
curl -X PUT -H "content-type: text/plain" \
http://127.0.0.1:8098/riak/users/user1 \
--data '{"name": "田中太郎", "age": 30, "email": "[email protected]"}'
# データ取得
curl -i http://127.0.0.1:8098/riak/users/user1
# 条件付き取得(一貫性レベル指定)
curl http://127.0.0.1:8098/riak/users/user1?r=2
# データ更新
curl -X PUT -H "content-type: application/json" \
-H "X-Riak-Vclock: a85hYGBgzGDKBVIcypz/fgaUHjmdwZTImMfKyDt97Q==" \
http://127.0.0.1:8098/riak/users/user1 \
--data '{"name": "田中太郎", "age": 31, "email": "[email protected]"}'
# データ削除
curl -X DELETE http://127.0.0.1:8098/riak/users/user1
# キー一覧(開発用のみ)
curl http://127.0.0.1:8098/riak/users?keys=true
バケット設定
# バケット設定取得
curl http://127.0.0.1:8098/riak/users
# レプリケーション数設定(N値)
curl -X PUT -H "content-type: application/json" \
http://127.0.0.1:8098/riak/users \
--data '{"props":{"n_val":5}}'
# 一貫性レベル設定
curl -X PUT -H "content-type: application/json" \
http://127.0.0.1:8098/riak/users \
--data '{"props":{"r":"quorum","w":"quorum","dw":"quorum"}}'
# 競合解決設定
curl -X PUT -H "content-type: application/json" \
http://127.0.0.1:8098/riak/users \
--data '{"props":{"allow_mult":true,"last_write_wins":false}}'
Node.js クライアント使用例
const RiakClient = require('basho-riak-client')
// クライアント作成
const client = new RiakClient(['127.0.0.1:8087'])
// データ保存
const storeValue = {
bucket: 'users',
key: 'user1',
value: JSON.stringify({
name: '田中太郎',
age: 30,
email: '[email protected]'
}),
contentType: 'application/json'
}
client.storeValue(storeValue, (err, rslt) => {
if (err) {
console.error('保存エラー:', err)
} else {
console.log('データ保存成功')
console.log('Vector Clock:', rslt.vclock)
}
})
// データ取得
const fetchValue = {
bucket: 'users',
key: 'user1'
}
client.fetchValue(fetchValue, (err, rslt) => {
if (err) {
console.error('取得エラー:', err)
} else if (rslt.values.length === 0) {
console.log('データが見つかりません')
} else {
const userData = JSON.parse(rslt.values[0].value.toString())
console.log('ユーザーデータ:', userData)
}
})
// 複数バージョン処理(競合解決)
client.fetchValue(fetchValue, (err, rslt) => {
if (rslt.values.length > 1) {
console.log('競合が検出されました')
rslt.values.forEach((value, index) => {
console.log(`バージョン ${index + 1}:`,
JSON.parse(value.value.toString()))
})
// 最新の更新時刻のデータを選択(例)
const latest = rslt.values.reduce((prev, current) => {
const prevData = JSON.parse(prev.value.toString())
const currentData = JSON.parse(current.value.toString())
return currentData.updatedAt > prevData.updatedAt ? current : prev
})
console.log('選択されたバージョン:', JSON.parse(latest.value.toString()))
}
})
// 接続終了
client.stop()
クラスタ管理
# クラスタ状態確認
riak-admin cluster status
# 新ノード追加
riak-admin cluster join [email protected]
# クラスタプラン表示
riak-admin cluster plan
# プラン実行
riak-admin cluster commit
# ノード退去
riak-admin cluster leave [email protected]
# リング状態確認
riak-admin ring-status
# 手動でハンドオフ実行
riak-admin transfer-limit 4
riak-admin handoff status
監視・保守
# ノード統計情報
riak-admin stat
# 特定統計のみ表示
riak-admin stat | grep -E "(ring_|handoff_|coord_redirs)"
# vnodeの状態確認
riak-admin vnode-status
# パフォーマンス診断
riak-admin diag
# AAE(Anti-Entropy)状態
riak-admin aae-status
# 強制修復実行
riak-admin repair-2i users
# バックアップ(データディレクトリ)
sudo tar -czf riak-backup-$(date +%Y%m%d).tar.gz /var/lib/riak
設定ファイル例(riak.conf)
# ノード設定
nodename = [email protected]
distributed_cookie = riak
# ディレクトリ設定
platform_data_dir = /var/lib/riak
platform_etc_dir = /etc/riak
platform_log_dir = /var/log/riak
# ネットワーク設定
listener.http.internal = 127.0.0.1:8098
listener.protobuf.internal = 127.0.0.1:8087
# ストレージ設定
storage_backend = bitcask
# クラスタ設定
ring_size = 64
# デフォルトバケット設定
buckets.default.n_val = 3
buckets.default.r = 2
buckets.default.w = 2
buckets.default.dw = 1
# パフォーマンス設定
erlang.schedulers.force_wakeup_interval = 500
erlang.schedulers.compaction_of_load = false
# ログ設定
log.error.file = /var/log/riak/error.log
log.console.level = info
log.crash.file = /var/log/riak/crash.log
ベストプラクティス
# 1. 適切なクラスタサイズ(最低5ノード)
# 3ノード: 開発環境のみ
# 5ノード: 本番環境推奨
# 7ノード以上: 大規模環境
# 2. データモデリング
# - オブジェクトサイズ: 1MB以下
# - 自然キー使用: ユーザーID、タイムスタンプ等
# - バケット設計: 論理的なデータグルーピング
# 3. 一貫性レベル設定
# R + W > N: 強一貫性
# R = 1, W = 1: 高可用性優先
# R = quorum, W = quorum: バランス型
# 4. 運用時の注意点
# - 同時ノード障害は N/2 未満に留める
# - 定期的なAAE実行で整合性確認
# - ハンドオフ完了を待ってから次のノード操作
# - 監視項目: CPU 30%以下、I/O 90%以下
# 5. パフォーマンス最適化
# - Bitcaskの定期的なマージ実行
# - 適切なerlang.schedulers設定
# - ネットワーク帯域幅の確保
# - SSDストレージの使用推奨