Redis in production

    Хотелось бы рассказать о некоторых особенностях Redis при использовании на боевом сервере. Будут рассмотрены альтернативы при сохранении данных на диск, позволяющие достичь различной степени надёжности при сбоях. Так же будут приведены примеры конфигурации для резервного копирования и мониторинга. Используется Redis 2.2.11 на Amazon EC2 с установленной Ubuntu 10.10.



    Резервное копирование



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

    Redis делает RDB-снапшоты на основе следующих параметров в redis.conf:

    save 900 100
    save 300 1000
    save 60 100000
    
    dir /var/redis/
    dbfilename dump.rdb
    rdbcompression yes
    


    Существует два persistence-режима: RDB и AOF. Стоит отметить, что в обоих режимах используется весьма надёжный способ записи информации на диск, который практически исключает ситуации потери данных при аппаратном сбое. Как много данных вы потеряете зависит лишь от выбора persistence-режима. RDB позволяет регулировать этот параметр гибко, но в среднем, при сбое, может быть потеряно около часа. В этом режиме Redis сначала пишет полный снапшот базы во временный файл и только после окончания записи на диск переименовывает его в рабочий. Это исключает потерю данных благодаря атомарности системного вызова rename().

    В случае с AOF, Redis ведёт лог операций, которые выполняют клиенты и записывает их в файл (по умолчанию каждую секунду). AOF это аббревиатура от Append Only File, а это означает то, что Redis не изменяет уже записанные данные, а лишь добавляет новые в конец. Благодаря тому, что при использовании AOF, Redis по умолчанию пишет данные на диск каждую секунду, максимум, что вы теряете в случае сбоя при использовании этого режима — это 1 секунда.

    В проекте у нас используется RDB потому что час данных для нас потерять некритично. Кроме того, в худшем случае, данные могут быть восстановлены из основной СУБД.

    Подробнее про persistence в Redis:

    http://redis.io/topics/persistence
    http://antirez.com/post/redis-persistence-demystified.html

    Для бэкапов мы используем замечательный backup gem, в котором есть поддержка Redis.

    # encoding: utf-8
    
    Backup::Model.new(:my_backup, 'My Backup') do
      split_into_chunks_of 500
    
      database Redis do |db|
        db.name               = "dump"
        db.path               = "/var/redis"
        db.host               = "localhost"
        db.additional_options = []
        db.invoke_save        = false
      end
    
      compress_with Gzip do |compression|
        compression.best = true
        compression.fast = false
      end
    
      notify_by Mail do |mail|
        # ...
      end
    
      store_with S3 do |s3|
        # ...
      end
    end
    


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

    Мониторинг



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

    set mailserver localhost
    set mail-format { from: monit-app1@example.com }
    set alert support@example.ru but not on { action pid ppid }
    
    check process redis with pidfile /var/run/redis.pid
      start program = "/usr/bin/redis-server /etc/redis/redis.conf"
      stop program = "/usr/bin/redis-cli -p 6379 shutdown"
      group redis
    


    Нехватка памяти при BGSAVE



    При больших объёмах хранимых данных возможны проблемы в случае использования RDB-снапшотов. Для записи используется команда BGSAVE, которая делает форк текущего процесса и в этом форке данные записываются на диск. Таким образом, основной поток не блокируется и запись происходит асинхронно. Проблема в том, что в UNIX-системах при вызове fork(), в дочерний процесс так же копируется содержимое памяти, которую использует родительский процесс. Допустим, если Redis в текущий момент времени занимает 2Gb памяти, а в системе остался только 1Gb свободной памяти, то при выполнении команды BGSAVE возможна следующая ошибка:

    [18696] 28 Mar 12:26:54 # Can't save in background: fork: Cannot allocate memory
    


    В современных системах при копировании памяти для форков используется метод Copy on Write. Память копируется только тогда, когда происходит запись в соответствующий участок. Redis делает форк процесса лишь для того, чтобы сохранить данные асинхронно, этот форк их никак не изменяет, а значит мы можем спокойно установить системный параметр vm.overcommit_memory в значение 1. Этот параметр отвечает за возможность выделения большего объёма памяти, чем доступно. Добавляем в /etc/sysctl.conf строчку:

    vm.overcommit_memory = 1
    


    И перечитывам конфиг:

    # sysctl -p
    


    Подробнее про эту проблему:

    http://groups.google.com/group/redis-db/browse_thread/thread/dc4876861b174358
    Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!
    Share post

    Similar posts

    Comments 19

      +4
      Несколько сумбурно показалось, но все равно здорово читать статьи, основанные на практике.
      Спасибо.
        +2
        Даже если детка не пишет в память, то copy on write происходит, если папа пишет — а папа обычно пишет будь здоров! Что на самом деле меняет overcommit_memory — лишь «умную» проверку, что детка может аллоцировать столько же памяти, сколько есть у папы. И всё. А так в своп загнать нагруженный редис как два пальца. См. соответсвущую секцию в faq redis.io/topics/faq («Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!»)
          0
          Спасибо за информацию, я добавил в пост ссылку на FAQ.

          Даже если папа очень интенсивно пишет, то всё упирается в скорость записи данных на диск. Теория такова, что пока детка пишет на диск, папа может изменить все ключи, но на практике детка всё-таки успевает всё быстренько записать и рост используемой памяти за это время минимальный. Зависит от объёмов данных и количества частоизменяемых ключей, конечно. У нас в настоящий момент Redis использует 3Gb и пишет это всё на диск (попутно сжимая) секунд за 30 (EC2 moderate IO performance).
          +1
          Спасибо информацию. мы уже год или больше используем Redis в продакшин системе на debian/Ec2 в количестве около 10 серверов (с репликациями и т.п.) Очень довольные
            +1
            Жаль, думал увидеть практический опыт использования редиса в продакшене (удобства, подводные камни), а не выдержку из мана :(
              0
              А всё-таки, расскажите пожалуйста какие такие недостатки режима AOF вынудили вас склонится в пользу RDB? Спасибо!
                +1
                из aof слишком долго поднимается. особенно, если у вас в редисе пара zset, поднятый инстанс 1-2гб и aof файл на 5гб. из rdb при этом поднимется со скоростью чтения с диска файла в 600мб
                  0
                  Таких недостатков для себя не нашёл, просто нет в этом необходимости, достаточно RDB.
                  0
                  В моем случае данные не так критичны, если пропадут на несколько последних часов и поэтому использую RDB, что бы не гонять диск постоянно — на ВДС за это ругаются когда на него большая нагрузка.
                    0
                    Есть ли сейчас какая-то возможность сделать простой master-master? Хотя бы его эмуляцию с автоматическим перебрасыванием ролей. Потеря части последних данных не критична, а вот сихронизация автоматом после того как первый из серверов упал очень нужна.
                    0
                    А как вы храните фиды в redis? Допустим, если необходимо показывать объединенный фид нескольких или всех пользователей сразу?
                      0
                      Фиды у нас хранятся в виде Sorted Sets для каждого пользователя, в которых зачениями выступают названия других ключей, которые являются Set. Это делается для группировки однотипных событий. Необходимости показывать объединённые фиды пока не возникало, но я думаю, что можно попробовать использовать ZUNIONSTORE.
                        0
                        Понятно, что ZUNIONSTORE, но если фидов десятки тысяч, то получающийся Set будет огромным. По меньшей мере, если показывать фиды всех пользователей. На этом я немного застрял, думаю сейчас попробовать какой-нибудь другой инструмент, возможно даже отдельную базу SQL.
                          0
                          Если нужен именно глобальный фид, то я бы предложил использовать отдельный Sorted Set, в который автоматически будут добавляться активити всех юзеров при создании. Дело в том, что значениями в Sorted Set выступают названия Set-ов, которые уникальны для каждой активити и не дублируются. То есть, если один юзер создаёт активити, то в фиды всех подписчиков добавляется ссылка на один и тот же Set.
                      0
                      Понятно, что ZUNIONSTORE, но если фидов десятки тысяч, то получающийся Set будет огромным. По меньшей мере, если показывать фиды всех пользователей. На этом я немного застрял, думаю сейчас попробовать какой-нибудь другой инструмент, возможно даже отдельную базу SQL.
                        0
                        Может уже позновато — какая у вас нагрузка на Редис и время ответа?

                        Only users with full accounts can post comments. Log in, please.