Redis: основные параметры конфигурации и тюнинг производительности

Thank you for reading this post, don't forget to subscribe!

Рас­смот­рим основ­ные пара­мет­ры кон­фи­гу­ра­ции Redis, их зна­че­ния, как они и на что они вли­я­ют, и на какие из них сто­ит обра­тить внимание.

Места­ми совсем крат­ко, но вез­де со ссылками.

Начать, пожа­луй, сто­ит с ути­ли­ты redis-benchmark https://github.com/maxux/redis-benchmark .

Уста­нав­ли­ва­ет­ся вме­сте с сами Redis, мож­но сра­зу использовать:

root@bttrm-dev-app-1:/home/admin# redis-benchmark -p 6389 -n 1000 -c 10 -k 1
====== 1 ======
1000 requests completed in 0.03 seconds
10 parallel clients
3 bytes payload
keep alive: 1
98.30% <= 1 milliseconds
99.30% <= 2 milliseconds
100.00% <= 2 milliseconds
30303.03 requests per second

Кро­ме того — мож­но выпол­нить про­вер­ку коли­че­ства опе­ра­ций с помо­щью redis-cli и опции --latency или --latency-dist:

Redis server-level config

timeout

Отклю­чать соеди­не­ние при неак­тив­но­сти кли­ен­та задан­ное коли­че­ство секунд.

Ноль для отклю­че­ния воз­мож­но­сти (IDLE-кли­ен­ты будут висеть до пере­за­груз­ки, или пока не отклю­чат­ся сами):

timeout 0

В целом — есть смысл оста­вить дефолт­ные 300 секунд, что бы не оста­ва­лись завис­шие клиенты.

tcp-keepalive

См. Redis Clients Handling.

Не вли­я­ет при PUBLISH/SUBSCRIBE (real-time operations), см. Redis Pub/Sub: Intro Guide и Redis Pub/Sub… How Does it Work?

Сер­вер отправ­ля­ет ACK-запро­сы (Acknowledgment) через ука­зан­ный про­ме­жу­ток вре­ме­ни в секун­дах, под­дер­жи­вая сессию:

tcp-keepalive 300
127.0.0.1:6389> CONFIG GET tcp-keepalive
1) "tcp-keepalive"
2) "300"

 

Зна­че­ние по-умол­ча­нию — 300 секунд (но зави­сит от версии).

Если кли­ент не отве­ча­ет на запрос — соеди­не­ние закрывается.

Если и timeout, и tcp-keepalive на сто­роне сер­ве­ра будут зада­ны в 0 (отклю­че­ны) — то «мёрт­вые» под­клю­че­ния будут висеть до пере­за­груз­ки сервера.

См. Things that you may want to know about TCP Keepalives.

Про­ве­ря­ем с вклю­чен­ным tcp-keepalive (-k == 1):

root@bttrm-dev-app-1:/home/admin# redis-benchmark -p 6389 -n 1000 -c 10 -k 1 -q | tail -2
MSET (10 keys): 16129.03 requests per second

И без:

root@bttrm-dev-app-1:/home/admin# redis-benchmark -p 6389 -n 1000 -c 10 -k 0 -q | tail -2
MSET (10 keys): 7042.25 requests per second

Сно­ва-таки — не вижу смыс­ла в отклю­че­нии про­вер­ки вооб­ще, пото­му — остав­ля­ем дефолт­ные 300 секунд.

RDB Persistence

Созда­ёт пол­ную копию базы. См. Redis Persistence.

Пове­де­ние опре­де­ля­ет­ся пара­мет­ром save (см. так­же Redis save, SAVE и BGSAVE).

Про­ве­ря­ем:

127.0.0.1:6379> CONFIG GET save
1) "save"
2) "3600 1 300 100 60 10000"

 

Уда­ля­ем:

127.0.0.1:6379> CONFIG SET save ""
OK
127.0.0.1:6379> CONFIG GET save
1) "save"
2) ""

Сохра­ня­ем:

127.0.0.1:6389> CONFIG rewrite
OK

 

В слу­чае, если Redis исполь­зу­ет­ся про­сто для кеши­ро­ва­ния — мож­но отклю­чить: уби­ра­ем save из кон­фи­га вообще.

При этом сам меха­низм RDB будет исполь­зо­вать­ся для син­хро­ни­за­ции мастер-слейв при исполь­зо­ва­нии репли­ка­ции (см. Redis: репли­ка­ция, часть 1 — обзор. Replication vs Sharding. Sentinel vs Cluster. Топо­ло­гия Redis).

AOF persistence

Append Only File — сохра­ня­ет в лог каж­дую операцию.

Ана­ло­гич­но RDB, если Redis исполь­зу­ем про­сто, как кеш — лог не нужен.

Для отклю­че­ния — меня­ем пара­метр appendonly, и зада­ём его в no:

appendonly no

maxmemory

maxmemory зада­ёт мак­си­маль­ный раз­мер памя­ти сер­ве­ра, кото­рый будет досту­пен Redis-у для хра­не­ния данных.

См. Using Redis as an LRU cache.

Может быть задан в виде %:

127.0.0.1:6389> CONFIG SET maxmemory 80
OK

 

Или мегабайт/гигабайт:

127.0.0.1:6389> CONFIG SET maxmemory 1gb
OK
127.0.0.1:6389> CONFIG GET maxmemory
1) "maxmemory"
2) "1073741824"

 

Или в 0 для отклю­че­ния лими­та вооб­ще и явля­ет­ся зна­че­ни­ем по-умол­ча­нию для 64-х бит­ных систем. Для 32-х бит­ных — 3 ГБ.

При дости­же­нии лими­та — будут выбра­но реше­ние об уда­ле­нии дан­ных, осно­вы­ва­ясь на поли­ти­ках, см. maxmemory-policy.

Учи­ты­вая, что у нас на каж­дом хосте кро­ме само­го Redis кру­тят­ся PHP-про­цес­сы и memcached — под Redis мож­но отдать не более 50% памяти.

maxmemory-policy

Опре­де­ля­ет поли­ти­ку, кото­рая будет исполь­зо­вать Redis при дости­же­нии maxmemory.

Note: LRU — Less Recently Used

Может иметь одно из значений:

  • volatile-lru: будут уда­ле­ны наи­ме­нее исполь­зу­е­мые клю­чи, у кото­рых задан expire
  • allkeys-lru: будут уда­ле­ны наи­ме­нее исполь­зу­е­мые клю­чи неза­ви­си­мо от expire
  • volatile-random: уда­лить слу­чай­ный ключ с задан­ным expire
  • allkeys-random: уда­лить слу­чай­ный ключ неза­ви­си­мо от expire
  • volatile-ttl: уда­лить ключ с наи­мень­шим остав­шим­ся TTL
  • noeviction: не выпол­нять очист­ку вооб­ще, про­сто воз­вра­щать ошиб­ку при опе­ра­ци­ях записи

Для про­вер­ки зна­че­ние expire — исполь­зуй­те TTL/PTTL:

127.0.0.1:6379> set test "test"
OK
127.0.0.1:6379> ttl test
(integer) -1
127.0.0.1:6379> pttl test
(integer) -1
127.0.0.1:6379> EXPIRE test 10
(integer) 1
127.0.0.1:6379> ttl test
(integer) 8
127.0.0.1:6379> ttl test
(integer) 7
127.0.0.1:6379> ttl test
(integer) 7

 

В нашем слу­чае — деве­ло­пе­ры не уве­ре­ны, что expire зада­ёт­ся для всех клю­чей, а учи­ты­вая, что сно­ва-таки — это про­сто сер­вис кеши­ро­ва­ния, дан­ные из кото­ро­го поте­рять не страш­но — то зада­ём maxmemory-policy allkeys-lru.

unixsocket

Если при­ло­же­ние и Redis рабо­та­ют на одном хосте — попро­буй­те исполь­зо­ва­ние соке­та вме­сто TCP-порта.

Зада­ём:

unixsocket /tmp/redis.sock
unixsocketperm 755

Может дать хоро­ший при­рост про­из­во­ди­тель­но­сти, см. Tuning Redis for extra Magento performance.

Про­ве­рим с redis-benchmark.

Созда­ём тесто­вый конфиг:

unixsocket /tmp/redis.sock
unixsocketperm 775
port 0

Запус­ка­ем с сокетом:

root@bttrm-dev-console:/etc/redis-cluster# redis-server /etc/redis-cluster/test.conf
[simterm]
Про­ве­ря­ем:
[simterm]
root@bttrm-dev-console:/home/admin# redis-benchmark -s /tmp/redis.sock -n 1000 -c 100 -k 1
90909.09 requests per second

 

И через TCP-порт:

root@bttrm-dev-console:/home/admin# redis-benchmark -p 7777 -n 1000 -c 100 -k 1
66666.67 requests per second

 

loglevel

Уро­вень дета­ли­за­ции лога. При debug — наи­бо­лее подроб­ный, сле­до­ва­тель­но — отни­ма­ет боль­ше ресурсов.

Может быть (от наи­бо­лее подроб­но­го — к наи­ме­нее): debugverbosenoticewarning.

Зна­че­ние по-умол­ча­нию — notice, пока сер­вис в про­цес­се допи­ли­ва­ния — мож­но оста­вить notice, поз­же — пере­клю­чить в warning.

OS-level config

Transparent Huge Page

Меха­низм ядра Linux, поз­во­ля­ю­щий умень­шить коли­че­ство объ­ек­тов при выде­ле­нии и управ­ле­нии вир­ту­аль­ной памя­тью. См Transparent Hugepages: measuring the performance impactDisable Transparent Hugepages и Latency induced by transparent huge pages.

В Redis, судя по доку­мен­та­ции>>>, име­ет зна­че­ние толь­ко при вклю­чен­ном RDB, но име­ет смысл отклю­чить вообще:

Про­ве­рить теку­щее зна­че­ние можно:

root@bttrm-dev-app-1:/home/admin# cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

 

Зна­че­ние в скоб­ках явлет­ся теку­щим задан­ным зна­че­ни­ем, а дан­ном слу­чае — madvice.

madvice ука­зы­ва­ет на исполь­зо­ва­ние THP толь­ко в слу­чае, если это явно запро­ше­но при­ло­же­ни­ем через вызов madvice().

Про­ве­рить состо­я­ние мож­но так:

root@bttrm-dev-app-1:/home/admin# grep HugePages /proc/meminfo
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0

 

maxclients и fs.file-max

Зада­ёт мак­си­маль­ное коли­че­ство одно­вре­мен­но под­клю­чен­ных клиентов.

Зна­че­ние по умол­ча­нию — 10.000, пере­опре­де­ля­ет­ся через maxclients, см. Maximum number of clients.

При этом Redis про­ве­ря­ет лими­ты опе­ра­ци­он­ной систе­мы на мак­си­маль­ное коли­че­ство фай­ло­вых дескрип­то­ров — sysctl fs.file-max гло­баль­но для ядра:

root@bttrm-dev-app-1:/home/admin# sysctl fs.file-max
fs.file-max = 202080
root@bttrm-dev-app-1:/home/admin# cat /proc/sys/fs/file-max
202080

 

И ulimit для поль­зо­ва­те­ля на каж­дый процесс:

root@bttrm-dev-app-1:/home/admin# ulimit -Sn
1024

 

Для systemd-based систем мож­но задать лимит через systemd-юнит файл Redis с помо­щью LimitNOFILE:

root@bttrm-dev-app-1:/home/admin# cat /etc/systemd/system/redis.service | grep LimitNOFILE
LimitNOFILE=65535

 

tcp-backlog и net.core.somaxconn

Redis зада­ёт огра­ни­че­ние оче­ре­ди под­клю­че­ния кли­ен­тов в пара­мет­ре tcp-backlog (по-умол­ча­нию 511).

При этом — опе­ра­ци­он­на яси­сте­ма про­ве­ря­ет соб­ствен­ный лимит — net.core.somaxconn, и если он мень­ше, чем лимит Redis — будет выда­но пре­ду­пре­жде­ние вида:

The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128

Что такое TCP backlog и net.core.somaxconn

Что бы понять зна­че­ние tcp-backlog и роль net.core.somaxconn — вспом­ним про­цесс уста­нов­ле­ния сес­сии и нача­ла пере­да­чи данных:

  1. сер­вер: при­ло­же­ние на сер­ве­ре выпол­ня­ет listen(), пере­да­вая в listen() пер­вым аргу­мен­том фай­ло­вый дескрип­тор соке­та, а вто­рым — раз­мер accept backlog (зна­че­ние tcp-backlog из redis.conf)
  2. кли­ент: при­ло­же­ние кли­ен­та выпол­ня­ет connect() и отправ­ля­ет сер­ве­ру SYN-пакет
    1. на сто­роне кли­ен­та соеди­не­ние пере­хо­дит в состо­я­ние SYN_SENT
    2. на сто­роне сер­ве­ра новое соеди­не­ние пере­хо­дит в состо­я­ние SYN_RCVD, и попа­да­ет в оче­редь syn backlog (net.ipv4.tcp_max_syn_backlog) — incomplete connection queue
  3. сер­вер: отправ­ля­ет SYN+ACK
  4. кли­ент: отправ­ля­ет ACK, и пере­во­дит соеди­не­ние в ESTABLISHED
  5. сер­вер: при­ни­мет ACK, и пере­во­дит соеди­не­ние в ESTABLISHED, пере­ме­щая его в accept backlog — complete connection queue
  6. сер­вер вызы­ва­ет accept(), пере­да­вая ему соеди­не­ние из оче­ре­ди accept backlog
  7. кли­ент: вызы­ва­ет write(), и начи­на­ет пере­да­чу данных
  8. север: вызы­ва­ет read(), и полу­ча­ет данные

Соб­ствен­но, если в listen() Redis-а зна­че­ние backlog пере­да­ёт­ся боль­ше, чем оно зада­но в лими­тах ядра — net.core.somaxconn — и вызы­ва­ет­ся сообе­ще­ние «TCP backlog setting cannot be enforced«.

128 явля­ет­ся зна­че­ни­ем по-умолчанию:

root@bttrm-dev-app-1:/home/admin# sysctl net.core.somaxconn
net.core.somaxconn = 128

 

Зада­ём через -w:

root@bttrm-dev-console:/home/admin# sysctl -w net.core.somaxconn=65535
net.core.somaxconn = 65535
root@bttrm-dev-console:/home/admin# sysctl -p

 

См. TCP connection backlog — a struggling server и TCP Three-Way Handshake.

vm.overcommit_memory

Навер­но, самый неод­но­знач­ный параметр.

Крайне реко­мен­дую посмот­реть пост Redis: fork — Cannot allocate memory, Linux, вир­ту­аль­ная память и vm.overcommit_memory — про вир­ту­аль­ную память, систем­ные вызо­вы и Redis.

См. так­же overcommit_memory и См. Background saving fails with a fork() error under Linux even if I have a lot of free RAM.

overcommit_memory игра­ет роль при выпол­не­нии опе­ра­ций созда­ний снап­шо­тов дан­ных из памя­ти на диск, а кон­крет­но — при вызо­ве BGSAVE.

В нашем слу­чае, с отклю­чен­ным RDB и AOF, когда Redis исполь­зу­ет­ся толь­ко для кеши­ро­ва­ния, и хра­нить дан­ные на дис­ке смыс­ла нет — нет смыс­ла и в изме­не­нии overcommit_memory, и сле­ду­ет оста­вить его в зна­че­нии по-умол­ча­нию — 0.

А уж если хочет­ся задать лимит вруч­ную — то луч­ше исполь­зо­вать overcommit_memory == 2, и огра­ни­че­ние по %, что бы все­гда оста­вал­ся запас памяти.

vm.swappiness

Если опе­ра­ци­он­ной систе­ме раз­ре­ше­но исполь­зо­ва­ние SWAP — она может сдем­пить часть дан­ных Redis на диск, и когда Redis попро­бу­ет их счи­тать — это вызо­вет задерж­ку, т.к. ему при­дёт­ся ждать, пока систе­ма не счи­та­ет дан­ные с дис­ка обрат­но в память.

Во избе­жа­ние — отклю­ча­ем SWAP вообще:

root@bttrm-dev-console:/home/admin# sysctl -w vm.swappiness=0