読者です 読者をやめる 読者になる 読者になる

7つのデータベース7つの世界

7つのデータベース 7つの世界

7つのデータベース 7つの世界

読了。 Cassandraを「使うのが目的」のことがあるんだけど、やっぱり間違ってると分かった。

第8章 Redis

  • 速度に関していえば最高
  • 超高速なキーバリューストア
  • 何であるかを正確に分類するのは難しい
  • 応用的なデータ構造をサポート
  • ブロッキングキュー
  • スタック
  • Pub/Sub
  • 有効期限/永続レベル/レプリケーションのオプションが設定可能
  • 便利なデータ構造のアルゴリズムやプロセスのツールキット
% brew install redis
% redis-server -v
Redis server v=3.0.5 sha=00000000:0 malloc=libc bits=64 build=78b12a45f78ec0e
% redis-server
% redis-cli
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> SET 7wks http://www.sevenweeks.org/
OK
127.0.0.1:6379> GET 7wks
"http://www.sevenweeks.org/"
127.0.0.1:6379> MSET gog https://www.google.co.jp/ yah http://www.yahoo.co.jp/
OK
127.0.0.1:6379> MGET gog yah
1) "https://www.google.co.jp/"
2) "http://www.yahoo.co.jp/"
127.0.0.1:6379> SET count 2
OK
127.0.0.1:6379> INCR count
(integer) 3
127.0.0.1:6379> GET count
"3"
127.0.0.1:6379> SET bad_count "a"
OK
127.0.0.1:6379> INCR bad_count
(error) ERR value is not an integer or out of range
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET prag https://pragprog.com/
QUEUED
127.0.0.1:6379> INCR count
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 4
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR count
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> GET count
"4"
  • 複雑なデータ型
    • リスト・ハッシュ・セット・ソート済みが保存できる
    • ハッシュ
      • ネストできない
      • HDEL 削除
      • HINCRBY インクリメント
      • HLEN フィールド数
127.0.0.1:6379> MSET user:eric:name "Eric Redmond" user:eric:password s3cret
OK
127.0.0.1:6379> MGET user:eric:name user:eric:password
1) "Eric Redmond"
2) "s3cret"
127.0.0.1:6379> HMSET user:eric name "Eric Redmond" password s3cret
OK
127.0.0.1:6379> HVALS user:eric
1) "Eric Redmond"
2) "s3cret"
127.0.0.1:6379> HKEYS user:eric
1) "name"
2) "password"
127.0.0.1:6379> HGET user:eric password
"s3cret"
  • リスト
127.0.0.1:6379> RPUSH eric:wishlist 7wks gog yah
(integer) 3
127.0.0.1:6379> LRANGE eric:wishlist 0 -1
1) "7wks"
2) "gog"
3) "yah"
127.0.0.1:6379> LREM eric:wishlist 0 gog
(integer) 1
127.0.0.1:6379> LRANGE eric:wishlist 0 -1
1) "7wks"
2) "yah"
127.0.0.1:6379> LPOP eric:wishlist
"7wks"
127.0.0.1:6379> LRANGE eric:wishlist 0 -1
1) "yah"
127.0.0.1:6379> RPOPLPUSH eric:wishlist eric:visited
"yah"
% gem i redis
Successfully installed redis-3.2.1
1 gem installed
  • リストのブロック
127.0.0.1:6379> BRPOP comments 300
127.0.0.1:6379> LPUSH comments "Prag is great! I buy all my books there."
(integer) 1
127.0.0.1:6379> BRPOP comments 300
1) "comments"
2) "Prag is great! I buy all my books there."
(30.38s)
  • セット
127.0.0.1:6379> SADD news nytimes.com pragprog.com
(integer) 2
127.0.0.1:6379> SMEMBeRS news
1) "pragprog.com"
2) "nytimes.com"
127.0.0.1:6379> SADD tech pragprog.com apple.com
(integer) 2
127.0.0.1:6379> SINTER news tech
1) "pragprog.com"
127.0.0.1:6379> SDIFF news tech
1) "nytimes.com"
127.0.0.1:6379> SUNION news tech
1) "pragprog.com"
2) "apple.com"
3) "nytimes.com"
127.0.0.1:6379> SUNIONSTORE websites news tech
(integer) 3
127.0.0.1:6379> SMEMBErS websites
1) "pragprog.com"
2) "apple.com"
3) "nytimes.com"
  • ソート済みセット, 範囲
127.0.0.1:6379> ZADD visits 500 7wks 9 gog 9999 prag
(integer) 3
127.0.0.1:6379> ZINCRBY visits 1 prag
"10000"
127.0.0.1:6379> ZRANGE visits 0 1
1) "gog"
2) "7wks"
127.0.0.1:6379> ZRANGE visits 0 -1 WITHSCORES
1) "gog"
2) "9"
3) "7wks"
4) "500"
5) "prag"
6) "10000"
127.0.0.1:6379> ZREVRANGE visits 0 -1 WITHSCORES
1) "prag"
2) "10000"
3) "7wks"
4) "500"
5) "gog"
6) "9"
127.0.0.1:6379> ZRANGEBYSCORE visits 9 10000
1) "gog"
2) "7wks"
3) "prag"
127.0.0.1:6379> ZRANGEBYSCORE visits (9 (10000
1) "7wks"
127.0.0.1:6379> ZRANGEBYSCORE visits -inf inf
1) "gog"
2) "7wks"
3) "prag"
127.0.0.1:6379> ZADD votes 2 7wks 0 gog 9001 prag
(integer) 3
127.0.0.1:6379> ZUNIONSTORE importance 2 visits votes WEIGHTS 1 2 AGGREGATE SUM
(integer) 3
127.0.0.1:6379> ZRANGEBYSCORE importance -inf inf WITHSCORES
1) "gog"
2) "9"
3) "7wks"
4) "504"
5) "prag"
6) "28002"
  • 有効期限
    • カウントダウンは EXPIREAT (絶対時間)
    • EXPIRE は相対時間
127.0.0.1:6379> SET ice "I'm melting..."
OK
127.0.0.1:6379> EXPIRE ice 10
(integer) 1
127.0.0.1:6379> EXISTS ice
(integer) 1
127.0.0.1:6379> EXISTS ice
(integer) 1
127.0.0.1:6379> EXISTS ice
(integer) 1
127.0.0.1:6379> EXISTS ice
(integer) 0
127.0.0.1:6379> SETEX ice 10 "I'm melting..."
OK
127.0.0.1:6379> TTL ice
(integer) 4
127.0.0.1:6379> SETEX ice 10 "I'm melting..."
OK
127.0.0.1:6379> PERSIST ice
(integer) 1
127.0.0.1:6379> EXISTS ice
(integer) 1
127.0.0.1:6379> SET greeting hello
OK
127.0.0.1:6379> GET greeting
"hello"
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> GET greeting
(nil)
127.0.0.1:6379[1]> SET greeting "guten tag"
OK
127.0.0.1:6379[1]> SELECT 0
OK
127.0.0.1:6379> GET greeting
"hello"
127.0.0.1:6379> MOVE greeting 2
(integer) 1
127.0.0.1:6379> SELECT 2
OK
127.0.0.1:6379[2]> GET greeting
"hello"
127.0.0.1:6379[2]> SELECT 0
OK
127.0.0.1:6379> GET greeting
(nil)
% telnet
telnet> open localhost 6379
Trying ::1...
Connected to localhost.
Escape character is '^]'.
SET test hello
+OK
GET test
$5
hello
SADD stest 1 99
:2
SMEMBERS stest
*2
$1
1
$2
99
% (echo -en "ECHO hello\r\n"; sleep 1) | nc localhost 6379
$5
hello
% (echo -en "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
  • 出版/購読
127.0.0.1:6379> SUBSCRIBE comments
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "comments"
3) (integer) 1
127.0.0.1:6379> PUBLISH comments "Check out this shortcoded site! 7wks"
(integer) 1
127.0.0.1:6379> SUBSCRIBE comments
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "comments"
3) (integer) 1
1) "message"
2) "comments"
3) "Check out this shortcoded site! 7wks"
  • サーバ情報
127.0.0.1:6379> info
# Server
redis_version:3.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:78b12a45f78ec0e
redis_mode:standalone
...
  • 永続性
    • SAVE
    • BGSAVE
  • スナップショット
  • 追記型フィールド
    • めちゃくちゃ遅い
  • セキュリティ
    • プレインテキストのパスワードを設定するだけの機能はある
    • ファイアウォールSSHのセキュリティを使うべき
    • コマンド自体を隠したり無効にしたりできる
  • パラメータの調整
% redis-benchmark -n 100000                                                                                                         [252/576]
====== PING_INLINE ======
  100000 requests completed in 3.33 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.86% <= 1 milliseconds
83.00% <= 2 milliseconds
99.95% <= 7 milliseconds
99.96% <= 8 milliseconds
100.00% <= 9 milliseconds
100.00% <= 9 milliseconds
30012.00 requests per second
...
% redis-server redis-sl.conf
37908:S 24 Oct 21:38:41.265 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 3.0.5 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6380
 |    `-._   `._    /     _.-'    |     PID: 37908
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

37908:S 24 Oct 21:38:41.268 # Server started, Redis version 3.0.5
37908:S 24 Oct 21:38:41.268 * The server is now ready to accept connections on port 6380
37908:S 24 Oct 21:38:41.268 * Connecting to MASTER 127.0.0.1:6379
37908:S 24 Oct 21:38:41.269 * MASTER <-> SLAVE sync started
37908:S 24 Oct 21:38:41.269 * Non blocking connect for SYNC fired the event.
37908:S 24 Oct 21:38:41.270 * Master replied to PING, replication can continue...
37908:S 24 Oct 21:38:41.270 * Partial resynchronization not possible (no cached master)
37908:S 24 Oct 21:38:41.271 * Full resync from master: d5f7d1c8d4640c774e1c747b7cec1bd169e092c9:1
37908:S 24 Oct 21:38:41.397 * MASTER <-> SLAVE sync: receiving 400726 bytes from master
37908:S 24 Oct 21:38:41.400 * MASTER <-> SLAVE sync: Flushing old data
37908:S 24 Oct 21:38:41.400 * MASTER <-> SLAVE sync: Loading DB in memory
37908:S 24 Oct 21:38:41.451 * MASTER <-> SLAVE sync: Finished with success
  • ブルームフィルタ
    • セットに存在しない項目をチェックする確率的データ構造
    • Ruby の神童 Ilya Grigorik
  • SETBIT と GETBIT
127.0.0.1:6379> SETBIT my_burger 1 1
(integer) 0
127.0.0.1:6379> SETBIT my_burger 2 1
(integer) 0
127.0.0.1:6379> GETBIT my_burger 3
(integer) 0
127.0.0.1:6379> GETBIT my_burger 1
(integer) 1
  • ポリグロット永続亜kサービス
  • ノンブロッキングコード
    • 時間のかかるプロセスの終了を待たずにメインコードを実行し続けること
    • Ruby: EventMachine
    • Python: Twisted
    • Java: NIOライブラリ
    • C#: Interlace
    • JavaScript: Node.js
  • Redis の強み
    • 速度
    • 他のキーバリューストアと違うのはリスト・ハッシュ・セットのような複合データ型の保存や検索ができる
    • 永続化のオプションがある
      • 速度とデータの保全性を秤にかけて落としどころを探る
    • レプリケーションは読み取り回数の非常に多いシステムに最適
  • Redis の弱み
    • スナップショットをとる前にシャットダウンするとデータが失われる
    • 追記型ファイルを設定しても期限切れの値を扱うリスクがある
    • 使用可能な RAM より大きなデータセットは扱えない

第9章 まとめ

  • データの保存方法は大きく5つに分類
    1. リレーショナル
    2. キーバリュー
    3. カラムナー
    4. ドキュメント
    5. グラフ
  • リレーショナル
    • データが変わりやすかったり、データ階層が深いものには適していない
  • キーバリュー
    • 複雑なモデリングが必要なデータには弱い
    • データがさほど結びついていない問題に適している
    • CRUD 操作以上のクエリが必要なときにはうまくいかない
  • カラムナ-
    • 水平スケーラビリティが高いので、ビッグデータの問題に最適
    • データ使用パターンが事前に決定できていないければ適していない
  • ドキュメント
    • 変化の大きなドキュメントの問題に適している
    • 事前にデータがどのようになるか正確に分からない場合
    • インピーダンスミスマッチが少ない
    • 結合に該当するものがないので正規化できない
  • グラフ
    • レコメンテーションエンジン
    • アクセスコントロールリスト
    • ジオグラフィックデータ
    • ネットワーキングアプリケーション
    • オブジェクト指向にも完璧に対応
    • ネットワーク分割には適さない
  • データベースの選択はドメインのデータに適したデータベースの分野を考えるより複雑