Carpe Diem

備忘録

RedisのReplication

概要

Redisの冗長構成は

の3種類がありますが、そのうちのReplicationについて説明します。

環境

  • Redis 5.0.7

Replication

Replicationはmasterからデータをreplicaにコピーし、read系コマンドをreplicaから行うことでスケーラビリティを向上させる冗長構成です。

システム図

Replicationのシステム図は以下です。

f:id:quoll00:20200309172531p:plain

5.0からslaveという言葉はやめてreplicaという呼び方に変わっているので注意してください。

  • The slave name was removed from logs and documentation, now replica is used instead.

ref: https://raw.githubusercontent.com/antirez/redis/5.0/00-RELEASENOTES

また多段(multi level)でもreplicationを組むことが可能です。

f:id:quoll00:20200309180109p:plain

これはmasterに多くのreplicaが紐づくと、トラフィックがmasterに集中してしまいよくないため、replicaでも伝播できるようにするという仕組みです。

検証

実際にローカルで構築して動作検証してみます。

f:id:quoll00:20200309185304p:plain

こんな感じの構成にします。

redis.conf

redis.confは公式サイトの以下のページから各バージョンごとに取得できます。

Redis configuration – Redis

今回は↑を少しいじって利用します。

# Redis configuration file example.
#
################################## NETWORK #####################################

# bind 127.0.0.1
protected-mode no
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300

################################# GENERAL #####################################

daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo yes

################################ SNAPSHOTTING  ################################

save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./

################################# REPLICATION #################################

replica-serve-stale-data yes
replica-read-only yes
# repl-diskless-sync yes
# repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100

################################### CLIENTS ####################################

maxclients 10000

############################## MEMORY MANAGEMENT ################################

maxmemory 128mb
maxmemory-policy noeviction

############################## APPEND ONLY MODE ###############################

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes

################################## SLOW LOG ###################################

slowlog-log-slower-than 10000
slowlog-max-len 128

############################### ADVANCED CONFIG ###############################

hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

色々設定がありますが、デフォルトと違う点は以下です。

  • bindコメントアウトしてどこからでもアクセス化
  • protected-mode noにしてbind設定を無効化可能に
  • appendonly yesにして永続化部分をmixed RDB+AOFに。mixedは4.0から使用可能です。
  • maxmemory128mb

docker-compose.yaml

docker-composeの設定です。

version: '3'

services:
  master: &base
    image: redis:alpine
    ports:
      - 6379:6379
    volumes:
      - $PWD/redis.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf
  replica_1:
    <<: *base
    ports:
      - 6380:6379
    command: redis-server /usr/local/etc/redis/redis.conf --replicaof master 6379
  replica_2:
    <<: *base
    ports:
      - 6381:6379
    command: redis-server /usr/local/etc/redis/redis.conf --replicaof master 6379
  replica_3:
    <<: *base
    ports:
      - 6382:6379
    command: redis-server /usr/local/etc/redis/redis.conf --replicaof replica_1 6379

ポイントは以下です。

  • redis.confをマウントして指定
  • --slaveofでなく--replicaofでmasterを指定
  • 多段レプリケーションのためreplica_3replica_1をmasterとして指定

動作検証

レプリケーションの確認

master

データを書き込みます。

127.0.0.1:6379> set foo 1
OK
127.0.0.1:6379> set bar 2
OK

replica_1

masterに紐付いていることが確認できます。

$ redis-cli -p 6380

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:585
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=172.22.0.5,port=6379,state=online,offset=571,lag=1
master_replid:a584a10254d68bd8467a923da89d59d950ead3d2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:585
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:585

masterで書いたデータが読み込めます。

127.0.0.1:6380> get foo
"1"
127.0.0.1:6380> get bar
"2"

しかしreplicaなので書き込みはできません。

127.0.0.1:6380> set buz 3
(error) READONLY You can't write against a read only replica.

replica_2

こちらも同様

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:658
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:cac819ca2d30046c83cbd82f1a37ef3628a70135
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:658
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:658

replica_3

replica_3replica_1を親としています。

$ redis-cli -p 6382

127.0.0.1:6382> info replication
# Replication
role:slave
master_host:replica_1  // ←replica_1になってる
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:697
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a584a10254d68bd8467a923da89d59d950ead3d2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:697
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:683

masterダウン時の可用性

masterがダウンしてもreplicaからread系コマンドは実行できます。

$ docker stop replication_master_1
replication_master_1

ログも以下のように出ます。

master_1     | 1:signal-handler (1583749670) Received SIGTERM scheduling shutdown...
master_1     | 1:M 09 Mar 2020 10:27:51.037 # User requested shutdown...
master_1     | 1:M 09 Mar 2020 10:27:51.037 * Calling fsync() on the AOF file.
master_1     | 1:M 09 Mar 2020 10:27:51.037 * Saving the final RDB snapshot before exiting.
master_1     | 1:M 09 Mar 2020 10:27:51.040 * DB saved on disk
master_1     | 1:M 09 Mar 2020 10:27:51.040 * Removing the pid file.
master_1     | 1:M 09 Mar 2020 10:27:51.040 # Redis is now ready to exit, bye bye...
replica_1_1  | 1:S 09 Mar 2020 10:27:51.059 # Connection with master lost.
replica_1_1  | 1:S 09 Mar 2020 10:27:51.059 * Caching the disconnected master state.
replica_2_1  | 1:S 09 Mar 2020 10:27:51.059 # Connection with master lost.
replica_2_1  | 1:S 09 Mar 2020 10:27:51.059 * Caching the disconnected master state.
replication_master_1 exited with code 0
replica_1_1  | 1:S 09 Mar 2020 10:27:51.946 * Connecting to MASTER master:6379
replica_2_1  | 1:S 09 Mar 2020 10:27:51.950 * Connecting to MASTER master:6379
replica_2_1  | 1:S 09 Mar 2020 10:27:51.978 # Unable to connect to MASTER: Resource temporarily unavailable
replica_1_1  | 1:S 09 Mar 2020 10:27:51.978 # Unable to connect to MASTER: Resource temporarily unavailable

replicaでコマンドを実行してみます。

replication $ redis-cli -p 6380
127.0.0.1:6380> get foo
"1"

取得できました。

replicaの昇格

一般にreplicationはreplica(slave)は昇格できないと言われています。
しかしこれは自動的に昇格しないだけであって、replicaであることをやめればmasterとして変わることが可能です。

現在はslaveになっていますが、

$ redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:master
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:125
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=172.21.0.4,port=6379,state=online,offset=125,lag=0
master_replid:907ebbc8e196a8489b8d696caa1b28837ca0f326
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:125
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:125

replicaof no oneとするとreplicaであることを止めます。

127.0.0.1:6380> replicaof no one
OK

以下のようにrole:masterになっていることが確認できます。

127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.21.0.4,port=6379,state=online,offset=290,lag=0
master_replid:2ee1d02a91695469f5b59708dd3129d0f51cbadc
master_replid2:907ebbc8e196a8489b8d696caa1b28837ca0f326
master_repl_offset:290
second_repl_offset:140
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:290

書き込みもでき、

127.0.0.1:6380> set piyo 3
OK
127.0.0.1:6380> keys *
1) "piyo"
2) "fuga"
3) "hoge"

元々のmasterには影響せず、

127.0.0.1:6379> keys *
1) "fuga"
2) "hoge"

自分の下にいるreplicaにはきちんと伝播されます。

127.0.0.1:6382> keys *
1) "fuga"
2) "hoge"
3) "piyo"

Full resyncの挙動

Replicationではmasterとreplicaの接続が途切れてから復旧すると、masterからデータをfull resyncする挙動になっています。
その際BGSAVEが実行されてデータがダンプされますが、メモリを結構使う(最大30~45%程度)ため、Redis masterで使用するメモリ使用量は50%程度にしておかないとダンプ時にメモリが枯渇します。

通常の場合

ダンプファイルを作成し、各replicaがコピーする挙動です。
Disc I/Oが遅いマシンや、トラフィックが遅いとこの処理に非常に時間がかかります。

replicaをstop -> startしたときの挙動は以下です。

master_1     | 1:M 09 Mar 2020 13:11:35.841 * Replica 172.25.0.3:6379 asks for synchronization
master_1     | 1:M 09 Mar 2020 13:11:35.841 * Full resync requested by replica 172.25.0.3:6379
master_1     | 1:M 09 Mar 2020 13:11:35.841 * Starting BGSAVE for SYNC with target: disk
master_1     | 1:M 09 Mar 2020 13:11:35.841 * Background saving started by pid 14
master_1     | 14:C 09 Mar 2020 13:11:35.844 * DB saved on disk
master_1     | 14:C 09 Mar 2020 13:11:35.844 * RDB: 0 MB of memory used by copy-on-write
master_1     | 1:M 09 Mar 2020 13:11:35.895 * Background saving terminated with success
replica_2_1  | 1:S 09 Mar 2020 13:11:35.896 * MASTER <-> REPLICA sync: receiving 196 bytes from master
master_1     | 1:M 09 Mar 2020 13:11:35.896 * Synchronization with replica 172.25.0.3:6379 succeeded

disklessの場合

disklessの場合はダンプファイルを作るのではなくソケット通信によってreplicaにデータを転送します。

################################# REPLICATION #################################

replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes  # yesに
repl-diskless-sync-delay 5  # アンコメント
repl-disable-tcp-nodelay no
replica-priority 100

replicaをstop -> startしたときの挙動は以下です。

master_1     | 1:M 09 Mar 2020 13:06:08.663 * Replica 172.25.0.3:6379 asks for synchronization
master_1     | 1:M 09 Mar 2020 13:06:08.663 * Full resync requested by replica 172.25.0.3:6379
master_1     | 1:M 09 Mar 2020 13:06:08.663 * Delay next BGSAVE for diskless SYNC
replica_2_1  | 1:S 09 Mar 2020 13:06:14.941 * Full resync from master: 0e0799975d8758bae0e3d2b41623412c854326c8:70
master_1     | 1:M 09 Mar 2020 13:06:14.941 * Starting BGSAVE for SYNC with target: replicas sockets
master_1     | 1:M 09 Mar 2020 13:06:14.941 * Background RDB transfer started by pid 13
replica_2_1  | 1:S 09 Mar 2020 13:06:14.942 * MASTER <-> REPLICA sync: receiving streamed RDB from master
master_1     | 13:C 09 Mar 2020 13:06:14.941 * RDB: 0 MB of memory used by copy-on-write
replica_2_1  | 1:S 09 Mar 2020 13:06:14.942 * MASTER <-> REPLICA sync: Flushing old data
replica_2_1  | 1:S 09 Mar 2020 13:06:14.943 * MASTER <-> REPLICA sync: Loading DB in memory
replica_2_1  | 1:S 09 Mar 2020 13:06:14.943 * MASTER <-> REPLICA sync: Finished with success
replica_2_1  | 1:S 09 Mar 2020 13:06:14.943 * Background append only file rewriting started by pid 12
replica_2_1  | 1:S 09 Mar 2020 13:06:14.973 * AOF rewrite child asks to stop sending diffs.
replica_2_1  | 12:C 09 Mar 2020 13:06:14.973 * Parent agreed to stop sending diffs. Finalizing AOF...
replica_2_1  | 12:C 09 Mar 2020 13:06:14.973 * Concatenating 0.00 MB of AOF diff received from parent.
replica_2_1  | 12:C 09 Mar 2020 13:06:14.974 * SYNC append only file rewrite performed
replica_2_1  | 12:C 09 Mar 2020 13:06:14.974 * AOF rewrite: 0 MB of memory used by copy-on-write
replica_2_1  | 1:S 09 Mar 2020 13:06:15.041 * Background AOF rewrite terminated with success
replica_2_1  | 1:S 09 Mar 2020 13:06:15.041 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
replica_2_1  | 1:S 09 Mar 2020 13:06:15.041 * Background AOF rewrite finished successfully
master_1     | 1:M 09 Mar 2020 13:06:15.042 * Background RDB transfer terminated with success
master_1     | 1:M 09 Mar 2020 13:06:15.042 # Slave 172.25.0.3:6379 correctly received the streamed RDB file.
master_1     | 1:M 09 Mar 2020 13:06:15.042 * Streamed RDB transfer with replica 172.25.0.3:6379 succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming
master_1     | 1:M 09 Mar 2020 13:06:15.751 * Synchronization with replica 172.25.0.3:6379 succeeded

こちらはパフォーマンスは良い(メモリ使用量が少なくresync時間も少ない)ですが、1台ずつしかresyncできません。

大量のデータを保つ場合はdisklessにした方が良いです。

サンプルコード

今回のサンプルコードはこちらです。

github.com

ソース