суббота, 16 июня 2012 г.

Redis в жизни

Итак, первый, а точнее второй пост (первый был про то, как я все это ненавижу) будет о редисе. Нет, не о том редисе, который выращивают бабушки на грядках своих полуразрушенных дачных участков и продают потом нам у метро, а о современной NoSQL базе данных, которая помогает обеспечить быстрый доступ до часто изменяемых/требуемых данных. 

Для тех кто не знает, Redis позволяет легко организовать out of process memory cache и, помимо этого, имеет возможность сохранять данные на жесткий носитель, что превращает его из обычного кэша в полноценную базу данных. Так как у редиса достойная производительность, то для своего проекта, где требуется хранить миллионы key-value записей я выбрал его. Я не админ, поэтому если мне хочется поставить какой-либо софт, то чтобы он сразу работал и забыть про него насовсем. И вроде бы redis это позволяет сделать: легко устанавливается, в процессе работы не  падает, но... как всегда есть свои тонкости, которые не дают спокойно жить.

У редиса есть три возможности работать с данными: in-memory, когда на диск ничего не созраняется, rdb - когда раз в x секунд вызывается функция сохранения всех данных на диск и aof - когда измененные данные постоянно дописываются в журнал на диск в режиме append, а потом проигрываются по журналу, в случае, например, сбоя.

Я выбрал режим rdb - просто потому что он был проще в понимании и, к тому же, в документации не было сносок, как про aof, что в некоторых случаях все может накрыться.

Выбрал и выбрал. Настроил чтобы раз в 5 минут база пересохранялась (мне не сильно страшно, если 5 минут последних изменений пропадут в случае каких-либо проблем типа отключения питания), сделал регулярные бэкапы rdb файла на другую машинку ну и вроде можно забыть... но оказалось, что нет!

Итак, проблема номер 1. База постоянно растет в размере (что ожидаемо), скорость роста базы не сильно высока и ожидать заполнения 8-ми гигов оперативной  памяти базой следует месяцов эдак через 6. Вроде все время можно отдыхать, пока не будем подходить к верхней планке, но не тут было!
На машине 8GB, текущий размер базы стал примерно 3.7GB. Посмотреть размер базы можно с помощью команды INFO в клиенте редиса redis-cli. Там выводятся следующие параметры:

used_memory - сколько памяти редис навыделял для базы
used_memory_rss - сколько редис отъедает памяти у OS (эта цифра больше, из-за фрагментации памяти)
used_memory_peak - хрен бы знал что это значит, потому что цифра всегда меньше, чем used_memory
Так вот, как только редис отожрал примерно половину памяти операционки он перестал сохранять данные на диск. Заметил я это не сразу, а через пару дней, когда машина перезагрузилась из-за проблем с электричеством. Офигенно! Естественно, все бэкапы никак помочь не могли, потому что в них просто не было данных, которые остались в оперативке.

В общем, стал разбираться, и выяснилось следующее. Redis использует fork() в операционной системе, для того, чтобы сделать полную копию текущего процесса и в бэкграунде сохранить данные. А fork работает хитрым образом, допустим есть 4GB данных - он не копирует полностью страницы памяти в новые, а просто оставляет ссылки, и новые страницы памяти выделяет только в том случае, если в родительском или порожденном процессе с этими страницами произошли какие-то изменения. Так как редис второй процесс использует для чтения данных, то речь идет об измененных данных в родительском процессе. Получается, дополнительная память расходуется только в том случае, когда клиенты редиса меняют значения ключей в процессе сохранения на диск.

Но у меня-то данных меняется не так-то уж и много за то время, пока файл сохранялся, памяти должно хватать, а редис настойчиво пишет "could not allocate memory" в логах. Почему?

Стал смотреть дальше, оказывается, OS тоже не проста и очень интересно работает с fork(). Вот пример: база данных 4.1GB, в процессе сохранения изменяется 10MB даных, соответсвенно пик использования памяти во время fork() должен быть 4.11GB.  Хватит ли оперативной памяти размером
 8GB?  При дефолтных настройках получается, что не хватит. А все потому, что OS до начала fork не знает, сколько реально памяти потребуется процессам, и предполагает самый худший вариант, когда все страницы придется продублировать. Ну а так как 8.2 GB уже не помещаются в оперативку, то операционка отказывается начинать делать fork() c out of memory.

К счастью, тут OS удается обмануть. Есть две настройки: vm.overcommit_memory и vm.overcommit_ratio. Посмотреть текущие значения можно либо командой
sysctl vm.overcommit_ratio
либо
cat /proc/sys/vm/overcommit_ratio

Эти настройки позволяют затюнить OS так, чтобы она не отказывала в выделении памяти, даже если ее реально нет. Для это значение overcommit_memory должно быть установлено в 2. Тогда, OS будет считать, что количество памяти зависит от overcommit_ratio. Вот формула, по которой высчитывается доступная память в зависимости от overcommit_ratio:
 
allocatable memory=(swap size + (RAM size * overcommit ratio))

overcommit_ratio здесь указывается в процентах. 

Так как swap у меня небольшой по размеру, то я в overcommit_ratio указал значение 200, что увеличило доступную память чуть более чем в два раза.

сделать это можно либо командой sysctl, либо изменив /ect/sysctl.conf


sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=200

Но редис все равно приходится постоянно мониторить на предмет сохранения данных. Команда  
tail -f /var/log/redis_6379.log | grep save 
помогает в этом и если нет кучи записей с ошибками выделения памяти, то вроде все нормально.

Подробнее про настройки виртуальной памяти можно прочитать тут (с одной поправкой: в статье перепутали значения 1 и 2 для  vm.overcommit_memory)

Проблема номер 2. Я пока еще не оценил по достоинству данную проблему, но, думаю, в скором времени она доставит много неприятностей. Дело в том, что Redis очень медленно делает fork, когда работает под Xen! При сохранении 7-гигового сета время форка занимает около секунды. (Узнать время, которое тратится на fork() можно воспользовавшись командой Redis INFO и смотреть параметр latest_fork_usec - показывает время форка в микросекундах).  А это может создать очень много неприятностей, так как в течении форка редис блокируется и становится недоступным для клиентов. Представьте, когда в высоконагруженной среде с тысячами одновременно работающих клиентов в течении секунды нельзя обратиться к кэшу.  Как пишут разработчики redis, это проблема Xen, так как нигде больше не наблюдается.

Может спросить, почему на Xen свет клином сошелся? Да хотя бы просто потому, что у меня на сервере стоит xen, под которым крутятся разные виртуалки,  а кроме того почти все облака так или иначе построены на технологии Xen.








Комментариев нет:

Отправить комментарий