Недавно мы обнаружили интересную проблему в Redis Cluster. Потратив много времени на отладку и тестирование, мы смогли сократить использование памяти Redis на 25% в некоторых кластерах, изменив срок действия ключа.
Twitter запускает несколько внутренних служб кэширования. Один из них реализован Redis. Некоторые важные данные о случаях использования Twitter, такие как данные о показах и взаимодействиях, счетчики расходов на рекламу и прямые сообщения, хранятся в нашем кластере Redis.
фон проблемы
Еще в начале 2016 года команда Twitter Cache внесла ряд обновлений в архитектуру Redis Cluster. В Redis произошли некоторые изменения, включая обновление Redis версии 2.4 до версии 3.2. После этого обновления возникло несколько проблем, таких как пользователи начали видеть, что использование памяти не соответствует тому, что они ожидали или были готовы использовать, увеличение задержки и проблемы с очисткой ключей. Очистка ключей — это большая проблема, которая может привести к удалению данных, которые должны быть сохранены, или к отправке запросов в исходное хранилище данных.
Предварительное расследование
Затронутая команда и команда тайника начали первоначальное расследование. Мы обнаружили, что увеличение задержки связано с очисткой ключей, которая происходит сейчас. Когда Redis получает запрос на запись, но не имеет памяти для записи, он останавливает свои действия, очищает ключ и затем сохраняет новый ключ. Однако нам все еще нужно выяснить, что вызывает увеличение использования этой недавно очищенной памяти.
Подозреваем, что память заполнена просроченными, но еще не удаленными ключами. Некоторые люди предлагают использовать сканирование, метод сканирования прочитает все ключи и позволит удалить ключи с истекшим сроком действия.
В Redis ключи имеют два метода истечения срока действия: активный и пассивный. Сканирование вызовет пассивное истечение срока действия ключа, при чтении ключа будет проверяться TTL, если TTL истек, TTL будет удален и ничего не будет возвращено. Активное истечение срока действия ключей в версии 3.2 описано в документации Redis. Активное истечение срока действия ключа начинается с функции под названием activeExpireCycle. Он запускается несколько раз в секунду по внутреннему таймеру, называемому cron. Что делает функция activeExpireCycle, так это выполняет итерацию по каждому пространству ключей, проверяя случайный kry с установленным TTL, и, если процентное пороговое значение для просроченного kry достигнуто, повторяет этот процесс до тех пор, пока не будет соблюдено ограничение по времени.
Этот метод сканирования всех крыс сработал, и когда сканирование было выполнено, использование памяти также уменьшилось. Похоже, что срок действия ключей Redis больше не истекает. Однако решение в то время состояло в том, чтобы увеличить размер кластера и больше оборудования, чтобы ключи распределялись больше и было больше доступной памяти. Это разочаровывает, потому что вышеупомянутый проект по обновлению Redis уменьшает размер и стоимость запуска этих кластеров, делая их более эффективными.
Версия Redis: что изменилось?
Реализация activeExpireCycle изменилась между версиями Redis 2.4 и 3.2. В Redis 2.4 каждая база данных проверялась при каждом запуске, а в Redis 3.2 было достигнуто максимальное количество баз данных, которые можно было проверить. В версии 3.2 также появилась возможность быстрой проверки базы данных. «Медленный» запускается по таймеру, а «быстрый» запускается перед проверкой событий в цикле событий. Период быстрого истечения будет возвращаться раньше при определенных условиях, а также имеет более низкие пороговые значения времени ожидания и функции выхода. Сроки также проверяются чаще. Всего к этой функции было добавлено 100 строк кода.
Дальнейшее расследование
Недавно у нас было время вернуться и вернуться к этой проблеме использования памяти. Мы хотели выяснить, почему происходит регрессия, а затем посмотреть, как мы можем лучше реализовать истечение срока действия ключа. Нашей первой мыслью было то, что в Redis так много ключей, что выборки из 20 недостаточно. Еще одна вещь, которую мы хотели изучить, — это влияние ограничений базы данных, введенных в Redi 3.2.
То, как вы масштабируете и обрабатываете сегменты, делает запуск Redis в Twitter уникальным. У нас есть ключевые пространства, содержащие миллионы ключей. Это редкость для пользователей Redis. Осколки представлены пространством ключей, поэтому каждый экземпляр Redis может иметь несколько осколков. В нашем экземпляре Redis много ключевых пробелов. Шардинг в сочетании с масштабом Twitter создает плотный бэкэнд с большим количеством ключей и баз данных.
Улучшения просроченных тестов
Количество выборок в каждом цикле определяется переменной
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
В тесте есть один элемент управления и три тестовых экземпляра для выборки большего количества ключей. 500 и 200 произвольные. Значение 300 — это результат калькулятора, основанный на размере статистической выборки, где общее количество ключей — это размер совокупности. На графике выше, даже просто взглянув на начальное количество тестовых экземпляров, видно, что они работают лучше. Разница между этим и процентом запущенных сканирований показывает, что накладные расходы на ключи с истекшим сроком действия составляют около 25%.
Хотя выборка большего количества ключей помогает нам найти больше ключей с истекшим сроком действия, отрицательный эффект задержки больше, чем мы можем себе позволить.
График выше показывает задержку 99,9% в миллисекундах. Это показывает, что задержка коррелирует с увеличением выборки ключей. Оранжевый представляет значение 500, зеленый — 300, синий — 200, а элемент управления — желтый. Линии соответствуют цветам в таблице выше.
Увидев, что на задержку влияет размер выборки, я задаюсь вопросом, можно ли автоматически настроить размер выборки в зависимости от того, сколько ключей истекает. Задержка уменьшается, когда истекает срок действия большего количества ключей, но когда работы больше нет, мы сканируем меньше ключей и выполняем быстрее.
Идея в основном работает, мы видим более низкое использование памяти, отсутствие влияния на задержку и метрику, отслеживающую размер выборки, показывающую, что он увеличивается и уменьшается с течением времени. Однако мы не приняли это решение. Это решение привело к некоторым всплескам задержки, которых не было в нашем контрольном экземпляре. Код также немного сложен, трудно объясним и не интуитивно понятен. Нам также приходилось настраивать каждый неоптимальный кластер, потому что мы хотели избежать усложнения работы.
Исследуйте соответствие между версиями
Мы также хотели исследовать изменения между версиями Redis. В новой версии Redis представлена переменная CRON_DBS_PER_CALL. Эта переменная устанавливает максимальное количество баз данных для проверки при каждом запуске этого cron. Чтобы проверить действие этой переменной, мы просто закомментировали эти строки.
//if (dbs_per_call > server.dbnum || timelimit_exit)dbs_per_call = server.dbnum;
Это сравнивает эффект проверки всех баз данных за прогон с ограничениями и без них. Результаты наших тестов очень впечатляют. Однако наш тестовый экземпляр имеет только одну базу данных, и логически эта строка кода не делает разницы между модифицированной и немодифицированной версиями. Переменные всегда установлены.
99,9% в микросекундах. Немодифицированный Redis выше, а модифицированный Redis ниже.
Мы начали исследовать, почему комментирование этой строки имеет такое огромное значение. Поскольку это оператор if, наше первое подозрение — предсказание ветвления. мы используем
gcc’s__builtin_expect
Затем мы смотрим на сгенерированную сборку, чтобы увидеть, что именно происходит.
Мы компилируем заявление о если в три важных инструкция MOV, CMP и JG. MOV загрузит некоторую память в реестр, CMP будет сравнивать два регистрия и установить другой регистр на основе результата, JG выполнит условный скачок на основе значения другого регистра. Код для перехода к тому, что будет кодом в блоке IF или Block. Я вынимаю заявление IF и поместил собранную сборку в Redis. Затем я проверяю влияние каждой инструкции, комментируя различные линии. Я проверил инструкцию MOV, чтобы увидеть, была ли проблема производительности с загрузкой памяти или кэш CPU, но не нашел никакой разницы. Я проверил команду CMP и не нашел никакой разницы. Когда я запускаю тест с включенной директивой JG, задержка возвращается на немодифицированный уровень. После нахождения этого я проверил, если бы это был просто прыжок, или определенная инструкция JG. Я добавил безоговорочную инструкцию JMP JMP, которая прыгает, а затем прыгает назад, чтобы запустить код без штрафа производительности.
Мы провели некоторое время, глядя на различные показатели эффективности и попробуйте несколько пользовательских индикаторов ЦП, перечисленные в руководстве. О том, почему такая директива приведет к вопросам производительности, у нас нет никаких заключений. При выполнении прыжка у нас есть некоторые идеи и в кэш-памяти к кэше и поведению, связанное с CPU, но время работает короткое, если возможно, мы вернемся к этому в будущем.
разрешение
Теперь, когда мы хорошо понимаем причину проблемы, нам нужно выбрать решение проблемы. Мы решили сделать простую модификацию, чтобы иметь возможность настроить стабильный размер выборки в параметрах запуска. Таким образом, мы можем найти хороший баланс между задержкой и использованием памяти. Даже если удаление оператора if приведет к такому значительному улучшению, нам будет очень трудно внести изменения, если мы не сможем объяснить, почему.
На этом графике показано использование памяти первым развернутым кластером. Верхняя линия (розовая), скрытая оранжевым цветом, представляет собой среднее использование памяти кластера. Оранжевая верхняя строка — это экземпляр элемента управления. Средняя часть диаграммы — тренд нового изменения. В третьем разделе показан пример перезапуска элемента управления по сравнению со светло-желтым цветом. После перезапуска использование памяти элементом управления быстро увеличивается.
Это был довольно большой опрос с участием инженеров и нескольких команд, и сокращение размера кластера на 25 % было очень хорошим результатом, и мы многому научились! производительность и какие оптимизации мы можем сделать с помощью других команд настройки.
Среди других инженеров, внесших значительный вклад в исследование, Майк Барри, Рашми Рамеш и Барт Робинсон.
- end -
Автор: Мэтью Теджо.
Перевод: Сюй Е
Эта статья воспроизведена в Интернете, пожалуйста, проштампуйте оригинальный текст:Improving Key Expiration in Redis