Redis まわりのメモです.最近触ってるので.間違いなどあったらおしえてください
tl;dr
- AWS Elasticache で Redis のクラスタを組むよ
- PHP では 2 つの Redis クライアント実装が有名だよ
- クラスタには注意点があるよ
- クラスタと DB 番号の制限があったよ
- クラスタで
SCAN
できなくて困ったよ - Predis で
MOVED 1234 123.456.789:1111
Predis\Response\ServerException
出て困ったよ
AWS Elasticache
AWS の Elasticache で Redis のインスタンスをたてるときは クラスタを有効するにするかオプションがある.
クラスタを有効にしない場合は,プライマリのエンドポイントと,リーダーエンドポイントの 2 つが有効になる.
クラスタを有効にした場合は,設定エンドポイントというものが有効になる.
見た感じはプライマリのエンドポイントは DB でいうところのマスターで,リーダーエンドポイントはスレーブのような感じ.書き込みと読み込みで違うエンドポイントを使う感じ.
一方,設定エンドポイントはそれ単体で読み書きに使え,サーバーサイドでクラスタが組んであるので,クライアント側で意識する必要はなさそうだ.
PHP の 2 つの Redis クライアント実装
PHP で Redis を扱う場合は,だいたい phpredis か Predis のどちらかを使ってる例が多い.
phpredis は PHP エクステンションで,C で実装されているので速度が期待できる.
一方,Predis は PHP の実装なので,ネイティブコードよりは速度は期待できないかもしれないが,エクステンションを入れる必要がないので,よりポータブルであることが特徴.
Laravel の Redis
ファサード はデフォルトでは Predis の実装を使ってるようだ.ただし,コンフィグ次第で実装は phpredis に変更できる.
クラスタの注意点
クラスタでハマったポイントがある.
どうにもクラスタを組む場合は,通常の単体のインスタンス (Standalone) とは勝手が異なる点がありそうである.
ちなみにこれらは Predis を使用していて出会った問題である.
データベース番号
1 つ目は データベースを変更できない こと.
Redis はデータベース番号というものを持っていて,0〜15 の 16 個のデータベースを切り替えられる.
MySQL でいうとデータベースともいえそうだが,テーブルのほうが単位としては近いかもしれない.
で,クラスタを組むとデータベース番号を変更すること (SELECT
) ができない.SELECT
するとエラー (Predis だと例外) が出てしまい,クラスタモードだとデータベース指定ができないと言われてしまう.
Uncaught Predis\Connection\ConnectionException:
SELECT
failed: ERR SELECT is not allowed in cluster mode
なので DB 番号は 0 で固定になる.
参考:
Redis Cluster does not support multiple databases like the stand alone version of Redis. There is just database 0 and the SELECT command is not allowed.
Redis Cluster Specification - https://redis.io/topics/cluster-spec#implemented-subset
SCAN
これは疑わしいのだが,キーをある条件で絞り込んで取得する SCAN
が使えないというものだ.
Uncaught Predis\NotSupportedException: Cannot use 'SCAN' with redis-cluster
なぜ疑わしいかというと,GUI のクライアントでは SCAN
が普通につかえているし,SCAN
できないというのはどうにも不便で納得がいかない.
なので,僕の実装がまずいという説が濃厚である.ここはもう少し調べる必要がありそうだ.
Scan is a command for single redis node. If you do want to use it in cluster, first get nodes list in the cluster, and run scan for each node.
この StackOverflow のエントリでは SCAN
は 1 つのノードに対しての操作なので,クラスタ内のノードすべてを走査する必要があるとのことだ.
サーバーサイドのクラスタで面倒みてくれないとなると,ノードのリストを取ってくるようなことをしないといけないのだろうか…….要調査.
ちなみに Predis は Predis\ClientInterface
に scan
メソッドがあるので,このメソッドで SCAN
コマンドを発行できる.ただ,カーソルを管理してループする必要があるので,ある程度の量を取得するときは,イテレータを使うのが便利そうだ (Predis\Collection\Iterator\KeySpace
)
→ 使用例: https://github.com/nrk/predis/blob/v1.1.1/examples/redis_collections_iterators.php#L43-L47
MOVED <ポート番号> <IPアドレス>:<ポート>
という例外が出る
クラスタの設定エンドポイントを指定して操作をしていると,たまに MOVED 1234 123.456.789:1111
のような Predis\Response\ServerException
の例外が出ることがある.「たまに」は3回に2回エラーで,1回は成功する程度の頻度.
結論から言うと,これは Predis のクラスタモードでの接続先指定を誤っていたからだった.
通常,Predis\Client
インスタンスを new
するときは,次のようにして使う:
<?php
use Predis\Client;
function provideClient(): Client
{
$parameters = [
'host' => '<エンドポイント>',
'port' => '<ポート番号>',
'database' => 0,
];
return new Client($parameters);
}
Standalone モードで 1 つのノードを指定する場合はこれでいいが,クラスタモードで接続する場合は,コンストラクタの2番めの引数にオプションの連想配列を指定しないといけない:
<?php
use Predis\Client;
function provideClient(): Client
{
$parameters = [
'host' => '<エンドポイント>',
'port' => '<ポート番号>',
'database' => 0,
];
$options = [
'cluster' => 'redis',
];
return new Client($parameters, $options);
}
これでサーバーサイドのクラスタを指定できる…と思いきやこれではダメである.
実際のところ,クラスタの場合は接続先の $parameters
は複数の接続先の配列を指定しないといけない.AWS Elasticache の Redis クラスタがサーバーサイドで 1 つの管理されたエンドポイントであろうと.
<?php
use Predis\Client;
function provideClient(): Client
{
$parameters = [
[
'host' => '<エンドポイント>',
'port' => '<ポート番号>',
'database' => 0,
]
];
$options = [
'cluster' => 'redis',
];
return new Client($parameters, $options);
}
こうすることで,MOVED ...
もクライアント側で正しく処理されてエラーが出なくなった.
ちなみにこの原因には Laravel の Redis
ファサードの実装を GitHub で確認して気づいた.Laravel さまさまである.
→ https://github.com/illuminate/redis/blob/5.1/Database.php#L31-L35