phpredis チートシート

今度isuconに参加するので、きっと使うであろう [phpredis](https://github.com/phpredis/phpredis) のチートシートを作ってみた。
名前が似ていてややこしいけど [predis](https://github.com/nrk/predis) では無いのでご注意。
predisはphp実装のRedisクライアントで、今回のphpredisはc言語実装のより速いRedisクライアント。

## Redisのデータ型
phpredis以前に、Redisの前提としてどんなデータ型があるのか頭に入れておきたい。
Redisには5種類のデータ型がある。
文字列型とハッシュ型さえ知っておけばだいたい事足りると思うけど、他の３つもわりと便利っぽい。

* 文字列型(String)
* リスト型(List): 双方向リスト、keyの持たないハッシュっていうか、配列みたいなやつって覚えればいいよ
* セット型(Set): 順不同の集合、値が重複しないようになっているので、重複のないリスト作るのに便利
* ソート済みセット型(ZSet): 自動的にソートされるセット型、簡易的なランキングを作るのに便利
* ハッシュ型(Hash): key=>valueないわゆる普通のハッシュ

文字列型以外の4つの末端のデータは全て文字列型になる。
要するにこれらは文字列を構造的に扱うためのデータ型で、結局Redisは全てのデータを文字列として扱うように見える。

## 初期化関連
```php
// 接続
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// データベース切り替え（isuconで複数つかうんかな・・・）
$redis->select(1);

// データ全部消す
// returnは常にTRUE
$redis->flushAll();

// データベース単位で全部消す
// returnは常にTRUE
$redis->flushDb();
```

## データ型とは関係ないコマンドのチートシート

```php
// 条件を満たすkeyを全部取得する、'*'がワイルドカード
// 返ってくるのはkeyだけで、valueもとりたいならこの後別のコマンドが必要
$allKeys = $redis->keys('*');	// 全部のkeyがarrayで返ってくる
$keyWithUserPrefix = $redis->keys('user*'); // 'user'ってprefixがついてるkeyがarrayで返ってくる

// 対象のkeyのデータ型を取得する
// Redisクラスで定義されてる定数で返ってくる
// string: Redis::REDIS_STRING set: Redis::REDIS_SET list: Redis::REDIS_LIST zset: Redis::REDIS_ZSET hash: Redis::REDIS_HASH other: Redis::REDIS_NOT_FOUND
$redis->type('key');
```

## 文字列型のチートシート
バイナリセーフなので、実は画像とかシリアライズされたphpオブジェクトとかなんでも入れられちゃうらしい。
ここに書いてないけど、valueの末尾に追加(append)したり、valueの一部だけをとってくるsubstrっぽいやつ(range)とかもある。

```php
// ゲット、あれば文字列が、なければFALSEが返ってくる
$redis->get('key');

// セット、成功すればTRUEが返ってくるけど、失敗時のreturnは不明（ドキュメントに書かれていない）
$redis->set('key', 'value');
// Redis2.6.12以降ならexpireを秒単位で指定できる
$redis->set('key', 'value', 10);

// 既存がない時だけセット
// 成功したらTRUE、失敗したらFALSE
$redis->setNx('key', 'value');

// 置換、置換前のvalueがreturnされる
$beforeValue = $redis->getSet('key', 'newValue');

// 削除、一度に複数削除できて、配列で指定する
// 削除できたkeyの数がreturnされる
$redis->delete(['key3', 'key4']); // return 2

// 存在確認、あればTRUE、なければFALSE・・・らしいんだけど実際にやってみると1と0が返ってくる
$redis->exists('key'); // return 1

// マルチゲット、なかったkeyの部分はFALSEが返ってくる
$redis->mGet(['key1', 'non_exist_key', 'key5']); // return ['value1', `FALSE`, 'value5']

// マルチセット
// 成功したらTRUE、失敗したらFALSEらしいけど、失敗ってどこまでを指してるのだろうか？
$redis->mSet(['key0' => 'value0', 'key1' => 'value1']);
```

## リスト型のチートシート
先頭または末尾からプッシュしたりポップしたりできる配列っぽいデータ型。
例えばSNSの足跡機能みたいなやつで時系列のデータが次々に来て、
その順番通りに取り出したい時なんかに便利かもしれない。

```php
// 要素の先頭へプッシュ(left push)
// 逆に末尾につけたい場合はrPushを使う
$redis->delete('key1');
$redis->lPush('key1', 'C'); // returns 1
$redis->lPush('key1', 'B'); // returns 2
$redis->lPush('key1', 'A'); // returns 3
$redis->lRange('key1', 0, -1); // return [ 'A', 'B', 'C' ]

// 要素の先頭からポップ(left pop)
// 逆に末尾から獲りたい場合はrPopを使う
$redis->lPop('key1'); // return 'A'
$redis->lRange('key1', 0, -1) // return [ 'B', 'C' ]

// 1個だけゲット、配列をインデックスでアクセスするみたいに第２引数に数値渡せばいい
// ポップではないので元の配列から除去されない
$redis->lGet('key1', 0); // return 'B'

// 範囲でゲット、第2、第3引数にstart~endのインデックス番号を渡す
// start: 0, end: -1にすれば全部取れる
// returnはarrayで返ってくる
$redis->lRange('key1', 0, -1); // ['B', 'C']

// 配列サイズの取得
$redis->lSize('key1'); // return 2
```

## セット型
重複のない集合。
既存と同じvalueを突っ込んでも数が増えたりしない。
またリスト型と違って順番という概念がないので、「先頭からn件とってくる」といったことはできない。
例えばSNSの友達機能などは重複を許さず、順番の概念が無いデータなので、そういう時に便利かも。
ここには書いてないけど、他のセット型とマージしたりとか、diffをとったりもできるらしい。便利。

```php
// 追加、一度に複数入れられる、追加した数がreturnされる
// 既存と重複した分は追加されず、returnの数値にも加算されない
$redis->sAdd('key1' , 'value1', 'value2'); // return 2

// 全部取得、arrayで返ってくる
$redis->sMembers('key1'); // ['value1', 'value2']

// メンバの数を取得
$redis->sCard('key1'); // return 2

// valueの存在確認、あればTRUE、なければFALSE
$redis->sIsMember('key1', 'value1'); // return true

// 削除、一度に複数削除できる
$redis->sRem('key1', 'value1', 'value2'); // return 2
```

## ソート済みセット型
セット型の各要素にscoreという値も入れ、取得するときはその値でソートされた順番で取得できるデータ型。
簡易的なランキングを作る時とか便利そう。
scoreにはfloat型を渡せとある。とりあえず整数いれときゃいいんじゃないかな。

```php
// 追加、第２引数がscoreで、取得時はこの値でソートされた結果が返ってくる
$redis->zAdd('key', 1, 'value1'); // return 1
$redis->zAdd('key', 0, 'value0'); // return 1
$redis->zAdd('key', 5, 'value5'); // return 1
$redis->zRange('key', 0, -1); // return ['value0', 'value1', 'value5']

// score加算、対象のvalueが存在しない場合は0スタートでaddするので、zAddの代替として使っても良い？
// 加算後のscoreがfloat型でreturnされる
$redis->zIncrBy('key', 1, 'value1'); // return 2.0

// 範囲で取得、scoreでソートされた結果が返ってくる
// 第2、第3引数で取得したい順位（scoreの値ではない）を範囲指定する、順位は0始まり
// scoreの値も欲しい場合は第4引数にtrueを渡す
// ちなみに同率4位が2つあって、4位までを取得すると片方は除去されてしまう
$redis->zRange('key', 0, -1); // return ['value0', 'value1', 'value5']
$redis->zRange('key', 0, -1, true); // return ['value0' => 0.0, 'value1' => 2.0, 'value5' => 5.0]

// scoreの値指定で範囲取得、第2、第3引数にscoreの範囲を指定する
$redis->zRangeByScore('key', 0, 3); // return ['value0', 'value1']

// 要素数を取得、第2、第3引数で指定された範囲のscoreを持つ要素の数が返ってくる(valueは返ってこない)
$redis->zCount('key', 0, 3); // return 2

// 指定したvalueの順位を取得、順位は0始まり
$redis->delete('key');
$redis->zAdd('key', 1, 'one');
$redis->zAdd('key', 2, 'two');
$redis->zRank('key', 'one'); // return 0
$redis->zRank('key', 'two'); // return 1
```

## ハッシュ型のチートシート
ゲットするにもhGetとhGetAllの2通りあるのが罠。
基本的にhMSetとhGetAllだけ使ってりゃいいんじゃないかな。

```php
// セット、returnが微妙
// 既存がなければ1が、あれば既存を置換した上で0が、セットに失敗したらFALSEが返ってくる
$redis->hSet('key', 'hashKey0', 'value0'); // return 1
$redis->hSet('key', 'hashKey1', 'value1'); // return 1

// ゲット、ハッシュの中のkey1つ分とってくる
// あれば文字列が、なければFALSEが返ってくる
$value = $redis->hGet('key', 'hashKey0'); // return 'value0'

// ハッシュ全体をとってくる
// phpのarray型で返ってくる、なければFALSEが返ってくる
$array = $redis->hGetAll('key'); // return ['hashKey0' => 'value0', 'hashKey1' => 'value1']

// 既存がない時だけセット
// セットされたらTRUE、失敗したらFALSEが返ってくる
$redis->hSetNx('key', 'hashKey2', 'value2'); // return true

// マルチセット、複数回hSet実行するくらいならこっち使ったほうが全然早いらしいんで、基本こっち使ったほうが良いかも
// 参考: https://qiita.com/ndxbn/items/11e3895389d5c7c1ff80
$redis->hMSet('user:1', ['name' => 'Joe', 'salary' => 2000]); // return true

// 削除、hashKey単位の削除なので、keyごと削除したいならdeleteつかってね
// 複数個まとめて削除もできるよ
$redis->hDel('key', 'hashKey1', 'hashKey2'); // return 2

// hashKeyの一覧を取得する、valueまでは返ってこないよ
// arrayで返ってくる
$redis->hKeys('user:1'); // return ['name', 'salary']

// valueの一覧を取得する、keyまでは返ってこない、hGetAllで別にいい気がする
// arrayで返ってくる
$redis->hVals('user:1'); // return ['Joe', '2000']
```

## まとめ
リスト型・セット型・ソート済みセット型は、やろうと思えばハッシュ型で同じような動作は実現できる。
これらはハッシュ型を小目的に特化させるために機能を少なくしたものだと捉えることができると思っている。
（なにぶんRedisを使う機会が少ないので自信をもっては言えない

ただネストが一段減ることでデータ量が減ったり、アプリケーションのコード量を減らしたりはできると思っているので、
機会があれば積極的に使っていこう。