Redis4.0のUNLINKを使ってみる
こんにちは、サーバーサイドエンジニアの菅原です。
今回は今更ながらRedis4系から追加されたUNLINKコマンドについて調べて検証してみました。
背景
Webアプリケーションのパフォーマンスを向上しようとするときRedisは強力なツールです。
ですがRedisを運用する注意点としてRedisの容量の懸念が見込まれます。
キーがどんどん積み上がるとRedisの容量を大きく圧迫し、そうなるとRedisはキーの検索に時間を要するのとキーを削除するときもサーバーに負荷がかかりサーバーダウンしてしまうことも考えられます。
setした時に設定したexpireが切れたキーも完全に消えることは確証されなく、
ゴミのデータが残る場合があるのでDELコマンドで定期的に掃除が必要になります。
しかしRedis3までのDELコマンドは一度に指定されたキーを全てアクセスして削除しようとするのでその間はRedisは他の処理を受け付けないため
Redisに膨大なサーバー負荷がかかり、Redisが落ちてしまったり、クラスターダウンの影響にも繋がります。
今回はその問題を解決するためにRedis4系で新たに追加されたUNLINKコマンドについて紹介します。
Redis4とは
Redis4は2017年7月14日にリリースされました。現在は4.0.10までのバージョンが出ています。
4系で追加された仕様の中でもっとも気になったのはモジュール機能とUNLINKコマンドの追加です。
モジュール機能はRedisの型やコマンドなどを拡張できる機能で
UNLINKコマンドは非同期にキーのデータを削除してくれることで
スレッドの待ち時間なしにデータを順に削除することができる機能です。
今回は実際にUNLINKコマンドを扱って挙動を確認してみましょう。
今回の目標とゴール
- Redis Clusterを構築する
- Redis Clusterにデータをセットする
- UNLINKコマンドで容量が大きいキーTOP10を削除するシェル作成
- 実際に登録したデータが反映されているか確認する
環境
- Mac OS
- redis-4.0.9
Redis Clusterを構築する
まずは公式HPから自分のマシンにRedis 4をダウンロードして解凍します。
以前に環境構築した記事で細かい設定などを執筆してあるのでそちらも参考にしてください。
$ wget http://download.redis.io/releases/redis-4.0.9.tar.gz $ tar xzf redis-4.0.9.tar.gz $ cd redis-4.0.9 $ make $ sudo make install
上記インストールが完了したら
redis-serverを立ち上げましょう(brew)など/usr配下のredisを起動したい場合はそのパスに合わせてredis-serverコマンドを入力します。
$ src/redis-server
redisが立ち上がったらredisのコマンドラインを立ち上げます
$ src/redis-cli
無事立ち上げることができたら成功です。デフォルトでは6379のポート番号でredisが立ち上がっていることがわかります。
そして早速Redis Clusterを構築するためのCluster構成を設計しましょう
今回はClusterを構成するための最低限のCluster構成(master: 3台, slave: 3台)で設計して構築します。
図で表すとこんな感じです。
Port(7000~7005)までのRedisでクラスターを構築していきます。
まずは先ほど立ち上げたredisサーバーを閉じてcluster-testディレクトリを作成します。
作成が完了したらその中に(7000~7005)までのディレクトリの部屋を用意してあげます。
用意ができたらそれぞれの部屋にRedisの設定ファイル(redis.conf)を用意してあげます。ポート番号は各自変更してください。
シェルで配布もできそうなのでそこらへんもまた作っていけたらと思います。
port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes
各ディレクトリに配布が完了したら各Redisを立ち上げていきましょう。
#!/bin/sh /usr/local/bin/redis-server ./7000/redis.conf --pidfile ./7000/redis.pid --logfile ./7000/redis.log --cluster-config-file ./7000/node.conf& /usr/local/bin/redis-server ./7001/redis.conf --pidfile ./7001/redis.pid --logfile ./7001/redis.log --cluster-config-file ./7001/node.conf& /usr/local/bin/redis-server ./7002/redis.conf --pidfile ./7002/redis.pid --logfile ./7002/redis.log --cluster-config-file ./7002/node.conf& /usr/local/bin/redis-server ./7003/redis.conf --pidfile ./7003/redis.pid --logfile ./7003/redis.log --cluster-config-file ./7003/node.conf& /usr/local/bin/redis-server ./7004/redis.conf --pidfile ./7004/redis.pid --logfile ./7004/redis.log --cluster-config-file ./7004/node.conf& /usr/local/bin/redis-server ./7005/redis.conf --pidfile ./7005/redis.pid --logfile ./7005/redis.log --cluster-config-file ./7005/node.conf&
無事立ち上がったら次にクラスターで各nodeをclusterとして連携させていきます。
今回は redis-trib.rb
というrubyで作られたクラスター管理ツールで構築していきます。
(あらかじめgemなどでインストールしておきます)
$ gem install redis4.0.1
clusterは3系以降しか構築できないためgemでインストールするredisは3系以上のインストールをお願いします。
redis-trib.rb
のバイナリが入ることがわかるので実行していきましょう。
redis-tribはクラスターを構築したり、nodeを追加したり、クラスターやノードの状態をチェックすることもできます。
今回は各MASTERノードにSLAVEを1 対 1 で構築してあげたいのでreplicas 1のコマンドをコマンド内に入れて実行していきましょう
$ ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
実行が完了するとコンバートしたよとクラスターの構成ができたことがわかります。
[OK] All 16384 slots covered
実際にクラスターの状態を確認します。
$ redis-cli -c -p 7000 cluster nodes e75b961fb9d1520d017d6b344d20e47e095ae51d 127.0.0.1:7000@17000 myself,master - 0 1529857410000 1 connected 0-5460 87db09e5e5f7cc3cd6a0c80d335f0cc5fbd71fee 127.0.0.1:7002@17002 master - 0 1529857411571 3 connected 10923-16383 4dcf420b2674f6b4608b5677ee61d6a8c5b0d74e 127.0.0.1:7001@17001 master - 0 1529857412091 8 connected 5461-10922 5e25fd42ea7ca7cb920db0cc5f16ff6c6a0f1e7b 127.0.0.1:7004@17004 slave 4dcf420b2674f6b4608b5677ee61d6a8c5b0d74e 0 1529857411000 8 connected 4bd60fc5c721be1e560119bad7f4c358ae4050c3 127.0.0.1:7005@17005 slave 87db09e5e5f7cc3cd6a0c80d335f0cc5fbd71fee 0 1529857411467 6 connected b615c32f67585687139a99d199560d3a57716657 127.0.0.1:7003@17003 slave e75b961fb9d1520d017d6b344d20e47e095ae51d 0 1529857412000 11 connected
これでクラスターが構築されたことがわかります。
Redisにデータをセットする
それでは構築したClusterにデータを投入していきましょう
$ redis-cli -c -p 7000 127.0.0.1:7000 > set wp-cluster1 test1 OK 127.0.0.1:7000 > set wp-cluster2 test1 -> Redirected to slot [9505] located at 127.0.0.1:7004 OK 127.0.0.1:7004 >
最初に設定したwp-cluster1のキーバリューは7000(master)のredisに格納され、
そのあとのwp-cluster2はポート7004のredis(master)にリダイレクトで格納されて分散されていることがわかります。
次に先ほど自動的に格納されて切り替わったポート7004のコマンドラインから7000に格納したキー(wp-cluster1)の値を取り出してみます。
127.0.0.1:7004> GET wp-cluster1 -> Redirected to slot [5442] located at 127.0.0.1:7000 "test1" 127.0.0.1:7000>
7000ポートのredisを参照しに行ってることがわかります!これでredis clusterへのデータの投入ができました。
しかし、上記方法でデータの追加をしてUNLINKを扱うにはかなり面倒です。
Redisにはダミーデータを登録するコマンドがあるのでそれを使いましょう。
ダミーデータはDEBUGコマンドから投入することができます。
それでは1000件のダミーデータを投入していきましょう。
*Clusterではこのダミーデータはslaveへの同期が行われないので注意
127.0.0.1:7000 > debug populate 1000 OK
登録が完了できたら実際に投入されているか確認します。
127.0.0.1:7000 > dbsize (integer) 1000
1000件のデータが投入されていることがわかります。
それでは実際にこのキーの中でサイズが大きいTOP10を出力してUNLINKするためのシェルを書いていきます。
UNLINKコマンドで容量が大きいキーTOP10を削除するシェル作成
unlink_scan_big_keys.shファイルを作成し、以下のシェルを記載します。
#!/bin/bash BEGIN=0 TOPNUM=10 TMPFILE="./scan.out" RESULTFILE="./result.out" > $RESULTFILE redis-cli -c -p 7000 --raw SCAN $BEGIN > $TMPFILE while true do for key in `sed '1d' $TMPFILE` do echo -n $key" " >> $RESULTFILE redis-cli -c -p 7000 --raw DEBUG OBJECT $key | awk '{print $NF}' | cut -d":" -f 2 >> $RESULTFILE done CURSOR=`head -n1 $TMPFILE` if [[ $CURSOR -eq $BEGIN ]] then echo "Scan ended!" echo "The Top $TOPNUM key in your Redis is" cat $RESULTFILE | sort -nrk2 | head -n$TOPNUM ./unlink-keys.sh exit fi redis-cli -c -p 7000 --raw SCAN $CURSOR > $TMPFILE done
#!/bin/bash DATA=`cat ./result.out | sort -nrk2 | head -n10 | awk '{ sub(" .*", ""); print $0; }'` while read keyline do redis-cli -c -p 7000 UNLINK $keyline done << FILE $DATA FILE
コマンド一つ一つの意味は解説しませんが
7000のredisサーバにあるキーバリューをSCANで読み出し、読み出したデータの情報を加工して
result.outファイルに吐き出します。
result.outに記載しているキーのデータサイズを昇順に並び替え、そのトップ10をUNLINKで削除します。
unlink-keys.sh
は別シェルで叩いてますが同一シェル上で問題ありません。
実際に完成したらこのシェルを実行してあげましょう
$ ./unlink_scan_big_keys.sh Scan ended! The Top 10 key in your Redis is key:895 7408 key:477 7408 key:192 7408 key:963 7407 key:953 7407 key:949 7407 key:9 7407 key:899 7407 key:890 7407 key:876 7407 (integer) 0 (integer) 0 (integer) 1 (integer) 0 (integer) 0 (integer) 0 (integer) 0 (integer) 0 (integer) 0 (integer) 0
消す対象に上がったキーのバックアップファイルを参照してそのキーが本当に消されているのか確認します。
GET keys key:192 nil
nilが返ってきたのでデータが削除されていることがわかります。
今回はトップ10のデータをunlinkで削除しました。
実際にDELコマンドで削除するよりも非同期で安全に削除することができるため
バッチのように定期的に取り扱うことも簡単にできそうです。
まとめ
Redis4系のUNLINKが気になったので軽く触ってみました。
大量にデータをUNLINKしてる場合にも別ターミナルからRedisへデータを投入なども可能で
DELではできなかった操作でデータ量と頻度によってはUNLINKがおすすめであることがわかりました!
redis4.0ではさらに可用性で追加された仕様もあるので皆さんも是非使ってみてください。