Redis まわりを試したメモ (AWS Elasticache + PHP with Predis)

Redis まわりのメモです.最近触ってるので.

AWS Elasticache

AWS の Elasticache で Redis のインスタンスをたてるときは クラスタを有効するにするかオプションがある.

クラスタを有効にしない場合は,プライマリのエンドポイントと,リーダーエンドポイントの 2 つが有効になる.

クラスタを有効にした場合は,設定エンドポイントというものが有効になる.

見た感じはプライマリのエンドポイントは DB でいうところのマスターで,リーダーエンドポイントはスレーブのような感じ.書き込みと読み込みで違うエンドポイントを使う感じ.

一方,設定エンドポイントはそれ単体で読み書きに使え,サーバーサイドでクラスタが組んであるので,クライアント側で意識する必要はなさそうだ.

PHP の 2 つの Redis クライアント実装

PHP で Redis を扱う場合は,だいたい phpredisPredis のどちらかを使ってる例が多い.

phpredisPHP エクステンションで,C で実装されているので速度が期待できる.

一方,PredisPHP の実装なので,ネイティブコードよりは速度は期待できないかもしれないが,エクステンションを入れる必要がないので,よりポータブルであることが特徴.

Laravel の Redis ファサード はデフォルトでは Predis の実装を使ってるようだ.ただし,コンフィグ次第で実装は phpredis に変更できる.

クラスタの注意点

クラスタでハマったポイントがある.

どうにもクラスタを組む場合は,通常の単体のインスタンス (Standalone) とは勝手が異なる点がありそうである.

ちなみにこれらは Predis を使用していて出会った問題である.

データベース番号

1 つ目は データベースを変更できない こと.

Redis はデータベース番号というものを持っていて,0〜15 の 16 個のデータベースを切り替えられる.

MySQL でいうとデータベースともいえそうだが,テーブルのほうが単位としては近いかもしれない.

で,クラスタを組むとデータベース番号を変更すること (SELECT) ができない.SELECT するとエラー (Predis だと例外) が出てしまい,クラスタモードだとデータベース指定ができないと言われてしまう.

なので 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 が使えないというものだ.

なぜ疑わしいかというと,GUI のクライアントでは SCAN が普通につかえているし,SCAN できないというのはどうにも不便で納得がいかない.

なので,僕の実装がまずいという説が濃厚である.ここはもう少し調べる必要がありそうだ.

stackoverflow.com

この StackOverflow のエントリでは SCAN は 1 つのノードに対しての操作なので,クラスタ内のノードすべてを走査する必要があるとのことだ.

サーバーサイドのクラスタで面倒みてくれないとなると,ノードのリストを取ってくるようなことをしないといけないのだろうか…….要調査.

ちなみに Predis は Predis\ClientInterfacescan メソッドがあるので,このメソッドで SCAN コマンドを発行できる.ただ,カーソルを管理してループする必要があるので,ある程度の量を取得するときは,イテレータを使うのが便利そうだ (Predis\Collection|Iterator|KeySpace)

使用例

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

感想

Redis は話しに聞くにいろいろと経験して勉強が必要そうだが、いろいろと学びがある……かもしれない.


👉 よろしければ Twitter でフォロー をお願いいたします.