4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS Elasticache Redis クラスタ + Predis での注意点

Last updated at Posted at 2019-07-02

Redis まわりのメモです.最近触ってるので.間違いなどあったらおしえてください

tl;dr

  1. AWS Elasticache で Redis のクラスタを組むよ
  2. PHP では 2 つの Redis クライアント実装が有名だよ
  3. クラスタには注意点があるよ
  4. クラスタと DB 番号の制限があったよ
  5. クラスタで SCAN できなくて困ったよ
  6. Predis で MOVED 1234 123.456.789:1111 Predis\Response\ServerException 出て困ったよ

AWS Elasticache

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

ElastiCache_Management_Console.png

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

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

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

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

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

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

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\ClientInterfacescan メソッドがあるので,このメソッドで 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 するときは,次のようにして使う:

provide.php
<?php 

use Predis\Client;

function provideClient(): Client 
{
    $parameters = [
        'host'     => '<エンドポイント>',
        'port'     => '<ポート番号>',
        'database' => 0,
    ];

    return new Client($parameters);
}

Standalone モードで 1 つのノードを指定する場合はこれでいいが,クラスタモードで接続する場合は,コンストラクタの2番めの引数にオプションの連想配列を指定しないといけない:

provide2.php
<?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 つの管理されたエンドポイントであろうと.

provide3.php
<?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

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?