Cкоростная синхронизация миллиарда файлов

Есть несколько идентичных серверов (4 ноды) на Amazon EC2 с Ubuntu. Каждый генерирует и хранит у себя на диске кэш, который хотелось бы синхронизировать. Но простой rsync тут не подойдет — файлов несколько миллиардов, nfs — слишком медлителен, и т. д. Полный список рассмотренных вариантов с пояснениями ниже.

К тому же, время от времени нужно удалять устаревшие файлы сразу на всех серверах, что пока делается вручную и занимает несколько суток. Вопрос наиболее быстрой для такого Use Case файловой системы планирую описать позже. Оговорюсь только, что по нескольким причинам была выбрана XFS.

После теста нескольких кластерных технологий и файловых систем, по совету старшего товарища, решили использовать тот же rsync, но в связке с inotify. Немного поискав в интернете готовое такое решение, дабы не изобретать велосипед, наткнулся на csyncd, inosync и lsyncd. На хабре уже была статья о csyncd, но он тут не подходит, т.к. хранит список файлов в базе SQLite, которая вряд-ли сможет сносно работать даже с миллионом записей. Да и лишнее звено при таких объемах ни к чему. А вот lsyncd оказался именно тем, что нам и было нужно.

UPD: Как показала практика, необходимо ощутимое измение и дополние в тексте. Я решил внести лишь незначительные правки в основную часть, а новыми выводами поделиться в конце статьи.


Rsync + inotify = lsyncd


Lsyncd — демон, который следит за изменениями в локальной директории, агрегирует их, и по прошествии определенного времени стартует rsync для их синхронизации.



Итак, rsync'ать все сразу мы не можем, зато можем обновлять только то, что изменилось. О последнем нам расскажет inotify, подсистема ядра, уведомляющий приложения об изменениях в файловой системе. Lsyncd следит за событиями изменения локального дерева файлов, собирает эту информацию за 10 секунд (можно указать любое другое время) либо пока не соберется 1000 событий (смотря какое событие произойдет первым), и запускает rsync для отправки этих файлов на остальные ноды в нашем кластере. Запускается rsync с параметром update, то есть файл на получателе будет заменен отправляемым только если последний новее. Это дает возможность избежать коллизий и лишних операций (например, если один и тот же файл был сгенерирован параллельно и на отправителе, и на получателе).



Реализация


Процесс установки описан для Ubuntu 11.10. В других версиях могут быть отличия.

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

ssh-keygen

Passphrase оставляем пустой.

Далее добавляем содержимое ~/.ssh/id_rsa.pub на все остальные ноды в кластере в ~/.ssh/authorized_keys. Естественно выбираем $HOME того пользователя, который имеет права на запись в папку синхронизации. Проще всего, если это будет /root, но это не лучший выбор с точки зрения безопасности.
Желательно также прописать все ноды в /etc/hosts. Я назвал их node01, node02, node03.
Повторяем на всех нодах.

2. Устанавливаем lsyncd

apt-get install lsyncd


3. Конфиг хоть и нужно создавать вручную, но он довольно прост. Пишется на языке Lua. Интересен также комментарий о причинах выбора именно Lua от автора lsyncd. Я еще создал отдельную директорию для логов.

mkdir -p /etc/lsyncd
mkdir -p /var/log/lsyncd
vi /etc/lsyncd/lsyncd.conf.lua


Содержимое конфига с комментариями:

settings = {<br/>
  logfile    = "/var/log/lsyncd/lsyncd.log", <br/>
  statusFile = "/var/log/lsyncd/lsyncd.status", <br/>
  nodaemon   = true --<== лучше оставить для дебага. потом выключите.<br/>
} <br/>
--[[<br/>
sync { <br/>
  default.rsync, --<== используем rsync для синхронизации. по-идее, можно использовать и что-то другое.<br/>
  source="/raid", --<== локальная директория, которую синхронизируем<br/>
  target="node01:/raid", --<== dns-имя и директория на ноде-получателе через двоеточие<br/>
  rsyncOps={"-ausS", "--temp-dir=/mnt-is"}, --<== temp-dir нужна если синхронизация двухсторонняя.<br/>
  delay=10 --<== время, которое будет собираться список событий для синхронизации<br/>
} <br/>
]]<br/>
sync { <br/>
  default.rsync, <br/>
  source="/raid", <br/>
  target="node02:/raid", <br/>
  rsyncOps={"-ausS", "--temp-dir=/mnt-is"}, <br/>
  delay=10 <br/>
} <br/>
<br/>
sync { <br/>
  default.rsync, <br/>
  source="/raid", <br/>
  target="node03:/raid", <br/>
  rsyncOps={"-ausS", "--temp-dir=/mnt-is"}, <br/>
  delay=10<br/>
}


Конфиг проще сделать один раз и дальше разнести на все ноды, закомментировав лишний для каждого конкретного сервера блок. Комментарии — все что находится между «--[[» и «]]».

Используемые опции вызова rsync:
a — режим архивации; равносильно -rlptgoD, тоесть копировать рекурсивно, вместе с симлинками и специальными файлами, сохраняя права доступа, группу, владельца.
l — копировать симлинки;
u — не обновлять файлы на получателе если они новее;
t,p,o,g — копировать время, права доступа, владельца, группу соответственно.
s — на случай если в имени файла попадется пробел.
S — оптимизировать передачу данных состоящих из нулей.
Подробнее в man lsyncd или в документации.

4. Стартуем демон на всех нодах:

/etc/init.d/lsyncd start


Если Вы оставили «nodaemon = true» в конфиге, то сможете видеть что происходит.

Дальше копируем/создаем/удаляем что-нибудь в директории, которую мы указали для синхронизации (у мнея это /raid), ждем несколько секунд и проверяем результат синхронизации.

Скорость передачи данных достигает 300 Мбит/с и на загрузку сервера это мало влияет (по сравнению с тем же GlusterFS, например), да и задержка в данном случае сглаживает пики. Многое еще зависит от используемой ФС. Тут тоже пришлось провести маленькое исследование, с цифрами и графиками, так как ситуация довольно специфическая и результаты существующих опубликованных тестов не отражают того, что требуется в задаче.

Что еще было рассмотрено и почему не подходит в данном случае


Все исследование было нацелено на работу с Amazon EC2, с учетом ее ограничений и особенностей, поэтому полученные выводы в основном касаются только ее.
  • DRBD – репликация идет на блочном уровне. В случае деградации одного носителя убиваются оба. Ограничение в 2 ноды. (Больше можно, но 3 и 4-й можно подключить только как слейвы.)
  • Ocfs2 – используется либо поверх DRBD (о чем есть хорошая статья на хабре), либо нужно иметь возможность монтировать один раздел с нескольких нод. Невозможно на ec2.
  • Gfs2 – аналог ocfs2. Не пробовал, т. к. согласно тестам эта ФС медленней ocfs2, в остальном — ее аналог.
  • GlusterFS – вот тут все заработало практически сразу и как надо! Проста и логична в администрировании. Можно сделать кластер до 255 нод с произвольным значением реплик. Создал кластерный раздел из пары серверов и примонтировал его на них же но в другую директорию (то есть сервера были одновременно и клиентами). К сожалению на клиенте этот кластер монтируется через FUSE, и скорость записи оказалась ниже 3 МБ/сек. А так, впечатления от использования очень хорошие.
  • Lustre — чтобы запустить сие дело в krenel mode нужно патчить ядро. Как ни странно, в репозитории Ubuntu есть пакет с этими патчами, но вот самих патчей под нее или хотя-бы под Debian я не нашел. И судя по отзывам, понял, что завести это в deb-системе — шаманство.
  • Hadoop w/ HDFS, Cloudera — не пробовал, поскольку было найдено другое решение (см. ниже). Но первое что бросается в глаза — написано на Java, следовательно ресурсов кушать будет много, да и масштабы не как у Фесбука или Яху, всего 4 ноды пока.


UPD: Данное решение отлично себя показало на тестах (после чего и была написана статья), но в боевых условиях все оказалось совсем по другому. Минимальная продакшн-конфигурация — 584 тысячи вложенных директорий. А lsyncd навешивает inotify'и на каждую директорию. Сделать это сразу для всего дерева невозможно. Памяти, 584 тысячи нотифаев, съедают относительно немного, около 200 Мб (из 16 ГБ имеющихся), но вот процесс этот занимает 22 минуты. В принципе, не страшно: раз запустил и забыл. Но после этого, при стандартной конфигурации, lsyncd запускает синхронизацию всех файлов, которая в наших условиях либо глючила, либо занимала дни. В общем — не вариант. 100%-ная консистентность не требуется и без начальной синхронизации можно обойтись. Оставалось ее «выключить». Благо, демон написан так, что можно изменить практически все его функции прямо из конфига. Также, для увеличения производительности default.rsync был заменен на default.rsyncssh, а ядро натюнино на предмет лимитов inotify'а. То есть, для большинства задач подойдет конфиг выше, но в нашей конкретной ситуации работает следующее:


settings = {
  logfile    = "/var/log/lsyncd/lsyncd.log",
  statusFile = "/var/log/lsyncd/lsyncd.status",
  statusInterval = 5, --<== чтобы видеть что происходит без включения подробного лога
}

sync {
  default.rsyncssh,
  source = "/raid",
  host = "node02",
  targetdir = "/raid",
  rsyncOps = {"-ausS", "--temp-dir=/tmp"}, --<== описано выше
  delay = 3, --<== ставим по-меньше, чтобы очередь не забивать
  init = function(event) --<== перезагрузка функции инициализации. как она выглядела в оригинале можно посмотреть в документации или в исходниках
    log("Normal","Skipping startup synchronization...") --<== чтобы знать, что мы этот код вообще запускали и когда
  end
}

sync {
  default.rsyncssh,
  source = "/raid",
  host = "node03",
  targetdir = "/raid",
  rsyncOps = {"-ausS", "--temp-dir=/tmp"},
  delay = 3,
  init = function(event)
    log("Normal","Skipping startup synchronization...")
  end
}


Настройки ядра

У inotify есть три параметра (см. ls /proc/sys/fs/inotify/):
max_queued_events — максимальное число событий в очереди; default = 16384;
max_user_instances — сколько инстансов inotify может запустить один пользоваетль; default = 128;
max_user_watches — сколько файлов может отслеживать один пользоваль; default = 8192.

Рабочие значения:
echo "
fs.inotify.max_user_watches = 16777216 # 
fs.inotify.max_queued_events = 65536
" >> /etc/sysctl.conf
echo 16777216 > /proc/sys/fs/inotify/max_user_watches
echo 65536 > /proc/sys/fs/inotify/max_queued_events


Так все заработало уже в продакшине.

Спасибо за внимание!
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 29
  • 0
    файлов несколько миллиардов

    да и масштабы не как у Фесбука или Яху

    Хе-хе… скромничаете.

    Вообще, здравое решение и грамотный подход к вопросу! Жду про XFS в этом же духе ;)
    • 0
      Файлы просто очень мелкие, а нод то мало.
      Спасибо!
    • 0
      GlusterFS не очень быстро работает с мелкими файлами, зато там где размеры идут хотя бы на мегабайты, а лучше десятки мегабайт, она очень хорошо масштабируется за счёт страйпа и репликации. Там нужно подобрать набор трансляторов, который подходит конкретно вам с соответствующими настройками.
      • 0
        Только вот очень надо осторожно, а то при ребалансе можно запросто потерять 5ТБ данных и все файлы на одной ноде просто станут обнуленными. Или fuse-клиент намертво подвесится, а вместе с ним и программы что используют фс.
      • 0
        Всегда думал что XFS хорошо работает с небольшим количеством крупных файлов, в остальном проигрывает другим ФС, разве не?
        • 0
          Тоже так думал.
          Везде в статьях про торренты под Linux советуют именно XFS.

          «Вопрос наиболее быстрой для такого Use Case файловой системы планирую описать позже. Оговорюсь только, что по нескольким причинам была выбрана XFS.»

          Надеюсь, автор не забьет, и раскроет нам эту тему.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            а если нужно получить все файлы с нодов на главный сервер, это нужно на каждом ноде lsyncd подымать или есть более элегантное решение?
            • 0
              мне просто не нравится, что ноды имеют ключи, хоть и от одной папки, на сервере, возможно существует механизм сообщений о изменении файлов на ноде, а сервер уже засинкает?
              • 0
                Вообще мне кажется, что тут все ноды равноценные backend'ы к чему-то. Поэтому понятия сервер и клиент здесь условное и применяется в рамках lsyncd для обозначения стороны.
            • 0
              Мне пришлось решать аналогичную задачу, но с csync2 «на бэкэнде», на том же ec2-ebs с xfs (что очевидно для ebs). Примерно как описано тут, только еще пришлось дописывать конфиги на lua для новой версии lsyncd.

              Автор той статьи написал, что его устроил только уровень производительности csync2, чему я поверил (времени не было на тесты). В результате при запуске csync2 кушает cpu на 100% на какое-то время, что меня несказанно печалит. Быть может это из-за моих кривых рук.

              Кстати, если файлов очень много, то нужно повышать лимит количества файлов, за которым может следить inotify. И на сколько я успел разобраться в вопросе, inotify ничего не делает бесплатно — на каждый файл расходуются ресурсы (пусть и не очень много).
              • 0
                >то нужно повышать лимит количества файлов
                В смысле rlimit на open files?

                >inotify ничего не делает бесплатно — на каждый файл расходуются ресурсы
                На любую сущность в системе всегда расходуются какие то ресурсы. Но в данном случае мне хотелось бы уточнить, какого рода ресурсы и почему на каждый файл? Потому как inotify это подсистема ядра которая просто уведомляет приложение о происходящих изменения. Хранения очереди изменений для передачи в приложение в ней нет.
                • 0
                  Ну inotify же не забесплатно будет уведомлять, для того чтобы уведомить об изменениях — нужно какому-то процессу (пусть даже в ядре) при каждой файловой операции записи производить сравнение со списком «я же слежу за этими файлами и папками», собственно если этот список будет содержать 10 тыщ записей, то это наверное печально скажется на каждой файловой операции. Ну и если вдруг резко поменялось 5 тыщ файлов, то накопить 5 тыщ событий в лог, и потом разослать эти события всем слушальщикам.

                  Собственно поэтому я и боюсь на серверах всяких там программ, которые следят за изменениями в папках через inotify или ещё как, т.к. многие не думают о том что каждая прослушка добавляет тормозов и вешают inotify на все подряд на всякий случай, чтобы было. А потом получаем «что-то диск стал медленно работать, странно, наверное посыпался».
                  • 0
                    Ну есть более продуманная подсистема в виде fanotify. Хотя и там есть свои проблемы. А тормозов добавляет просто неграмотное их использование, и тут в принципе уже не важно, что и как использовать.
                    К слову сказать современные антивирусники активно используют подобные подсистемы. Все на выходе зачастую это выходит дешевле, чем шуршать диском в поиске «а не изменилось ли чего у нас в этом куске фс».
              • 0
                >К сожалению на клиенте этот кластер монтируется через FUSE, и скорость записи оказалась ниже 3 МБ/сек.

                Ну а кто запрещает клиентом? Fuse же известный тормоз.
                • +4
                  Вопрос автору. Знает ли он, что в случае с inotify и падением демона ядро после запуска демона не передаст ему произошедшие изменения за период простоя демона (к примеру, на период рестарта)? Т.е. ситуация, что файл изменится, но не будет синхронизирован на кластер очень вероятна.
                  • 0
                    В случае с lsyncd — при старте демона проводится проверка консистентности между нодами.
                    • 0
                      Что происходит при такой проверке и как долго это происходит? Кластер ждет и неработоспособен? А если проверка затянется часов на шесть?
                      • 0
                        Чудес не бывает. Какая тут может быть проверка кроме полного сканирования подконтрольного куска ФС?

                        В новых версиях ядра inotify сменён более прогрессивной подсистемой в которой в том числе в ядре копиться очередь до момента когда демон сможет принять данные. Собственно в том числе и делалось на случай временного падения/рестарта демона слежения. К большому сожалению прикладной софт пока не использует эту возможность и lsyncd не исключение.
                      • 0
                        Читал об этом, но не придал значения. В даном конкретном случае это не критично — одна из других нод рано или поздно сгенерирует свой такой файл и разнесет по всему кластеру.
                        • 0
                          Если файл больше не измениться, и нет других механизмов контроля консистентности кластера (хотя бы тупой пробежкой по файлам), то ни как файл по кластеру с ноды не разойдется. Хотя если проект допускает возможность такой неконсистентности, то почему нет. Я просто хотел обратить на этот аспект внимание в том числе и тех, кто будет читать, возможно в их задачах это будет не приемлемо.
                      • +1
                        Про DRBD немножко не правда. Да, оно не скейлится (штатная конфигурация 2 узла, дальше шаманить), но зато очень быстрая синхронизация, которая срала на файловые системы и количество файлов.
                        • 0
                          а что навесить сверху drbd на нодах в режиме мастер-мастер чтобы мои каталоги /var/www/html/mycoolpowerdpress были консистентны?
                        • 0
                          спасибо, выглядит неплохо, посмотрим на практике.

                          ps: для default.rsyncssh немного другой конфиг получается:

                          sync {
                          default.rsyncssh,
                          source="/raid",
                          host=«node01»,
                          targetdir="/raid",
                          delay=10
                          }
                          • 0
                            я правильно понимаю что в данной конфигурации синхронизация одного файла с одной ноды на 2 другие порождает синхронизацию этого же файла с тех двух других нод на третью. и при фактическом добавлении всего одного файла мы имеем 6 запусков rsync?
                            • 0
                              Во-первых, синхронизируются толко изменения. А во-вторых, можно не замыкать цепочку.
                            • 0
                              Судя по code.google.com/p/lsyncd/source/browse/trunk/fanotify-syscall.h?r=435 поддержка fanotify (упомянутая мною в комментарии новая подсистема) в lsyncd есть (с версии 2.1). Было бы любопытно узнать, использует ли автор топика по прежнему lsyncd и какой версии?
                              • 0
                                Сам спросил, сам отвечу. Подсистема fanotify признана непригодной для использования из-за отсутствия поддержки события «перемещение».
                                Fanotify is btw. currently not usable for Lsyncd, as it misses move events.

                                github.com/axkibe/lsyncd/issues/39#issuecomment-2760451
                              • 0
                                ocfs2 поверх drbd мы ставили на амазоне года 4 назад.
                                Подробностей, впрочем, уже не помню, и в продакшне я его не застал.

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