На проектах мы часто сталкиваемся с различными кейсами. Хочу поделиться одной из таких историй.

Беда пришла нежданно. Пару месяцев назад, в конце рабочего дня, когда я уже успел выключить комп и погрузился мыслями в вечерние планы, со мной связались коллеги из соседнего департамента. У их заказчика начал сбоить сервер баз данных Redis на Open Source: наблюдались дикие тормоза и потеря производительности.

Входные данные: есть три узла Redis, Standalone и две реплики, конфигурация по дефолту.

В какой-то момент Redis сильно «раздулся»: его дамп был огромен (исчислялся гигами) и на диск писался долго — показывал время запросов 2-3 секунды, не добегала репликация, видны были висящие в непонятном состоянии курсоры с тайм-аутом в час.

Стало понятно: никто особо ничего не настраивал, не ожидая подвоха и не сильно вдаваясь в рекомендации производителя. Похожих ситуаций у нас было несколько, поэтому хотим поделиться опытом решения проблемы и предостеречь коллег от неприятных ситуаций.

Что было дальше, описываю под катом.

Первое, с чего начали, — проверили железо. Диски SSD, памяти немало, да и с процами все OK.

Вторым шагом решили проанализировать загруженность системы через iostat. Но и тут без проблем:

Linux 3.10.0-1062.el7.x86_64 ()      22.12.2022           _x86_64_                (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle

                 3,94    0,00    0,78    0,01    0,00   95,27

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

fd0               0,00     0,00    0,00    0,00     0,00     0,00     8,00     0,00   41,00   41,00    0,00  41,00   0,00

sdd               0,00     0,00    0,00    0,00     0,00     0,00    69,23     0,00    0,86    0,86    0,00   0,73   0,00

sdb               0,00     0,00    0,00    0,00     0,00     0,00    87,65     0,00    1,16    1,16    0,00   1,01   0,00

sda               0,00     0,72    0,02    4,67     2,72  1850,34   790,51     0,02    4,29    7,33    4,27   0,37   0,17

sdc               0,00     0,28    0,00    0,47     0,04     3,14    13,52     0,00    0,68   11,13    0,63   0,37   0,02

dm-0              0,00     0,00    0,02    5,38     2,71  1850,34   685,30     0,02    3,84    7,35    3,82   0,32   0,17

dm-1              0,00     0,00    0,00    0,74     0,04     3,14     8,51     0,00    0,70   17,79    0,64   0,23   0,02

 

avg-cpu:  %user   %nice %system %iowait  %steal   %idle

                 12,64    0,00    0,61    0,00    0,00   86,75

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

fd0               0,00     0,00    0,00    0,00     0,00     0,00     0,00     0,00    0,00    0,00    0,00   0,00   0,00

sdd               0,00     0,00    0,00    0,00     0,00     0,00     0,00     0,00    0,00    0,00    0,00   0,00   0,00

sdb               0,00     0,00    0,00    0,00     0,00     0,00     0,00     0,00    0,00    0,00    0,00   0,00   0,00

sda               0,00     1,02    0,00    5,72     0,00    26,93     9,42     0,00    0,18    0,00    0,18   0,18   0,11

sdc               0,00     0,28    0,00    0,43     0,00     2,93    13,54     0,00    0,00    0,00    0,00   0,00   0,00

dm-0              0,00     0,00    0,00    6,73     0,00    26,93     8,00     0,00    0,17    0,00    0,17   0,16   0,11

dm-1              0,00     0,00    0,00    0,72     0,00     2,93     8,19     0,00    0,00    0,00    0,00   0,00   0,00

Стало понятно, что дело не в железе, а в конфигурации Redis.

Следующим шагом мы сразу приступили к тюнингу конфигурации. Сперва ограничили потребление памяти. Это было очевидным решением, потому что максимальная память должна составлять 70% от объема системы, чтобы она не занимала все ресурсы сервера. По умолчанию стояло значение 0, Redis съедал всю возможную память, сервер вис и падал.

maxmemory <~70% от максимального значения ram>

Затем поменяли политику, чтобы вытеснялись значения, которые дольше всего не запрашивались. Возможными значениями политики могут быть:

  • volatile-lru — вытесняет значения, которые дольше всего не запрашивались, у которых задан срок действия;

  • allkeys-lru — вытесняет значения, которые дольше всего не запрашивались;

  • volatile-lfu — вытесняет значения, которые наименее часто используются, у которых задан срок действия;

  • allkeys-lfu — вытесняет значения, которые наименее часто используются;

  • volatile-random — вытесняет случайные ключи, у которых задан срок действия;

  • allkeys-random — вытесняет случайные ключи;

  • volatile-ttl — вытесняет ключи, у которых истекает срок действия;

  • noeviction — отключает вытеснение, будет просто возвращаться ошибка.

Нам было важно, чтобы вытеснялись значения, которые не запрашивались дольше всего. Поэтому выбрали maxmemory-policy: allkeys-lru.

Далее поставили тайм-аут для соединений: timeout 150.

В конфигурации Redis настройка этого значения не позволяет тратить слишком много времени на подключение. В нашем случае мы настроили так, чтобы соединение клиента закрывалось через 150 секунд.

Затем ограничили количество клиентов: maxclients 40000.
Максимальное количество единовременных клиентов. По нашему опыту, лучше поставить значение побольше.

В завершение мы настроили мониторинг, чтобы отслеживать количество ключей и видеть всю картину происходящего: redis-cli info keyspace.

Добавили в мониторинг метрику, позволяющую в реальном времени наблюдать за активностью и доступностью Redis: redis-cli info stats | grep ops.

Для того, чтобы анализировать количество команд, обработанных за секунду, мы настроили также параметр instantaneous_ops_per_sec. Отслеживая его, можно увидеть аномальное поведение, например, плавные и резкие падения до нуля, что может сигнализировать о блокирующих клиентах.

Конечно, при дальнейшем наблюдении стоит откорректировать и другие параметры. Но в случае критической ситуации, когда выходить из положения нужно «здесь и сейчас», такого минимального решения проблем вполне хватит.

На будущее мы советуем задуматься о переходе на кластер. Это повысит отказоустойчивость, уменьшит время переноса данных между узлами и перераспределит нагрузку между ними.

Павел Глазков

Инженер-проектировщик вычислительных комплексов "Инфосистемы Джет"

Комментарии (6)


  1. fugasio
    06.07.2023 14:30
    +5

    А 40000 клиентов обращающихся к редису это что?


    1. JetHabr Автор
      06.07.2023 14:30

      40000 это одновременное количество коннектов


  1. trublast
    06.07.2023 14:30

    maxmemory-policy: allkeys-lru

    То есть когда редис будет практически заполнен, и вы добавите туда новый ключ, то при добавлении следующего нового ключа ваш только что добавленный сразу будет удален? Ведь он наименее "популярный". Спорно.

    redis-cli info stats | grep ops

    А чем не устроил redis-exporter?

    и на диск писался долго

    Тема aof vs rdb не раскрыта. Как и тема diskless репликации. И шардирования

    показывал время запросов 2-3 секунды

    И многопоточности.

    Можно наверное что-то про это добавить.

    А какого размера редис если не секрет?

    А пробовали хранить сжатые данные?


    1. onyxmaster
      06.07.2023 14:30
      +2

      LRU работает не так.


    1. JetHabr Автор
      06.07.2023 14:30

      Проблему нужно было решать максимально быстро. Пробовать все варианты возможности и времени у нас не было


  1. kekoz
    06.07.2023 14:30

    При всё том, что основной тезис статьи — “Дефолтная конфигурация — упавший сервис” — должен быть огромными буквами написан на всех стенах, на которые хотя бы изредка падают взоры администраторов, смутил всё же один момент:

    Redis съедал всю возможную память, сервер вис и падал

    и

    iostat. Но и тут без проблем:

    Только мне кажется, что это — несколько противоречивые факты? redis сожрал всю память до висячей и падучей, а в статистиках iostat — всё прелестно?