Репликация MongoDB на Amazon EC2

system.indexes


  • Предисловие
  • Настройка Amazon EC2
  • Установка MongoDB
  • Настройка репликации
  • Что почитать

local.abstract


В этой статье я расскажу о том, как максимально безболезненно организовать репликацию MongoDB на базе Amazon EC2. Несомненно, существует отличная документация как по работе с Amazon EC2, так и по настройке MongoDB в целом и репликации в частности. Но, как известно, дьявол живёт в мелочах. И в этой статье я выделю те “мелочи”, которые более всего донимали меня.

{step: 1, title: «Amazon EC2 configure», devilCount: 2}


Начнём с начала – с настройки инстансов.

Первым делом надо создать две группы приватности: для web-инстансов и для инстансов базы данных.
Для web-инстансов откроем доступ для SSH, HTTP и HTTPS:


Для db-инстансов откроём всё тот же доступ по SSH, плюс доступ на порт 27017 для групп приватности web и db:


Теперь можем запускать сами инстансы: один small-инстанс для web-приложения, два large- и один micro- инстанс для базы данных. В качестве Amazon Machine Image (AMI) я выбрал Ubuntu Server 12. Важно: чтобы репликация MongoDB заработала, обязательным условием является нечётное количество инстансов. С этой целью мы и будем использовать 3й – микро-инстанс – в качестве арбитра. О том что такое арбитр, я расскажу ниже. Конечно, мы бы могли просто запустить и 5, 7 или 2n + 1 large-инстансов. Но данным примером я хочу показать хороший вариант того, как можно минимизировать затраты на Amazon EC2, и лишний раз акцентирую внимание на том, что инстансов в репликации должно быть нечётное количество.

Есть ещё один неочевидный, но достаточно весомый ньюанс – динамичность IP-адресов у инстансов. Соответственно завязываться на них при настройке репликации – не самый идеальный вариант. Лучше для этих целей воспользоваться алиасами, которые настраиваются в файле /etc/hosts. На каждом инстансе приведём файл hosts примерно к такому виду:
127.0.0.1 db1 localhost
10.40.120.30 db1
10.40.120.31 db2
10.40.120.32 db3

Теперь мы имеем готовые к дальнейшему использованию инстансы.

{step: 2, title: «MongoDB install», devilCount: 1}


Приступим к установке MongoDB. Процесс установки отлично описан в официальном мануале MongoDB, так что следуем чётко его указаниям. Создадим файл mongo_install.bash и запишем в него следующий скрипт:
apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | tee -a /etc/apt/sources.list.d/10gen.list
apt-get -y update
apt-get -y install mongodb-10gen

Выполняем наш скрипт:
sudo bash ./mongo_install.bash

Если всё прошло успешно, то мы увидим PID запущенного MongoDB:
mongodb start/running, process 2368

Теперь осталось запустить процесс mongod:
sudo service mongodb start

Маленькая хитрость напоследок: чтобы не проходить столь мучительный и однообразный путь установки софта на каждом инстансе, можно воспользоваться функциональностью Amazon EC2 Images.

{step: 3, title: «Replication», devilCount: 2}


Вот мы и подошли к главному пункту – настройке репликации. Сперва, определим в файлах конфигурации (/etc/mongodb.conf) на всех db-инстансах параметр replSet. Этот параметр должен содержать имя репликации:
replSet = myproject

После этого перезапускаем сервис:
sudo service mongodb restart

Далее подключаемся к Монге командой
mongo

Инициируем реплику:
rs.initiate()

Добавляем второй инстанс в нашу репликацию:
rs.add("db2:27017")

Третий же инстанс, и это важно, добавляем как арбитр:
rs.addArb("db3:27017")

Арбитр не хранит свою копию базы данных. Он не участвует ни в записи, ни в чтении данных. Предназначен исключтельно для голосования за Primary. Этим и обусловлен тот факт, что мы можем запускать арбитра на минимальном железе.

Посмотрим текущий статус реплики:
mydb:PRIMARY> rs.status()
{
        "set" : "myproject",
        "date" : ISODate("2013-02-04T12:17:42Z"),
        "myState" : 1,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "db1:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 1139012,
                        "optime" : Timestamp(1359738450000, 12),
                        "optimeDate" : ISODate("2013-02-01T17:07:30Z"),
                        "self" : true
                },
                {
                        "_id" : 1,
                        "name" : "db2:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 1138953,
                        "optime" : Timestamp(1359738450000, 12),
                        "optimeDate" : ISODate("2013-02-01T17:07:30Z"),
                        "lastHeartbeat" : ISODate("2013-02-04T12:17:42Z"),
                        "pingMs" : 0
                },
                {
                        "_id" : 2,
                        "name" : "db3:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 442498,
                        "optime" : Timestamp(1359738450000, 12),
                        "optimeDate" : ISODate("2013-02-01T17:07:30Z"),
                        "lastHeartbeat" : ISODate("2013-02-04T12:17:40Z"),
                        "pingMs" : 0
                }
        ],
        "ok" : 1
}

Узнать какое поле за что отвечает можно узнать здесь: docs.mongodb.org/manual/reference/replica-status/#fields

Проверим конфиг:
mydb:PRIMARY> rs.config()
{
        "_id" : "myproject",
        "version" : 12,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "db1:27017"
                },
                {
                        "_id" : 1,
                        "host" : "db2:27017"
                },
                {
                        "_id" : 2,
                        "host" : "db3:27017",
                        "arbiterOnly" : true
                }
        ]
}

Видим, что всё выглядит именно так, как мы и задумали. Ура!

И на десерт ещё один момент. В настройках реплики у каждого участника среди прочих есть свойство priority. По умолчанию оно равняется 1 и, как дефолтное значение, не отображается в конфиге. Это значение влияет на вероятность того, что участник будет избран Primary. Сделаем так, чтобы db1 был гарантировано Primary (ну, например, у него больше памяти):
config = rs.config()
config.members[0].priority = 2
rs.reconfig(config)

Теперь конфиг будет выглядить следующим образом:
mydb:PRIMARY> rs.config()
{
        "_id" : "myproject",
        "version" : 12,
        "members" : [
                {
                        "_id" : 0,
                        "host" : "db1:27017",
		"priority" : 2
                },
                {
                        "_id" : 1,
                        "host" : "db2:27017"
                },
                {
                        "_id" : 2,
                        "host" : "db3:27017",
                        "arbiterOnly" : true
                }
        ]
}

Очень важное замечание от StamPit:
Проблема: Не указан размер oplog. Для 64bit систем по умолчанию он составляет 5% от доступного дискового пространства, но не менее 1Gb. Если диск большой и средняя активность insert/update — стоит ограничить размер в конфиге, 2000Mb вполне достаточно:
oplogSize = 2000

Если данных не так много, а количество insert/update не слишком велико, можно слегка уменьшить дисковую активность следующим образом:
1) Отключить preallocate noprealloc = true
2) Уменьшить размер файлов (уменьшатся как файлы данных, так и журнала) smallfiles = true

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

local.links


  1. Amazon EC2 aws.amazon.com/documentation/ec2
  2. MongoDb Installation docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu
  3. MongoDB docs.mongodb.org/manual/tutorial/getting-started
  4. MongoDb Replication docs.mongodb.org/manual/replication
  5. MongoDb Replica Set Arbiters docs.mongodb.org/manual/administration/replica-sets/#replica-set-arbiters
  6. MongoDb Replica Set Configuration docs.mongodb.org/manual/reference/replica-configuration
Share post

Comments 14

    +4
    И успешно счёт за приличный Disk IO.

    Проблема: Не указан размер oplog. Для 64bit систем по умолчанию он составляет 5% от доступного дискового пространства, но не менее 1Gb. Если диск большой и средняя активность insert/update — стоит ограничить размер в конфиге, 2000Mb вполне достаточно:
    oplogSize = 2000

    Если данных не так много, а количество insert/update не слишком велико, можно слегка уменьшить дисковую активность следующим образом:
    1) Отключить preallocate noprealloc = true
    2) Уменьшить размер файлов (уменьшатся как файлы данных, так и журнала) smallfiles = true

      +2
      И да, почему именно нечётное количество? Для production используется 3-12 узлов в replica set, при этом только 7 из них могут голосовать.
      Какая, к примеру, будет опасность, если у меня будет replica set из 4 машин?
        0
        Так пишут в доках. Да и у меня не заработала реплика на двух инстансах, а когда добавил третий всё стало ок.
          +1
          Реплика на двух инстансах работать будет, но не будет failover (так как нет кворума).

          Вообще, иметь более двух нод — это одно из первых правил failover, поэтому минимальная production-конфигурация для mongodb — именно primary, secondary и arbiter, т.е три ноды. Но нет никакого 2n+1, так как на 4 нодах при выходе из строя мастера кворум сохраняется, и они сами вполне себе решат, кому быть новым мастером.
            0
            Понял
              0
              Смутила эта фраза:
              The set has an odd number of voting members.
              If you have an even number of voting members, deploy an arbiter to create an odd number.

              Видимо я её не совсем верно понял.
                0
                Фразу Вы поняли совершенно верно, и теперь это уже меня начало смущать. Но, как показыавет практика, общее нечётное количество не обязательно, когда реплик больше трёх. Хотя проверю ещё раз, чтобы точно в этом убедиться.
                  0
                  Да, чётное количество нод не катит, когда есть возможность развалить реплика сет на 2 равные части. split-brain — это очень печально. Когда такой возможности нет — вполне себе рабочее решение. Но да, в таком случае лучше всё равно добавить либо арбитра, либо дополнительный голос какой-нибудь из нод.
              0
              Потому что надо однозначное понимание, что делать в случае split brain. Если у вас репликасет из 4 машин развалится напополам, то он либо не будет работать совсем, либо будут работать обе половинки, данные вы потом будете с большим трудом восстанавливать. Репликасет с нечётным количеством реплик в принципе не может развалиться напополам, и именно для этого добавляют арбитра (по сути — бесполезную ноду, которая не делает ничего и просто болтается в реплика сете) в сеты с чётным количеством машин.
                0
                Да, я не учёл позиции «разваливается пополам», учитывал только выпадение мастера.
                  0
                  Можно, кстати, обойтись и без арбитра, просто добавив одной ноде лишний голос. Хотя возможны грабли.
                0
                Спасибо за такое важное уточнение! Я попросту ещё не сталкивался с этой проблемой. Обязательно добавлю в статью.
                +2
                Есть ещё один неочевидный, но достаточно весомый ньюанс – динамичность IP-адресов у инстансов. Соответственно завязываться на них при настройке репликации – не самый идеальный вариант. Лучше для этих целей воспользоваться алиасами, которые настраиваются в файле /etc/hosts. На каждом инстансе приведём файл hosts примерно к такому виду:


                Не стоит так делать. Лучше пропишите в CNAME паблик днс инстансов и при резолве будете получать внутренние IP.Ну и понятно, что при смене IP нужно поменять днс запись, а не hosts на всех машинках.
                  +1
                  Ваш вариант выглядит действительно более удобным. Обязательно попробую, спасибо.

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