Диагностика медленных команд Redis: контрольный список производительности

Практический контрольный список для поиска медленных команд Redis с помощью SLOWLOG, MONITOR, инструментов задержки, сложности команд и безопасных исправлений.

Диагностика медленных команд Redis: контрольный список производительности

Медленные команды Redis обычно начинаются как обычные команды, которые перерастают свои предположения. Вызов SMEMBERS был безвреден, когда в наборе было 200 элементов. Запрос к панели управления был нормальным, когда он загружал 50 ключей. Lua-скрипт был быстрым, пока один клиент не создал гораздо больший объем данных, чем все остальные.

Полезный вопрос не просто "какая команда медленная?", а "какая форма данных сделала эту команду медленной, и почему приложение просит Redis выполнить так много работы за один раз?"

Понимание производительности Redis

Производительность Redis, как правило, исключительна благодаря его природе in-memory. Однако несколько факторов могут способствовать задержке команд:

  • Сложность команды: Некоторые команды по своей сути более ресурсоемки, чем другие (например, KEYS для большого набора данных против GET).
  • Размер и структура данных: Большие списки, наборы или отсортированные наборы, а также сложные структуры данных могут влиять на производительность команд, которые с ними работают.
  • Сетевая задержка: Хотя это напрямую не проблема команды, высокая сетевая задержка между клиентом и сервером может сделать команды кажущимися медленными.
  • Нагрузка на сервер: Высокая загрузка ЦП, недостаточный объем памяти или другие процессы на сервере Redis могут снизить производительность.
  • Блокирующие команды: Некоторые операции могут блокировать цикл событий Redis, влияя на все последующие команды.

Выявление медленных команд с помощью SLOWLOG

Команда SLOWLOG — это встроенный механизм Redis для регистрации команд, превышающих заданное время выполнения. Это ваш основной инструмент для активного выявления проблемных команд.

Как работает SLOWLOG

Redis поддерживает циклический буфер, который хранит информацию о командах, выполнение которых заняло больше времени, чем пороговое значение slowlog-log-slower-than (в микросекундах). Порог по умолчанию обычно составляет 10 миллисекунд (10000 микросекунд). Когда этот буфер заполняется, старые записи удаляются.

Ключевые подкоманды SLOWLOG

  • SLOWLOG GET [count]: Извлекает последние count записей из медленного журнала. Если count опущен, извлекаются все записи.
  • SLOWLOG LEN: Возвращает текущую длину медленного журнала (количество записей).
  • SLOWLOG RESET: Очищает записи медленного журнала. Используйте эту команду с осторожностью, так как она безвозвратно удаляет записанные данные.

Пример использования SLOWLOG

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

# Подключитесь к вашему экземпляру Redis
redis-cli

# Получите последние 5 медленных команд
127.0.0.1:6379> SLOWLOG GET 5

Вывод будет выглядеть примерно так:

1) 1) (integer) 18
   2) (integer) 1678886400
   3) (integer) 15000
   4) 1) "KEYS"
      2) "*"

2) 1) (integer) 17
   2) (integer) 1678886390
   3) (integer) 12000
   4) 1) "SMEMBERS"
      2) "my_large_set"

...

Пояснение вывода:

  1. ID записи: Уникальный идентификатор записи медленного журнала.
  2. Временная метка: Временная метка Unix, когда команда была выполнена.
  3. Время выполнения: Продолжительность (в микросекундах), которую заняло выполнение команды.
  4. Команда и аргументы: Сама команда и ее аргументы.

В приведенном выше примере KEYS * занял 15000 микросекунд (15 мс), а SMEMBERS my_large_set занял 12000 микросекунд (12 мс). Они будут считаться медленными, если ваш slowlog-log-slower-than установлен на 10000 микросекунд.

Настройка slowlog-log-slower-than

Вы можете динамически изменять порог slowlog-log-slower-than с помощью команды CONFIG SET:

127.0.0.1:6379> CONFIG SET slowlog-log-slower-than 50000  # Регистрировать команды медленнее 50 мс

Чтобы сделать это изменение постоянным после перезапуска Redis, вам нужно изменить файл redis.conf и перезапустить сервер Redis или использовать CONFIG REWRITE для сохранения изменений в файле конфигурации.

Мониторинг команд в реальном времени с помощью MONITOR

В то время как SLOWLOG предоставляет исторический обзор, MONITOR предлагает поток всех команд, выполняемых сервером Redis, в реальном времени. Это бесценно для отладки в определенный период медленной производительности или для понимания шаблонов трафика команд.

Как работает MONITOR

Когда вы включаете MONITOR, Redis отправляет ответ клиенту MONITOR для каждой команды, которую он получает и обрабатывает. Это может генерировать очень большой объем вывода, особенно на загруженных экземплярах Redis. Поэтому обычно рекомендуется использовать MONITOR экономно и только при активной отладке.

Пример использования MONITOR

Из отдельного сеанса redis-cli выполните команду MONITOR:

# Подключитесь к вашему экземпляру Redis в *отдельном* терминале
redis-cli

# Начните мониторинг
127.0.0.1:6379> MONITOR

Теперь любая команда, выполненная в другом сеансе redis-cli или вашим приложением, появится в выводе MONITOR. Например, если вы выполните SET mykey myvalue в другом клиенте, вы увидите:

1678887000.123456 [0 127.0.0.1:54321] "SET" "mykey" "myvalue"

Использование MONITOR для отладки

  1. Воспроизведите проблему: Когда вы заметите замедление, немедленно запустите MONITOR в выделенном сеансе redis-cli.
  2. Запустите медленную операцию: Попросите ваше приложение выполнить действие, которое, по вашему мнению, вызывает замедление.
  3. Проанализируйте вывод: Наблюдайте за командами в потоке MONITOR. Ищите:
    • Команды, которые появляются с большой задержкой (хотя MONITOR сам по себе не показывает время выполнения, вы можете сделать вывод, замеряя время команд вручную или наблюдая задержки).
    • Необычные или неожиданные выполняемые команды.
    • Большой объем команд, которые могут перегружать сервер.
  4. Остановите мониторинг: Нажмите Ctrl+C, чтобы выйти из команды MONITOR.

Важно: Не запускайте MONITOR в производственной среде в течение длительного времени, так как это может значительно повлиять на производительность Redis из-за накладных расходов на отправку каждой команды клиенту.

Распространенные причины медленных команд и способы их исправления

Основываясь на информации, полученной из SLOWLOG и MONITOR, вот распространенные виновники и их решения:

1. Команда KEYS

  • Проблема: Команда KEYS перебирает все пространство ключей, чтобы найти ключи, соответствующие шаблону. В базах данных с миллионами ключей это может занять очень много времени и заблокировать сервер Redis, влияя на всех остальных клиентов.
  • Решение: Избегайте KEYS в больших производственных пространствах ключей. Используйте SCAN, когда вам нужен инкрементальный перебор ключей. SCAN возвращает подмножество ключей, соответствующих шаблону, в каждом вызове, что снижает вероятность длительной блокировки сервера.
      # Вместо KEYS user:*
      redis-cli -h <host> -p <port> SCAN 0 MATCH user:* COUNT 100
    
    Вам нужно будет вызывать SCAN несколько раз, используя курсор, возвращенный предыдущим вызовом, пока курсор не вернется к 0.

2. Сложные скрипты (Lua-скрипты)

  • Проблема: Долго выполняющиеся или неэффективные Lua-скрипты, выполняемые через EVAL или EVALSHA, могут блокировать сервер. Хотя Redis выполняет скрипты атомарно, один длинный скрипт может монополизировать цикл событий.
  • Решение: Оптимизируйте свои Lua-скрипты. Разбивайте сложную логику на более мелкие, управляемые скрипты. Анализируйте производительность скриптов. Убедитесь, что циклы внутри скриптов эффективны и правильно завершаются. Проводите бенчмаркинг ваших скриптов, чтобы понять время их выполнения.

3. Операции с большими структурами данных

  • Проблема: Команды, такие как SMEMBERS для набора с миллионами элементов, LRANGE для очень длинного списка или ZRANGE для огромного отсортированного набора, могут быть медленными.
  • Решение: Избегайте извлечения целых больших структур данных. Вместо этого используйте итеративные команды или обрабатывайте данные частями:
    • Наборы: Используйте SSCAN вместо SMEMBERS.
    • Списки: Используйте LRANGE с меньшими значениями start и stop для извлечения данных постранично.
    • Отсортированные наборы: Используйте ZRANGE с LIMIT или ZSCAN.

4. Команды, требующие перебора ключей (менее распространены, но возможны)

  • Проблема: Хотя это менее распространено, команды, которые могут неявно перебирать ключи из-за своей природы, могут быть медленными, если пространство ключей велико.
  • Решение: Просмотрите справочник команд Redis для конкретной команды и поймите ее сложность. Рассмотрите альтернативные структуры данных или подходы, если конкретная команда оказывается узким местом.

5. Блокирующие команды (редко в современном Redis)

  • Проблема: В старых версиях Redis были некоторые команды, которые могли блокировать сервер. Большинство из них были исправлены или заменены.
  • Решение: Убедитесь, что вы используете последнюю версию Redis. Обратитесь к документации Redis для получения информации о любых известных блокирующих операциях, характерных для вашей версии.

Сначала решите, медленный ли Redis или клиент ждет

Когда кто-то говорит "Redis медленный", это может означать несколько разных вещей. Сервер может тратить слишком много времени на выполнение команды. Клиент может ждать в сети. Пул соединений может быть исчерпан. TLS-прокси может быть перегружен. Большой ответ может передаваться дольше, чем выполнялась сама команда.

SLOWLOG записывает только время выполнения команды внутри Redis. Он не включает время передачи по сети, время ожидания в очереди клиента или время, затраченное на ожидание соединения из пула приложения. Вот почему чистый медленный журнал не всегда доказывает, что пользователи не выдумывают задержку.

Сравните три представления:

redis-cli --latency -h <host> -p <port>
redis-cli --latency-history -h <host> -p <port>
redis-cli SLOWLOG GET 10

Если задержка высока, но SLOWLOG пуст, смотрите на сеть, пулы клиентов, насыщение ЦП сервера, активность fork, персистентность или большие ответы. Если SLOWLOG показывает повторяющиеся дорогие команды, начните с дизайна команд и структур данных.

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

Используйте сложность команды как проверку на запах

Команды Redis быстры, когда они касаются небольшого, ограниченного объема данных. Они становятся рискованными, когда сканируют большое пространство ключей, возвращают огромную коллекцию или выполняют работу, пропорциональную большому значению.

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

  • GET user:123 ограничен размером одного значения.
  • HGET profile:123 email ограничен одним поиском в хеше.
  • SMEMBERS followers:celebrity возвращает весь набор.
  • KEYS * сканирует все пространство ключей.
  • LRANGE queue 0 -1 возвращает весь список.
  • ZREMRANGEBYSCORE может удалить большое количество членов отсортированного набора.

Рискованный шаблон обычно "дай мне все". Он может работать месяцами, а затем выйти из строя, когда набор вырастет с сотен элементов до миллионов. Redis не стал внезапно медленным; данные пересекли точку, где неограниченная команда стала заметной.

Безопасные замены для распространенных медленных шаблонов

Замените команды для всего пространства ключей и всей коллекции на инкрементальные шаблоны.

Для обнаружения ключей используйте SCAN:

redis-cli --scan --pattern 'user:*'

Для наборов используйте SSCAN:

SSCAN active_users 0 COUNT 500

Для хешей используйте HSCAN:

HSCAN user:123:settings 0 COUNT 200

Для отсортированных наборов предпочитайте диапазоны с явными границами и лимитами:

ZRANGE leaderboard 0 99 WITHSCORES
ZRANGEBYSCORE events 1716600000 1716686400 LIMIT 0 500

Для списков используйте постраничный вывод с ограниченными диапазонами:

LRANGE recent_jobs 0 99

SCAN инкрементален, но это не волшебная бесплатная операция. Он может возвращать дубликаты и не дает идеально согласованного снимка, пока ключи меняются. Он хорош для обслуживания, миграции и фонового обнаружения. Обычно это не правильный примитив для пути запроса, обращенного к пользователю, которому нужен точный список в реальном времени.

Большие ответы могут быть реальной стоимостью

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

Следите за сетевым выводом и памятью клиента во время медленной операции. Если один запрос возвращает десятки или сотни мегабайт, перепроектируйте шаблон доступа. Храните сводные данные отдельно. Разбивайте результат на страницы. Используйте индекс отсортированного набора и извлекайте только видимый срез. Избегайте размещения больших документов в Redis, когда приложению обычно нужно одно поле.

Практический пример: если панель управления показывает последние 50 заданий, не храните каждый ID задания в списке и не вызывайте LRANGE jobs 0 -1 перед нарезкой в приложении. Храните список в порядке от новых к старым и запрашивайте только то, что нужно странице:

LRANGE jobs:recent 0 49

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

MONITOR — это скальпель, а не панель управления

MONITOR полезен, когда вам нужно точно увидеть, какие команды отправляет клиент, особенно когда вы подозреваете, что приложение делает что-то отличное от того, что предполагает ревью кода. Но на загруженном сервере Redis MONITOR создает накладные расходы и порождает поток вывода.

Используйте его в течение короткого контролируемого окна:

redis-cli MONITOR | head -n 200

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

INFO commandstats часто безопаснее для общего обзора:

redis-cli INFO commandstats

Он показывает количество вызовов на команду и совокупные микросекунды. Он не скажет вам, какой ключ был медленным, но может выявить, что приложение выдает гораздо больше вызовов HGETALL, KEYS или EVAL, чем ожидалось.

Lua-скрипты нуждаются в границах

Lua-скрипты мощны, потому что они выполняются атомарно внутри Redis. Это же атомарное поведение означает, что длинный скрипт блокирует другие команды, пока он выполняется. Медленные скрипты часто возникают из-за циклов по большим коллекциям, неограниченного обнаружения ключей или логики, которая выросла из крошечного помощника в мини-приложение.

Просматривайте скрипты с теми же вопросами:

  • Сколько ключей он может затронуть?
  • Сколько элементов он может перебрать в цикле?
  • Что произойдет, когда входной ключ будет содержать один миллион элементов?
  • Можно ли разделить работу на более мелкие части?
  • Возвращает ли скрипт большую полезную нагрузку?

Если скрипт появляется в SLOWLOG, сопротивляйтесь искушению просто поднять slowlog-log-slower-than. Журнал говорит вам, что один атомарный блок выполняется достаточно долго, чтобы повлиять на других клиентов.

Персистентность, форки и "медленные команды", которые являются симптомами

Иногда команды медленные, потому что Redis занят фоновой работой. Снимки RDB и операции перезаписи AOF могут увеличить загрузку ЦП, нагрузку на память и дисковый ввод-вывод. В Linux форкинг большого процесса Redis также может создавать всплески задержки, особенно когда задействованы перераспределение памяти, огромные страницы или медленное хранилище.

Проверьте:

redis-cli INFO persistence
redis-cli INFO stats
redis-cli INFO memory
redis-cli LATENCY LATEST

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

Поведение клиента может перегрузить Redis без одной плохой команды

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

Используйте конвейеризацию, когда приложению нужно много независимых команд и оно может терпеть получение ответов вместе:

GET user:1
GET user:2
GET user:3

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

Также проверьте пулы соединений. Если журналы приложения показывают, что вызовы Redis занимают 500 мс, но Redis не видит медленных команд, приложение может ждать свободного соединения. Увеличивайте пул только после проверки, почему существующие соединения заняты. Больший пул может скрыть симптом, увеличивая нагрузку на Redis.

Практический контрольный список инцидентов

Когда задержка Redis вредит пользователям, собирайте факты в таком порядке:

date -u
redis-cli PING
redis-cli --latency -i 1
redis-cli SLOWLOG GET 20
redis-cli INFO commandstats
redis-cli INFO clients
redis-cli INFO memory
redis-cli INFO persistence
redis-cli LATENCY LATEST

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

Для каждой медленной команды запишите шаблон ключа и владельца. "Медленный SMEMBERS" — это недостаточно. "Сервис рекомендаций вызывает SMEMBERS product:123:viewers для набора, который может расти без ограничений" — это действенно.

Краткий контрольный список настройки производительности

  1. Включите и контролируйте SLOWLOG: Периодически просматривайте SLOWLOG GET, чтобы выявить повторяющиеся медленные команды. При необходимости настройте slowlog-log-slower-than.
  2. Используйте MONITOR с осторожностью: Для отладки в реальном времени во время предполагаемых замедлений, но отключайте его сразу после.
  3. Избегайте KEYS в больших производственных пространствах ключей: Используйте SCAN для инкрементального перебора, когда действительно необходимо обнаружение ключей.
  4. Оптимизируйте Lua-скрипты: Убедитесь, что скрипты EVAL и EVALSHA эффективны и не выполняются чрезмерно долго.
  5. Обрабатывайте большие структуры данных итеративно: Используйте SSCAN, ZSCAN, LRANGE с лимитами или SCAN вместо извлечения целых коллекций.
  6. Анализируйте аргументы команд: Убедитесь, что аргументы, передаваемые командам, не вызывают неожиданного поведения (например, очень большие счетчики, сложные шаблоны).
  7. Контролируйте ресурсы сервера: Следите за загрузкой ЦП, памяти и сети сервера Redis. Медленные команды иногда могут быть симптомом перегруженного сервера.
  8. Оптимизации на стороне клиента: Убедитесь, что ваше приложение не отправляет команды слишком быстро или неэффективными пакетами. Рассмотрите конвейеризацию для нескольких команд, где это уместно.

Финальная проверка

Используйте SLOWLOG, чтобы найти команды, которые медленны внутри Redis, инструменты задержки, чтобы поймать всплески на стороне сервера, и тайминг приложения, чтобы поймать ожидание клиента. Затем исправьте шаблон доступа, а не только порог. Ограниченные команды, меньшие ответы, разумная пакетная обработка и четкое владение большими ключами делают для производительности Redis больше, чем погоня за разовыми изменениями настройки.