RedisのString型は今でも本当に512MBが上限か?

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)))  

ドキュメントにも修正依頼を出しておきました。

My Twitter & RSS

Leave a Reply

Your email address will not be published. Required fields are marked *