はじめに
こんにちは、マイクロアドでサーバーサイドエンジニアをしている高橋です。 主にDigdag1とPythonを使用したETL処理のバッチを開発しています。
今回はredis-pyというPythonライブラリを使ってRedis Clusterに登録されている全キーを取得する方法を共有します。
経緯
マイクロアドでは広告配信のデータベースとして速度が求められる一部の処理でRedisを使用しています。 一方で、速度よりも検索性が求められるような場面ではRedisはあまり適しておらず、クエリで検索できるようなデータベースが望ましいです。 マイクロアドではRedis以外のデータベースとしてCDHも利用しており、上記の理由からやむを得ずRedisのデータをCDHに全件転送する処理も行っています。
今まではこのRedisをシャーディング構成で使用していましたが、最近ではRedis Clusterへの移行を進めている状態です。 Redis Clusterへの移行に伴い全件転送の処理もRedis Clusterに対応する必要があったため、redis-pyでの全件取得の方法を調査しました。
RedisやCDHをはじめとするマイクロアドで採用しているデータ基盤やログ蓄積については別の記事で詳しく書かれています。 興味がありましたらぜひご一読ください。
developers.microad.co.jp developers.microad.co.jp
redis-py とは
その名の通りRedisを操作するためのPythonライブラリで、Redis社によって開発が行われています。
redis-pyを採用した理由としては以下の2点が挙げられます。
- 既存のレプリケーション構成用の処理でもredis-pyを使用していた
- redis-pyがredis-py-clusterというライブラリを取り込む形で4.1.0から正式にRedis Clusterに対応した2
また、redis-pyの処理を高速化するためにhiredis-py3も導入します。 hiredis-pyはredis-pyと同様にRedis社によって開発が行われているライブラリで、パーサ部分をC言語で実装したhiredisをPythonで使用するためのラッパーです。
Redis関連のライブラリは以下のバージョンを使用しています。
redis==4.3.4 hiredis==2.0.0
Redis Clusterへの接続
接続にはホスト名とポート番号が必要です。
以下ではクラスタ内の各ノードをClusterNode
クラスのインスタンスとして定義し、そのリストを渡してRedisCluster
クラスのインスタンスを作成しています。
from redis.cluster import RedisCluster, ClusterNode nodes = [ {"host": "redis-cluster-host01", "port": 6379}, {"host": "redis-cluster-host02", "port": 6379} ] cluster_nodes = [ClusterNode(**node) for node in nodes] redis_cluster = RedisCluster(startup_nodes=cluster_nodes, read_from_replicas=True)
RedisCluster
クラスを定義する際の引数として、startup_nodes
以外にもread_from_replicas
など複数の項目があります。
read_from_replicas
はデフォルトでFalse
ですが、True
にするとread系のコマンドでマスタノード以外からも取得できるようになります。
問題なく接続できていれば、RedisCluster.get_nodes()
で以下のようなノードのインスタンスの情報が取得できます。
startup_nodes
では2ノードを指定しましたが、以下では3ノード分出力されておりクラスタ内の全てのノードが取得できています。
>>> redis_cluster.get_nodes() [[host=192.0.2.1,port=6379,name=192.0.2.1:6379,server_type=primary,redis_connection=Redis<ConnectionPool<Connection<host=192.0.2.1,port=6379,db=0>>>], [host=192.0.2.2,port=6379,name=192.0.2.2:6379,server_type=primary,redis_connection=Redis<ConnectionPool<Connection<host=192.0.2.2,port=6379,db=0>>>], [host=192.0.2.3,port=6379,name=192.0.2.3:6379,server_type=primary,redis_connection=Redis<ConnectionPool<Connection<host=192.0.2.3,port=6379,db=0>>>]]
Redisのコマンドでノードの取得をする場合はRedisCluster.cluster_nodes()
などで取得できます。
全キーの取得
Redis Clusterの全キー取得の方針として以下の3つが挙げられます。
KEYS
SCAN
CLUSTER GETKEYSINSLOT
キーが取得できたら取得したいデータの型に合わせてGET
やHGET
などを行えば全件の内容が取得できます。
KEYS
KEYS
は正規表現のパターンに一致するキーを全て取得するコマンドです。
これを使用した全キー取得のシンプルな方法としてKEYS *
が考えられますが、公式ドキュメントで推奨されていないコマンドでもありレコードが多い場合には危険です。
Don't use KEYS in your regular application code.
KEYS
の危険性についてはこちらの記事がわかりやすかったです。
SCAN
カーソルによって分割して参照していくSCAN
を使う方法もあります。
ただし、以下の通りredis-cliとredis-pyで挙動が異なっているので注意が必要です。
SCAN に該当する操作 |
挙動 | |
---|---|---|
redis-cli | SCAN |
特定のノードのキーの取得 |
redis-py | RedisCluster.scan() RedisCluster.scan_iter() |
クラスタ内の全ノードのキーの取得 |
どちらもキーを分割して取得できますが、redis-cliではクラスタ内の全ノードに対して個別にSCAN
を行わなければ全キーの取得ができません。
CLUSTER GETKEYSINSLOT
今回の記事で使用するのはクラスタのスロット内のキーを取得するCLUSTER GETKEYSINSLOT
です。
スロット・取得件数を引数として実行するコマンドで、指定した件数のキーが取得できます。
これとスロットのキー数を取得するCLUSTER COUNTKEYSINSLOT
を組み合わせることでスロット内の全キー取得を実現します。
redis-pyだとRedisCluster.cluster_get_keys_in_slot()
とRedisCluster.cluster_countkeysinslot()
がそれぞれのコマンドに対応しています。
デフォルトの設定ではRedis Clusterは16384スロット存在しているので、スロット数分繰り返せば全スロットの全キーの取得が可能です。
for slot in range(16384): num_keys = redis_cluster.cluster_countkeysinslot(slot) keys = redis_cluster.cluster_get_keys_in_slot(slot, num_keys)
特定のスロットで取得した結果が以下になります。
>>> slot = 15033 >>> num_keys = redis_cluster.cluster_countkeysinslot(slot) >>> num_keys 1 >>> keys = redis_cluster.cluster_get_keys_in_slot(slot, num_keys) >>> keys ['key_name']
備考
RedisCluster.scan_iter()
とRedisCluster.cluster_get_keys_in_slot()
の違いは多数ありますが、そのうち一点を紹介します。
RedisCluster.scan_iter()
はbytes型、RedisCluster.cluster_get_keys_in_slot()
はstr型でキーを取得するという点です。
>>> type(next(redis_cluster.scan_iter(match='*'))) <class 'bytes'> >>> type(redis_cluster.cluster_get_keys_in_slot(slot, num_keys)[0]) <class 'str'>
おわりに
redis-pyでのRedis Clusterに登録されている全キーの取得について紹介しました。 全キーの取得に関する記事はいくつかありましたが、クラスタ構成のものはなさそうだったので参考になれば幸いです。 接続時のオプションなどまだまだ改善すべき部分は多そうですが、機会があれば記事を書きます。
-
Digdagとはマイクロアドで採用しているワークフローエンジンです。
詳しくは過去記事を参照ください。https://developers.microad.co.jp/archive/category/Digdag↩ - https://github.com/redis/redis-py/releases/tag/v4.1.0rc1↩
- https://github.com/redis/hiredis-py↩