RedisのString型は今でも本当に512MBが上限か?
Redis本を執筆しました。こちらも是非合わせてご覧ください。
---------------------------------------------------------------------------------------------------------------------------------------------------------
RedisのString型が512MBが上限であると言われる事が多いことをご存知の方も多いかもしれません。 実際に、Redisの公式ドキュメント(An introduction to Redis data types and abstractions)にも以下のように記載されています。
- キーの制限
The maximum allowed key size is 512 MB.
- 値の制限
A value can’t be bigger than 512 MB.
この512MB制限は、キーと値のそれぞれ両方に適用される制限です。理由はRedisの文字列はSDS(Simple Dynamic Strings)で管理されており、バージョン1の制限によります。バージョン2でその制限がなくなりましたが、512MBの制限撤廃のためにはRedis側でも追随する必要がありました。
一方で、String型の512MBのIssueがクローズされていることを確認しました。すなわち、今現在は512MBの制限がなくなったようです。Redis 4.0.7でproto-max-bulk-len
ディレクティブが追加されているので、このタイミングと考えられます。
関連するディレクティブ(パラメーター)として2種類あり、proto-max-bulk-len
は実際に実行するコマンドのバイト数の制限、client-query-buffer-limit
はRESPのフォーマットを含めたバイト数で制限されます。これらを調整しながら確認していきます。
では、実際に検証して10GBのようなデータでも値として送ることができるのか確認してみましょう。今回はredis-pyを使用して確認します。インストールしていない場合は、pip3 install redis
などで事前にインストールしておいてください。
検証は、MacOSおよびAWSのEC2インスタンスでUbuntu, 20.04 LTS(オレゴンリージョンのAMI ami-03d5c68bab01f3496)、インスタンスタイプはr5.24xlargeのものを使用しました。
最初はMacOS上で検証します。最初からEC2上で検証しても問題ありません。
string512.py
という名前で以下のように512MiBの値をString型として保存する検証用コードを準備します。
import redis
client = redis.Redis(host = '127.0.0.1', port = 6379)
client.set('str512', "a" * int(512 * 1024 * 1024))
print(client.strlen('str512'))
以下のように実行します。
$ python string512.py
536870912
512MBより大きい値を送ると以下のエラーが返ってきます。
$ python string512.py
Traceback (most recent call last):
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 706, in send_packed_command
sendall(self._sock, item)
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/_compat.py", line 9, in sendall
return sock.sendall(*args, **kwargs)
OSError: [Errno 41] Protocol wrong type for socket
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/hayashier/string512.py", line 4, in <module>
client.set('str512', "a" * (512 * 1024 * 1024 + 1))
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/client.py", line 1801, in set
return self.execute_command('SET', *pieces)
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/client.py", line 900, in execute_command
conn.send_command(*args)
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 725, in send_command
self.send_packed_command(self.pack_command(*args),
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 717, in send_packed_command
raise ConnectionError("Error %s while writing to socket. %s." %
redis.exceptions.ConnectionError: Error 41 while writing to socket. Protocol wrong type for socket.
そこで、proto-max-bulk-len
ディレクティブの値を512mb
から10gb
に変更します。すると、512MiB+1Bのデータも送信できるようになります。
$ python string512.py
536870913
今度は1GB以上の値を送ると以下のエラーが返ってきます。
$ python string512.py
Traceback (most recent call last):
File "/Users/hayashier/string512.py", line 4, in <module>
client.set('str512', "a" * (1 * 1024 * 1024 * 1024))
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/client.py", line 1801, in set
return self.execute_command('SET', *pieces)
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/client.py", line 901, in execute_command
return self.parse_response(conn, command_name, **options)
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/client.py", line 915, in parse_response
response = connection.read_response()
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 739, in read_response
response = self._parser.read_response()
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 324, in read_response
raw = self._buffer.readline()
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 256, in readline
self._read_from_socket()
File "/Users/hayashier/.pyenv/versions/3.9.0/lib/python3.9/site-packages/redis/connection.py", line 201, in _read_from_socket
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
redis.exceptions.ConnectionError: Connection closed by server.
Redisサーバー側には以下のメッセージが表示されます。
43159:M 20 Sep 2021 14:03:27.540 # Closing client that reached max query buffer length: id=7 addr=127.0.0.1:55539 laddr=127.0.0.1:6379 fd=9 name= age=1 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=1073741826 qbuf-free=835572 argv-mem=9 obl=0 oll=0 omem=0 tot-mem=1074594857 events=r cmd=NULL user=default redir=-1 (qbuf initial bytes: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
そこでclient-query-buffer-limit
ディレクティブの値を1gb
から11gb
に変更します。前述の通り、client-query-buffer-limit
はRESPのフォーマットを含めたバイト数で制限されますので、少し多めに確保しています。
1GBでもデータを送ることができるようになりました。
$ python string512.py
1073741824
ここからは検証サイズが大きくなってきましたので、ローカル端末ではなく、大きめのEC2インスタンスを立てて検証します。
次に、4GBより大きな値を送ると以下のようにエラーが返ってきました。
$ python3 string512.py
Killed
freeコマンドを定期的に取ると、メモリーを使い切ってそうです。
$ free -m
total used free shared buff/cache available
Mem: 15823 14257 1430 0 134 1327
Swap: 0 0 0
$ free -m
total used free shared buff/cache available
Mem: 15823 14799 889 0 134 785
Swap: 0 0 0
$ free -m
total used free shared buff/cache available
Mem: 15823 15323 364 0 134 261
Swap: 0 0 0
$ free -m
total used free shared buff/cache available
Mem: 15823 11105 4662 0 55 4519
Swap: 0 0 0
$ free -m
total used free shared buff/cache available
Mem: 15823 8157 7609 0 55 7466
Swap: 0 0 0
そこでr5.24xlargeに変更に変更したところ(その前はr5.12xlargeで検証していました)、無事10GiBのデータも送ることができるようになりました。
$ python3 string512.py
10737418240
値だけではなく、キーの512MB制限について試す場合も同様に検証できます。コードを以下のように修正するのみです。
import redis
client = redis.Redis(host = '127.0.0.1', port = 6379)
client.set("a" * int(10 * 1024 * 1024 * 1024), 'value512')
print(client.strlen("a" * int(10 * 1024 * 1024 * 1024)))
ドキュメントにも修正依頼を出しておきました。