RESP (REdis Serialization Protocol)の調査

はじめに
---
下記の資料を参考にしている。

- https://redis.io/topics/protocol
- https://redis.io/topics/pipelining

大半が英訳となっているが、英語に難があるので意訳という形でごまかしている。

途中に出てくる例は、上記資料の例を引用している。

Redis プロトコルを読む目的
---
Redis の Pub/Sub コマンドを実装するために必要な情報を収集するため。


RESPとは
----
RESPとは、 `REdis Serialization Protocol` の略。RESPは、次の事項を実現している。

- シンプルな実装
- 高速にパース
- 人間でも読める


RESP の立ち位置。数字が大きくなるに連れて、物理層からアプリケーション層に近づく感じ。

1. TCP/IP
1. RESP
1. Redis コマンド

ネットワークレイヤ
----
TCP/6379 ポートを使用。もしくはUnixソケットなどのTCPと同等のストリームで使用される必要がる。


リクエスト-レスポンスモデル
----
サーバは、クライアントから要求に対して処理が完了するまで結果を返さない。
ただし、下記２つの例外がある。

1.Redisはパイプラインをサポートしているので、クライアントは複数のコマンドを一度に送信してからレスポンスを待つ。
2. クライアントが Pub/Sub チャンネルを購読する時、プッシュプロトコルに変化し、***クライアントはコマンドを送信しなくなる。*** サーバが素早くメッセージを送信するため。

以上２つを除けばシンプルなリクエスト-レスポンスモデル。


RESP の詳細
----

RESP は、次のデータ型をシリアライズするプロトコル。

- 文字列
- エラー
- 整数
- 長い文字列(Bulk String)
- 配列

クライアントは、 コマンドをRESP のBulk Stringの配列としてサーバに送っている。
サーバは、RESPの型のうち１つで応答する

RESPの型は、先頭バイトで判別できる

- 先頭バイト `+` は、文字列用
- 先頭バイト `-` は、エラー用
- 先頭バイト `:` は、整数用
- 先頭バイト `$` は、Bulk String用
- 先頭バイト `*` は、配列用

以上に加えて、後述するNull値が存在する。
RESPの部品は、"\r\n" (CRLF) で終了する。


文字列
----

文字列には、 "\r" (CR)や "\n" (LF) を含まれない。改行が許されていない。

使用用途は、小さなオーバーヘッドでバイナリではない文字列を送るために使用する。例えば、サーバが "OK"レスポンスを返す場合は、5バイトの文字列になる。

```
+OK\r\n
```


エラー
----
実のところエラーは、文字列と変わりないが、 `+` の代わりに `-` で始まる。簡単な例。

```
-Error message\r\n
```

エラーを受け取ったら、クライアントは、例外として扱ったら良い。


整数
----
`:` で始まって数字が続き、CRLFで終了する。例えば、 ":0\r\n" や ":1000\r\n" 。
符号付き64bit整数値が保証うされている。
真偽値で利用される。1なら真、0なら偽。



Bulk String
----
512MBを上限とする単一バイナリ。
ここで言う512MBとは、512*1024*1024=536870912bytesである。redis-3.2.5のソースコードを参照した。

Bulk String は、次の方法でエンコードされる。

- "$" で始まり、構成するバイト数が付き、CRLFで終わる。
- 文字列データ
- CRLF で終わる

文字列 `foobar` は、次のようにエンコードされる。

```
$6\r\nfoobar\r\n
```

***Null値も存在する。*** 長さは-1で、データ無し。


```
$-1\r\n
```

Null Bulk String と呼ばれる。
クライアントライブラリは、空文字列ではなく、nilオブジェクトを返すと良い。RubyだったらNil、CだったらNULLかフラグで示すと良い。


配列
----
クライアントは、配列を使ってRedisサーバへコマンドを送る。Redisコマンドは、クライアントを配列を使って集合の要素を返す。例えば、LRANGEコマンドは、リストの要素を返す。
配列は次のフォーマットを使う。

- "*" で始まり、10進数で表記された要素数が付き、CRLFで終わる
- 続く型は、すべて配列の要素

空配列は、次の通り。

```
*0\r\n
```


"foo" と "bar" のBulk Stringを２つ持つ配列は次のようにエンコードされる

```
*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
```

3つの整数を持つ配列は次のようにエンコードされる

```
*3\r\n:1\r\n:2\r\n:3\r\n
```

同じ型でなくても複数の型を持つ配列も可能。

```
*5\r\n
:1\r\n
:2\r\n
:3\r\n
:4\r\n
$6\r\n
foobar\r\n
```

見やすくなるように改行している。

Null配列は -1 でカウントしている。

```
*-1\r\n
```

クライアントライブラリAPIは、空配列ではなくNullオブジェクトを返すと良い。空配列とと条件を分けるため。実例として、 [BLPOP](https://redis.io/commands/blpop)コマンドのタイムアウト条件がある。

配列の配列も可能。

```
*2\r\n
 *3\r\n
  :1\r\n
  :2\r\n
  :3\r\n
 *2\r\n
  +Foo\r\n
  -Bar\r\n
```

見やすいように改行とインデントを含めた。実際は含まれない。


Null要素を持つ配列
----

例えば、

```
*3\r\n
$3\r\n
foo\r\n
$-1\r\n
$3\r\n
bar\r\n
```

２番目の要素が Null。クライアントライブラリは次のように返す。

```
["foo",nil,"bar"]
```


Redisサーバへコマンド送信
----

以上で、Redis のクライアントライブラリを実装できる。ここからは、クライアントとサーバどのように動くか示す。

- クライアントは、RedisサーバへBulk Stringから成る配列を送る
- Redisサーバは応答として、正当なデータをクライアントへ返答する。

クライアントが、`mylist`キーに含まれるリストの長さを取得するためのコマンド `LLEN mylist` 送ると、サーバは、次のように整数で返答する。(Cはクライアント、Sはサーバ)

```
C: *2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n 
S: :48293\r\n
```


複数コマンドとパイプライン
----

クライアントは、複数コマンドを出すために同じ接続を使い回すことができる。パイプラインは、クライアントが1つの書き込み(write)操作で複数コマンド送信をサポートしている。

パイプライン
----
この章では、https://redis.io/topics/pipelining の `Redis Pipeline`を参照した。

このパイプライン方式は、POP3プロトコルでもすでにサポートされている。メールをサーバから劇的に早くダウンロードするために使われている。

Redisはパイプラインを初期からサポートしているので、どのRedisバージョンでもパイプラインを利用できる。この例では、netcatを使った例

```
(printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
```

すべてのコールに RTT(Round Trip Time) のコストをは楽事無く、1度に3コマンドを呼び出せる。

***重要!***: クライアントが、パイプラインを使ってコマンドを送っている間、サーバは、メモリを使って強制的に返答をキューする。なので、パイプラインを使ってたくさんのコマンドを送信する必要があれば、程よい数で送ってやると良い。10,000コマンド送って、返答を読んだ後に再度10,000コマンドを送るなど。全部送るのと速度はほぼ変わらないが、メモリ確保容量が異なってくる。


インラインコマンド
----
たまに、`telnet` の手打ちでRedisサーバへコマンド送信する必要がある。

Redisは、インラインコマンドフォーマットと呼ばれる特別なコマンドを用意している。

次の例は、インラインコマンドを使ったサーバ/クライアント間の会話である。

```
C: PING
S: +PONG
```

次の例は、もう一つの整数を返すインラインコマンド。

```
C: EXISTS somekey
S: :0
```

基本的に、telnetセッションにおいてスペースで分割された引数を簡単にかける。`*`で始まるコマンドでなく統一されていないのものの、 Redisは、この条件を検知できコマンドをパースできる。


まとめ
----
速そう。あと、Redisコマンドはリストを送って結果を受け取っているのでLispな感じがした。

Pub/Subコマンドは、パイプラインの仕組みを使ってい実現している。実装できそうな気がしてきた。まずは、PING/PONGコマンドの対応から着手した方が良いかも。