На проектах мы часто сталкиваемся с различными кейсами. Хочу поделиться одной из таких историй.
Беда пришла нежданно. Пару месяцев назад, в конце рабочего дня, когда я уже успел выключить комп и погрузился мыслями в вечерние планы, со мной связались коллеги из соседнего департамента. У их заказчика начал сбоить сервер баз данных 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. Отслеживая его, можно увидеть аномальное поведение, например, плавные и резкие падения до нуля, что может сигнализировать о блокирующих клиентах.
Конечно, при дальнейшем наблюдении стоит откорректировать и другие параметры. Но в случае критической ситуации, когда выходить из положения нужно «здесь и сейчас», такого минимального решения проблем вполне хватит.
На будущее мы советуем задуматься о переходе на кластер. Это повысит отказоустойчивость, уменьшит время переноса данных между узлами и перераспределит нагрузку между ними.
Павел Глазков
Инженер-проектировщик вычислительных комплексов "Инфосистемы Джет"