初めての NoSQL - Redis の編集履歴

nishikawa が 2015-12-16 23:15 に編集

--- Ver.6	2015-12-16 23:12:43+09:00
+++ Ver.7	2015-12-16 23:15:12+09:00
@@ -620,7 +620,7 @@
 (nil)
 ```
 
-# 例2: ジョブキュー #
+## 例2: ジョブキュー ##
 
 Redis を使ったジョブキューを実装してみます。ジョブキューとはその名の通りなんらかのジョブ (= 手続き) を要素に持つキューです。なんらかのジョブをバックグラウンドプロセスに任せる際、プロセス間におけるジョブのやり取りに利用されます。バックグラウンドプロセスはキューを監視し、キューにジョブが追加されればそれを取り出し、順にこなしていきます。
 

nishikawa が 2015-12-16 23:12 に編集

--- Ver.5	2015-12-12 12:54:49+09:00
+++ Ver.6	2015-12-16 23:12:43+09:00
@@ -99,7 +99,7 @@
 
 DEL はキーを引数にとり、キーに割り当てられた値を削除します。
 
-基本的な SET, GET, DEL のような基本的な読み書きのコマンドから、Redis が提供するデータ型に特化したコマンド:
+SET, GET, DEL のような基本的な読み書きのコマンドから、Redis が提供するデータ型に特化したコマンド:
 
  * 数値のインクリメント/デクリメント
  * リストへの PUSH/POP
@@ -309,7 +309,7 @@
 
 ## ZSET (ソート済みセット) ##
 
-ZSET は SET と同様にデータの集合を表現しつつ、集合内のデータをソート済みの状態で保持するデータ構造です。ハッシュ表とスキップリストの組み合わせで実装されています。ZSET に格納されるデータには、スコアと呼ばれる浮動小数点数を付与することになっています。いわば、値がスコアな HASH 表であるとも言えます。ZSET のデータはスコアをキーとしてソートされます。
+ZSET は SET と同様にデータの集合を表現しつつ、集合内のデータをソート済みの状態で保持するデータ構造です。ハッシュ表とスキップリストの組み合わせで実装されています。ZSET に格納されるデータには、スコアと呼ばれる浮動小数点数を付与することになっています。いわば、値がスコアな HASH であるとも言えます。ZSET のデータはスコアをキーとしてソートされます。
 
 以下は ZSET に対する代表的なコマンドです:
 
@@ -560,7 +560,7 @@
 
 ### スナップショット ###
 
-スナップショットは、ある時点での Redis の状態をそっくりそのまま保存する形式の永続化方式です。スナップショットは周期的に取得する仕組みが用意されています。周期的に取得するという特徴上、データのロスは避けられません。例えば 15 分おきにスナップショットを取得するよう設定していれば、15 分 + スナップショットの保存にかかった時間分のデータをロスする可能性があります。
+スナップショットは、ある時点での Redis の状態をそっくりそのまま保存する形式の永続化方式です。周期的にスナップショットを取得する仕組みが用意されています。周期的に取得するという特徴上、データのロスは避けられません。例えば 15 分おきにスナップショットを取得するよう設定していれば、15 分 + スナップショットの保存にかかった時間分のデータをロスする可能性があります。
 
 スナップショットの取得方法は大きく 2 つ用意されています:
 
@@ -622,7 +622,7 @@
 
 # 例2: ジョブキュー #
 
-Redis を使ったジョブキューを実装してみます。ジョブキューとはその名の通りなんらかのジョブ (= 手続き) を要素に持つキューです。その処理をバックグラウンドプロセスに対する要求として、キューに要素を追加されます。バックグラウンドプロセスはキューに追加されたジョブを先頭から順にこなしていきます。
+Redis を使ったジョブキューを実装してみます。ジョブキューとはその名の通りなんらかのジョブ (= 手続き) を要素に持つキューです。なんらかのジョブをバックグラウンドプロセスに任せる際、プロセス間におけるジョブのやり取りに利用されます。バックグラウンドプロセスはキューを監視し、キューにジョブが追加されればそれを取り出し、順にこなしていきます。
 
 ジョブキューには RabbitMQ や ActiveMQ, AWS の SQS など、専用のミドルウェア製品が存在しますが、Redis の LIST を使うことで、簡単に簡易的なジョブキューを構築することができます。
 

nishikawa が 2015-12-12 12:54 に編集

--- Ver.4	2015-12-12 12:29:40+09:00
+++ Ver.5	2015-12-12 12:54:49+09:00
@@ -528,15 +528,9 @@
 def update_token(conn, token, user, timeout=5):
     timestamp = time.time()
     pipe = conn.pipeline()
-    end = time.time() + timeout
-    while time.time() < end:
-        try:
-            pipe.hset('login:', token, user)
-            pipe.zadd('recent:', token, timestamp)
-            pipe.execute()
-            return
-        except WatchError:
-            continue
+    pipe.hset('login:', token, user)
+    pipe.zadd('recent:', token, timestamp)
+    pipe.execute()
 
 
 LIMIT = 10000000
@@ -554,6 +548,8 @@
     pipe.zrem('recent:', *tokens)
     pipe.execute()
 ```
+
+Python の Redis クライアントライブラリでは、pipeline メソッドを使うことでトランザクションを開始することができます。Pipeline オブジェクトに対する Redis コマンドの呼び出しはクライアントサイドにキューイングされ、execute メソッドを呼び出したタイミングでサーバーに送られます。そして各コマンドの実行結果が execute メソッドの戻り値として返ってきます。
 
 ## 永続化 ##
 

nishikawa が 2015-12-12 12:29 に編集

--- Ver.3	2015-12-12 12:29:00+09:00
+++ Ver.4	2015-12-12 12:29:40+09:00
@@ -541,7 +541,7 @@
 
 LIMIT = 10000000
 
-def clean_sessions(conn, timeout=5):
+def clean_sessions(conn):
     size = conn.zcard('recent:')
     if size <= LIMIT:
         return

nishikawa が 2015-12-12 12:29 に編集

--- Ver.2	2015-12-12 11:53:52+09:00
+++ Ver.3	2015-12-12 12:29:00+09:00
@@ -542,25 +542,17 @@
 LIMIT = 10000000
 
 def clean_sessions(conn, timeout=5):
+    size = conn.zcard('recent:')
+    if size <= LIMIT:
+        return
+
+    end_index = min(size - LIMIT, 100)
+    tokens = conn.zrange('recent:', 0, end_index - 1)
+
     pipe = conn.pipeline()
-    end = time.time() + timeout
-    while time.time() < end:
-        try:
-            pipe.watch('recent:')
-            size = conn.zcard('recent:')
-            if size <= LIMIT:
-                pipe.unwatch()
-                return
-        
-            end_index = min(size - LIMIT, 100)
-            tokens = pipe.zrange('recent:', 0, end_index - 1)
-            pipe.multi()
-            pipe.hdel('login:', *tokens)
-            pipe.zrem('recent:', *tokens)
-            pipe.execute()
-            return
-        except WatchError:
-            continue
+    pipe.hdel('login:', *tokens)
+    pipe.zrem('recent:', *tokens)
+    pipe.execute()
 ```
 
 ## 永続化 ##

nishikawa が 2015-12-12 11:53 に編集

--- Ver.1	2015-12-12 11:51:30+09:00
+++ Ver.2	2015-12-12 11:53:52+09:00
@@ -3,9 +3,9 @@
  * インメモリにデータを保持する
  * データ構造を直接扱える
 
-インメモリでデータを保持するため、データをディスクに保存するタイプのデータベースに比べて高速に動作します。Web システムにおいては、ページ出力のキャッシュ、ログインセッションの保存・読み込み、など高いパフォーマンスが求められる部分への適しています。当然、パフォーマンスのトレードオフとして永続化を捨てているので、データの永続性が重要な向きには利用できません。
-
-※永続化はオプションで可能であり、現実的には問題ない程度の信頼性は確保できる。よって、一概に **利用できない** とは言い切れない
+インメモリでデータを保持するため、データをディスクに保存するタイプのデータベースに比べて高速に動作します。Web システムにおいては、ページ出力のキャッシュ、ログインセッションの保存・読み込み、など高いパフォーマンスが求められる部分への適しています。当然、パフォーマンスのトレードオフとして永続化を捨てているので、データの永続性が重要な向きには利用できません。[^1]
+
+[^1]: 永続化はオプションで可能であり、現実的には問題ない程度の信頼性は確保できる。よって、一概に利用できないとは言い切れず、場合によるというのが正しい。
 
 Redis はリストや集合のようなデータ構造を直接扱えるようになっています。各データ構造に対するコマンドが豊富に用意されており、それらはすべてアトミックに動作します。例えば文字列に対して INCR (インクリメント), DECR (デクリメント) といったコマンドが提供されています。これらはアトミックに動作するので、(コマンド単位で) 処理の割り込みを気にする必要はありません。
 
@@ -473,7 +473,9 @@
 
 ### WATCH, UNWATCH ###
 
-WATCH による楽観的ロックは、トランザクションを実行するまでの間に他のクライアントによって値が変えられることを検知するための仕組みです。いわゆる CAS (Check And Set [^1]) の振る舞いを提供します。WATCH したキーの値が EXEC でトランザクションを実行するまでの間に書き換えられた場合、EXEC は何もせずに Null-Reply を返して終了します。クライアントはこの EXEC の結果をもって、トランザクションが実行されたかどうかを判断することができます。
+WATCH による楽観的ロックは、トランザクションを実行するまでの間に他のクライアントによって値が変えられることを検知するための仕組みです。いわゆる CAS (Check And Set [^2]) の振る舞いを提供します。WATCH したキーの値が EXEC でトランザクションを実行するまでの間に書き換えられた場合、EXEC は何もせずに Null-Reply を返して終了します。クライアントはこの EXEC の結果をもって、トランザクションが実行されたかどうかを判断することができます。
+
+[^2]: 個人的には CAS といえば Compare And Swap なのだが、Redis のドキュメントでは Check And Set と書かれている。
 
 RDBMS のトランザクションでは、BEGIN から COMMIT (ROLLBACK) までの読み書き操作が互いに影響を与え合って動作します。
 
@@ -515,8 +517,6 @@
 1) OK
 2) "3"
 ```
-
-[^1]: 個人的には CAS といえば Compare And Swap なのだが、Redis のドキュメントでは Check And Set と書かれている。
 
 先に挙げた check_token, update_token, clean_sessions をトランザクションを利用する形に修正してみます。
 

nishikawa が 2015-12-08 22:53 に投稿