Наверх ▲

Архитектура хранилища бинарных данных на Одноклассниках

Александр Христофоров Александр Христофоров В Одноклассниках работает с 2009 года.

Александр Христофоров: Добрый день, меня зовут Александр Христофоров. Я представляю компанию «Одноклассники». Мы долго думали, о чем рассказывать. Внутри нашей компании в последнее время появилось много интересного. В итоге мы решили, что рассказ о новом хранилище для бинарных данных будет наиболее актуален.

Я разбил доклад на 3 части. Сначала я расскажу, какую задачу и почему мы решали. Потом расскажу, как мы с ней справились и какие результаты получили.

Текущая ситуация на нашем портале такова. У нас примерно 1,6 миллиарда пользовательских фотографий. Это самое основное, о чем мы говорим, когда подразумеваем бинарные данные. Мы храним их в четырех размерах, поэтому в общей сложности получается 6,4 миллиарда фотографий. 

При этом каждый день у нас загружается еще 3,5 миллиона фотографий. Получается более миллиарда фотографий в год. Растем мы достаточно быстро. Скоро у нас будет 200 терабайт данных.

В отличие от фотохостингов, наши фотографии достаточно активно просматривают. У нас происходит примерно 150 тысяч чтений в секунду (суммарно, на все размеры). Мы хотим дать нашим пользователям возможность загружать полноценные, полноразмерные фотографии – либо оригиналы, либо очень большие фотографии, чтобы не терять качество.

Кроме того, у нас есть еще групповые фотографии. Присутствует музыка, видео, смайлы, подарки и много другого интересного контента.

До недавнего времени у нас было решение, которое нас относительно устраивало. У нас была установлена BerkeleyDB с интерфейсом удаленного доступа (англ. Remote Interface). Мы использовали репликацию по схеме "ведущий-ведомый" (англ. master-slave) . Вышеупомянутый интерфейс также позволял нам делать секционирование (англ. partitioning). Этого хватало, чтобы обслуживать текущую нагрузку. 

Мы поняли, что производительность у этого решения, прямо скажем, так себе. Плохо обстоят дела и с отказоустойчивостью: если "вылетит" ведомый (англ. slave), может отказать и ведущий (англ. master). Если потеряете ведущий, будет еще хуже. 

Обслуживать это решение тоже было тяжело. Во-первых, масштабировать его можно только ровно в два раза. Во-вторых, "погасить" сервер посреди дня нельзя, нужны ночные маневры с недоступностью (англ. downtime). Если требуется распартиционировать данные, необходима масса ручной работы.

Кроме того, решение работает на достаточно дорогом оборудовании. Мы используем внешние дисковые массивы. Мы уже устали покупать это "железо", хотим перейти на что-нибудь более дешевое.

Также мы делали резервные копии (англ. backup), потому что BerkeleyDB может попортить данные. У нас хранится по два экземпляра резервных копий. Мы посчитали, что суммарно мы сейчас храним 6 копий – 2 на ведущем из-за операции записи, 2 на ведомом по той же причине, и 2 резервных. В последнее время ситуация стала совсем печальной: резервное копирование могло занимать 17 часов. Мы просто могли не успеть за день все скопировать.

В мае мы собрались и решили что-то со всем этим сделать за лето, обрисовали себе цели, которых мы хотим достичь с помощью новой технологии.

• В первую очередь, нас интересовала максимальная производительность при чтении. 

• Во-вторых, мы желали иметь возможность изъять любой компонент из системы. При этом все должно работать.

• Мы хотели заменить резервное копирование резервированием: хранить по 3-4 реплики фотографии, чтобы при потере одной из них можно было восстановить ее обратно из двух оставшихся. Это должно было работать.

• Кроме того, администраторы настойчиво просили нас сделать добавление "железа" более простым, желательно в полуавтоматическом режиме.

• Как я уже говорил, требовалось перейти на дешевые серверы с обычными дисками, без особых премудростей.

• Кроме того, наш портал написан на Java, поэтому лучше, чтобы это было Java-решение. Во всех решениях есть ошибки. Решение на Java нашей команде будет легко сопровождать, поддерживать, дорабатывать, и так далее.

Какие решения рассматривались?

Разумеется, мы смотрели в сторону готовых решений, надеялись, что что-то подойдет. Рассматривали распределенные файловые системы. Очень серьезно изучили HDFS, особенно с точки зрения хранения больших объемов данных – видео или еще чего-нибудь.

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

В конечном итоге мы поняли, что не существует подходящего готового решения, которое подошло бы для и маленьких фотографий размером в 5 килобайт, и для видео размером 100 мегабайт. Мы подумали, что проще будет самостоятельно написать то, что нам нужно. 

Что мы написали и что у нас получилось?

На самом деле, все достаточно просто. У нас есть некие серверы, которые обслуживают диски. Данные серверы предоставляют некоторый интерфейс удаленного доступа (англ. Remote Interface) через протоколы TCP и HTTP. Для больших данных это может быть полезно.

Кроме того, у нас есть кластер Zookeeper, который мы используем для координации работы всех этих серверов. У нас есть клиентская библиотека, которая работает с кластером Zookeeper, она "узнает" о его состоянии и потом обращается к нодам за данными либо "кладет" туда данные.

Наши типичные клиенты – это наши HTTP-серверы. Если интересно, мы используем на них TomCat. Для маленьких фотографий внутри предусмотрен большой кэш, для больших фотографий, соответственно, его нет. В принципе, мы рассматриваем возможность отдавать большой контент непосредственно с нод, на которых данные лежат, без промежуточного HTTP-сервера. Он будет только формировать ссылку. 

Что представляет собой этот сервер-хранилище?

Первое решение, которое мы приняли: мы работаем с каждым диском независимо. У нас нет сервера как такового, мы работаем именно с дисками. Плюс в том, что можно диск вытащить из ноды, поставить в другую ноду, и все будет работать дальше. Нет абсолютно никакой разницы, где он стоит. 

Есть и другой плюс. Если диск "вылетает", это не оказывает никакого влияния на другие диски и работоспособность сервера в целом. Риск того, что при "вылете" одного диска сломается что-то еще, минимален. 

Как же мы все храним?

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

Кроме того, у нас есть индекс в памяти. Индекс целиком хранится в памяти, чтобы минимизировать количество поисков (англ. seek) по диску. При записи данных мы просто берем сегмент, записываем туда часть данных, запоминаем, куда мы их записали, и пишем эту информацию в индекс. В индексе еще имеется лог транзакций (англ. transaction log), о котором я расскажу потом.

Как я уже говорил, вовне этот сервер предоставляет интерфейс по TCP. Мы используем Apache и Mina, это неблокирующий сервер.

Сегменты данных

Сегменты данных могут быть любого размера, но все они должны быть одинаковы. Сейчас мы используем сегменты размером 256 мегабайт. Когда сегмент создается, мы сразу же резервируем место под него с помощью xfs_io. Это позволяет избежать фрагментации. У нас нет файлов разного размера, поэтому мы всегда знаем, что файл – это один непрерывный кусок диска. 

Также у нас есть только один активный сегмент на диск, через который идет запись. Мы всегда пишем только в один файл. Опять же, таким образом мы пытаемся минимизировать количество поисков и влияние записи на чтение. 

Еще мы решили, что не будем бороться с удалениями посредством запоминания пустых мест файлов и всего остального: это и расход памяти, и усложнение кода. Мы просто уплотняем сегменты со временем. Когда набирается 30 % свободного места в сегменте, мы берем из него данные, пишем в новый активный сегмент, а старый помечаем как свободный. В него потом можно начать писать данные.

Что такое индекс?

По сути, это хеш-таблица, в которой по ключу лежит адрес. Адрес – это номер сегмента и смещение в сегменте. Но так как все реализовано на Java, то обычный хеш-мап (англ. hash map) взять нельзя, будут проблемы со "сбором мусора", потому что там десятки миллионов элементов. Для маленьких фотографий суммарно на сервере больше миллиарда фотографий. 

Плюс у хеш-мапа есть другая особенность. Когда он заполняется, его нужно растянуть в два раза. Это пауза, и в это время обработка останавливается. Поэтому у нас написан свой мап, который умеет растягиваться без паузы. Он построен на одном массиве переменных int. Данные реально лежат в механической памяти (англ. direct memory). Таким образом, с точки зрения "сборщика мусора" один хеш-мап – это два объекта.

Кроме того, данные в памяти лежат абсолютно точно так же, как на диске. Это позволяет нам очень быстро "поднимать" сервер, "гасить" его, очень быстро синхронизировать сервер с диском. Не нужна никакая трансформация данных. Взяли кусок байтов, записали – и все.

Сам индекс в памяти, но потерять его не хочется. Поэтому у нас существует лог транзакций, в который мы пишем все изменения. Он выглядит точно так же, как и сам индексный файл. Периодически мы просто берем изменения, которые есть в памяти на текущий момент, выполняем "flash" на диск в индексный файл и удаляем логи. Считаем, что мы эти логи обработали.

Когда сервер "поднимается", он все проверяет: если есть какие-то изменения, он их "накатывает", потом делает снимок состояния (англ. snapshot) и чистит их. Кроме того, когда сервер "поднимается" и находит в логе какие-то новые данные, мы еще на всякий случай проверяем сегментные файлы на предмет их целостности. Там хранится ЦИК (англ. CRC), мы читаем данные, смотрим: ЦИК валиден, валидны данные. Если они не валидны, мы "говорим", что лучше их восстановить, потому что, похоже, мы так и не дописали их до конца.

 

Как это все вместе функционирует?

У каждого диска есть уникальный ID. Он заводится, когда диск активируется в системе. Он хранится в файле.

Фактор репликации, который мы сейчас используем для фотографий, – это 3. Мы всегда имеем 3 реплики данных. 

Реплики мы достаточно равномерно "размазываем" по кластеру. Данные одного диска не могут быть реплицированы на 2 других дисках. Они будут "размазаны" между 20 дисками. 

Единственное правило – двух реплик не должно быть на одном сервере. При записи мы используем кворум. Мы считаем, что запись была удачной, если мы смогли записать данные на две ноды. При чтении же, если мы записали хотя бы с одной ноды, мы считаем, что нам все удалось. 

Сейчас, если данных нет по ID, мы пойдем еще на один сервер и проверим: может быть, они там есть. Это, скорее, временная мера. Может, не временная. В очень редких случаях может произойти такое, что на одном сервере что-то есть, а до второго очередь еще не дошла, например. Это не репликация, я потом к этому вернусь.

Как мы понимаем, где какие данные лежат?

Серверов много, дисков много. Сначала мы отталкиваемся от некоего понятия «секция» (англ. partition). Это логическое понятие. Например, мы разбили все данные по текущей конфигурации на 5 тысяч секций. Для каждой секции мы потом храним остаток от деления, хеш отключен. 

Для каждой секции мы храним буквально 3 номера диска, на которых лежат все реплики. Это мы называем таблицей маршрутизации. Для каждой секции в явном виде все прописано. Это очень удобно, потому что это работает очень быстро, с одной стороны. С другой стороны, вся логика построения этой таблицы маршрутизации находится вне системы. Это какой-то внешний инструмент, который может посчитать. Вы можете создать таблицу маршрутизации вручную, загрузить ее, и все у вас сломается.

Как мы расширяемся?

Когда мы хотим добавить новые диски, мы просто создаем новую версию таблицы маршрутизации. 

Пример. Следующая ситуация: было две секции, одна из них лежала на диске 1, 2, 3, вторая – на диске 4, 5, 6. У нас есть какой-то инструмент. Мы сообщили ему: «Теперь мы хотим, чтобы у дисков 1, 2, 3 был вес 0. Мы на них больше не пишем. Зато у нас появились 7, 8, 9, мы на них точно так же хотим писать». Этот инструмент каким-то образом посчитает и даст ответ: «Все понятно, теперь здесь 7, 8, 9. Эта секция ложится теперь на эти диски».

Таким образом, у нас имеется вся история изменений кластера. Мы называем их «регионы» (я называю их «регион»). Для каждого региона мы знаем, грубо говоря, список ID, которые должны туда попасть. В нашем случае это очень просто. Это начальный ID и конечный ID. Если ID попал в этот регион, значит, мы берем эту таблицу. Если ID попал в этот регион, значит, мы берем эту таблицу маршрутизации. 

Что это нам дало? 

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

С другой стороны, есть системы, которые позволяют не трогать данные, зато (например, как HDFS) они хранят для каждого файла его местоположение на диске. Для каждой фотографии нам бы пришлось хранить ее местоположение. Это лишняя инфраструктура, лишнее обращение (а значит, и задержка), сложности и все остальное.

А у нас в данном случае все очень просто. У нас каждый клиент имеет реплику таблицы маршрутизации всех версий. Она достаточно маленькая, чтобы поместить ее в память каждого сервера.

Как мы используем Zookeeper?

В принципе, Zookeeper позиционирует себя как сервис для координации распределенных приложений. По большому счету, это некая база данных. Существует дерево, в узлах которого могут быть произвольные бинарные данные, массив байтов. Это такая распределенная система, где все хранится в памяти. Изменения регистрируются в логе транзакций. Для признания записи успешной точно так же, как и у нас, требуется кворум. 

Интересная особенность Zookeeper в том, что изменения считываются клиентом точно в такой же последовательности, в какой они были сделаны. Это очень важно. С этой гарантированной последовательностью можно успешно работать, придумывая разные хитрые решения.

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

Есть в Zookeeper и еще одна полезная вещь – нотификация. Можно подписаться на изменения узлов в дереве и, таким образом, сильно оптимизировать обращения к серверам.

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

Что мы там храним?

Мы храним, в первую очередь, сведения о серверах и их IP-адреса, а также диски и их статусы. Когда сервер "поднимается", он сообщает: «У меня есть такой-то диск и такой диск, который сейчас имеет статус такой-то. Я сервер такой-то, мой IP-адрес такой-то, я сейчас доступен».

Таким образом, конфигурация серверов очень простая. Все, что нам нужно знать, это Zookeeper-адреса серверов и какое-то семантическое имя кластера. Мы реально храним там сведения для нескольких – пяти-шести – кластеров, и для всех будем хранить конфигурацию.

Кроме того, мы там же храним таблицы маршрутизации и все их версии. Также мы используем Zookeeper для того, чтобы организовать распределенную блокировку. Правда, она нужна только в момент, когда меняется маршрутизация. Когда мы добавляем новую версию, нужно гарантировать, что никто не "залил" новую фотографию по старой, неправильной версии таблицы маршрутизации. Поэтому на момент смены версией мы вынуждены делать блокировку. Смена версий происходит, наверное, в течение секунды. 

Надежность – один из самых важных аспектов, о которых мы заботились. Надежность подразумевает борьбу с проблемами и правильную реакцию на них.

Типичная проблема – это "вылет" диска. Понять, что диск "вылетел", несложно – это ошибка ввода-вывода. Мы просто в кластере помечаем этот диск как "мертвый". Все об этом достаточно быстро "узнают". На него просто перестают идти запись и чтение.

Если "вылетает" какой-то сервер, нам помогает Zookeeper. Просто истекает время сессии, нода пропадает из дерева, и тоже все быстро "узнают" об этом. 

Но существует некоторый период времени, в течение которого все "узнают" об этом. Кроме того, клиент может потерять соединение до сервера, если у него сетевая проблема. Допустим, именно этот клиент этого сервера временно потерял соединение. 

В этом случае мы получим, скорее всего, какие-то ошибки выполнения. Клиент на время заблокирует сервер и начнет отслеживать его доступность. Как только сервер вновь появится, клиент снова начнет его вызывать. При этом из кластера данный сервер не выводится. 

Что делать в случае недоступности?

В нашем случае, если мы записали на две ноды, мы считаем, что все удалось. Одна реплика была недоступна, например. Тогда мы пишем хинты. Хинт – это, по сути, ключ, тип операции (удаление или добавление) и время. Мы точно так же пишем две реплики для надежности этих хинтов. Записать их можно на любые два произвольных сервера.

Когда сервер "поднимается", он пытается прочитать хинты для всех дисков, которые у него есть. Только тогда, когда он их прочитал, диск становится доступным на чтение. До этого диск доступен только на запись. 

Плюс мы периодически проверяем хинты в процессе работы (каждые 30 секунд, грубо говоря). Это, опять же, делается тот случай, если конкретный клиент потеряет соединение с конкретным сервером и, например, не сможет записать конкретную фотографию. Тот сервер все равно получит ее. Через 30 секунд он проверит хинты или соберет эти данные со своих "соседей". По факту у нас была ситуация, когда сервер обновлялся только через хинты, и все работало.

Вторая типичная проблема – это полный выход из строя диска, при котором данные требуется восстановить. Мы просто вставляем диск и даем команду «восстановить». Диск автоматически становится доступным на запись, но недоступным на чтение, что вполне логично. 

Как я уже говорил, наши реплики "размазаны" достаточно равномерно, то восстановление происходит параллельно с нескольких дисков. Дисков может быть порядка 20, например. Восстановление происходит очень быстро. Главное, что оно никак не сказывается на функционировании остальной системы. 

Где мы это запустили? 

В первую очередь мы запустили это решение для больших пользовательских фотографий. Напомню, что у нас их 1,6 миллиарда. В среднем каждая из них имеет размер в 60 килобайт. Мы собрали кластер их 24 серверов. Это SuperMicro – дешевые сервера по 16 гигабайт памяти, по 24 диска в каждом. 

Из них мы 20 реально используем под данные, 2 диска под систему и под логи транзакций (в RAID), 2 у нас резервные. Почему 2 в резерве? Мы посоветовались с администраторами, и они нам сказали, что это будет самая правильная и логичная схема. Если выйдет из строя диск, можно будет сразу нажать кнопку «восстановить данные». Они будут восстанавливаться на этом же сервере, но на соседнем диске. Потом, когда будет время, кто-то придет и поменяет все, что нужно.

Как мы оценивали результаты работы?

У нас очень много всякой статистики. Есть сбор статистики по серверам, ЦП, сети и всему остальному. Плюс у нас есть очень много всякой статистики из приложения. Мы можем посмотреть среднее время, ошибки, количество вызовов, максимальное время. Все это мы можем сделать по конкретному серверу, по клиенту, по методу, по диску, по комбинации клиент-сервер.

Что касается времени ожидания… Мы провели сначала синтетическое тестирование с заведомо большей нагрузкой, чем у нас будет в продакшне. Мы получили порядка 2000 чтений в секунду с сервера (это 100 чтений с диска). По большому счету, там больше ничего не выжмешь, потому что это вращающийся диск. 

Понятно, что если фотографии большие, то мы будем "упираться" в возможности сети. 2000 на 60 килобайт – это явно больше, чем гигабит. У нас пока стоят гигабитные интерфейсы. Предел наших возможностей – это 900 мегабит. Нам этого хватает, потому что наша номинальная нагрузка в продакшне сейчас составляет 600 чтений в секунду и 350 мегабит в секунду. То есть у нас есть запас примерно в 2,5 раза. Пока все довольны.

Немного о мониторинге

У нас есть внутренняя система мониторинга, с помощью которой мы мониторим, во-первых, каждый Zookeeper сервер, во-вторых, каждый сервер хранилищ, в том числе на предмет доступности дисков, наличия соединения с Zookeeper, хранилища хинтов (для Hinted Handoff) и ошибок. 

Плюс мы осуществляем мониторинг всего кластера целиком, проверяем, нет ли потерь. Допустим, мы считаем, что должен быть какой-то диск, а его просто нигде нет в системе.

Вот и все. Напоминаю, что меня зовут Александр Христофоров. Мы с коллегой Олег Анастасьев работаем в «Одноклассниках». Мы готовы ответить на ваши вопросы.

Спасибо.

Вопросы и ответы

Вопрос из зала: Из вашего доклада не совсем понял, есть ли на диске, который "поднимается", какая-то файловая система или вы просто, грубо говоря, "бьете" его низкоуровневыми операциями?

Александр Христофоров: Мы используем xfs.

Вопрос из зала: Здравствуйте! Спасибо. Скажите, пожалуйста, как происходит удаление файла? Я правильно понял, что закачка происходит с 20-ти "размазанных" дисков, как вы говорите? Но это же все равно физически одна нода. Или с нескольких нод происходит закачка?

Александр Христофоров: Нет-нет. Это будут разные ноды. Когда мы строим таблицу маршрутизации, реплики должны "размазаться" достаточно равномерно. Скажем так, сейчас на один диск приходится где-то 10 секций. Мы выбрали такую конфигурацию логически. Эти 10 секций, скорее всего, будут более или менее равномерно "размазаны" по 20-ти дискам. Эти 20 дисков, скорее всего, будут на разных машинах. Это вероятностная штука, поэтому мы не следим, чтобы это было так уж строго соблюдено. 

Вопрос из зала: Понятно. Как происходит удаление?

Александр Христофоров: Удаление реализовано точно так же, как обновления. Мы пишем в индекс: все, больше нет данных. В файле остается пустая часть, и мы помечаем, что реально там используется уже не 256 мегабайт, а 255, чтобы потом определить, когда нужно сделать уплотнение (англ. compaction). Все. Там точно так же – если есть какие-то проблемы, то существуют хинты, которые точно так же пишутся, потом обрабатываются, если сервер был дан.

Реплика из зала: Спасибо.

Вопрос из зала: Скажите, пожалуйста. Восстановление происходит путем перезаписи полностью одного диска?

Александр Христофоров: Да.

Вопрос из зала: Сколько это примерно занимает времени?

Александр Христофоров: Сложно сказать.

Вопрос из зала: 100 мегабайт в секунду в лучшем случае на…

Александр Христофоров: Это часы. В пределах дня. 

Вопрос из зала: С какой вероятностью в вашей вероятностной модели за это время происходит выход из строя еще двух дисков?

Александр Христофоров: Когда мы только запустили эту систему, и все "железо" было новым, необкатанным, у нас вышли из строя нода и два диска. В принципе, все работало. Только какой-то маленький процент людей не мог закачивать фотографии. Это понятно, потому что есть пересечение при таком количестве. По сути, мы потеряли 26 дисков в тот момент. 

Вопрос из зала: Были потеряны нода и два диска на разных нодах?

Александр Христофоров: По-моему, да. Мы потеряли два диска на разных нодах и ноду целиком.

Вопрос из зала: Если равномерно "размазывать", получается, что при выходе из строя трех дисков у вас практически со стопроцентной вероятностью должны теряться какие-то файлы, если восстановление невозможно.

Александр Христофоров: В принципе, да. Если даже так совпадет, и мы потеряем 3 диска, и там действительно будут все 3 реплики этих данных (что крайне маловероятно), то мы потеряем очень маленький процент данных. Это лучше, чем иметь полную реплику на трех дисках и вдруг (с еще меньшей вероятностью) потерять 3 реплики. Тогда будет совсем все плохо. Или хотя бы две из трех.

Вопрос из зала: Не рассматривали ли возможность восстановления, например, со всех дисков на все, чтобы снизить время из-за того, что секционирование было бы намного сложнее?

Александр Христофоров: Ну, да. Честно говоря, я даже не думал над этим.

Вопрос из зала: Контрольные суммы вы всегда считаете? Как еще целостность проверяете?

Александр Христофоров: Когда мы пишем, мы всегда считаем контрольную сумму. При чтении мы целостность не проверяем.

Вопрос из зала: Считаете контрольную сумму на каждый файл?

Александр Христофоров: На каждый файл логически. Для каждой фотографии в конце дописывается ЦИК (CRC). Есть идея периодически запускать сканы данных на предмет целостности. Можно попробовать проверять их каждый раз. Конечно, это тоже не проблема. Но это пока на стадии обсуждения. Возможно, мы так и сделаем.

Реплика из зала: Хорошо. Спасибо.

Вопрос из зала: Скажите, пожалуйста, сколько Zookeeper-серверов вы используете здесь?

Александр Христофоров: Пока у нас их 3. 

Вопрос из зала: Они одновременно работают и отвечают, у них кластер между собой?

Александр Христофоров: Да, да. Они выбирают один ведущий сервер, который координирует запись, а чтения идут…

Вопрос из зала: Это помимо 24-х серверов или 3 из этих 24-х?

Александр Христофоров: Нет-нет. Это помимо. Я считал их отдельно. Мы их поставили и планируем использовать для других задач в будущем.

Реплика из зала: Спасибо.

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

Александр Христофоров: Я такого не говорил. Хотя вообще это вполне разумная идея.

Вопрос из зала: Сейчас все-таки 4 версии картинки хранятся?

Александр Христофоров: Сейчас мы храним все 4, да. Наверное, это имеет смысл. Пока мы решали конкретную задачу – взять и переложить данные из старого хранилища, которое уже едва справлялось, в новое. Мы не пытаемся решать сверхзадачи.

Вопрос из зала: Вы говорили, что фотографии вы группируете и складываете в большие файлы. Не пробовали хранить их просто на Razer – каждая фотография в своем файлике?

Александр Христофоров: Нет, не пробовали.

Вопрос из зала: Какой смысл был реализовывать свою систему директорий, когда такие системы, как Razer FS справляются с десятками миллионов файлов вполне хорошо.

Олег Анастасьев: Я отвечу. Мы как-то пробовали Razer FS ради проверки быстродействия как раз на работе с массой маленьких файлов. Он потратил все ресурсы ЦП, но не смог даже один диск обработать. 

Вопрос из зала: Там, может быть, группировать каталоги можно было?

Александр Христофоров: Мы по-разному их группировали.

Вопрос из зала: Просто у меня получалось 100 миллионов файлов хранить на одном диске. Правда, mountain mount происходит. Это был первый вопрос. Второй вопрос – вопрос производительности. Нет ли у вас проблем с сетью при таких объемах работы с диском? Вы ведь Linux используете?

Александр Христофоров: Да.

Вопрос из зала: Я заметил, что у Linux есть такая особенность: когда есть большое число записей, почему-то начинает тормозиться весь сетевой стек. У вас такого не наблюдалось? Если наблюдалось, как вы решали эту проблему и решили ли?

Александр Христофоров: Да, я согласен, деградация есть. При пиковых нагрузках, когда речь идет уже о 900 мегабитах, деградация ощутима. Но для нас она лежит в пределах нормы. Это все равно лучше, чем наше старое решение. Ничего плохого в этом нет. Пока, честно говоря, мы с этим особо не работали. При номинальных нагрузках таких проблем точно нет. 

Вопрос из зала: Понятно. Эта проблема легко решается с помощью NFS. Но для этого надо много маленьких файлов. С большими файлами по NFS не поработаешь. Просто такое замечание. 

Александр Христофоров: Опять же – 100 миллионов, миллиард – может быть, есть большая разница. 

Вопрос из зала: Спасибо за доклад. Можно немного истории – какие решения у вас были до этого, на каких объемах, масштабах вы поняли, что надо мигрировать на что-то?

Александр Христофоров: Я говорил: у нас была BerkeleyDB. Олег знает, что использовалось до этого.

Олег Анастасьев: В самом-самом начале в качестве бинарного хранилища использовалась SQL-база. Это работало на 2-3 тысячах пользователей, которые были сначала. Потом стало понятно, что в SQL-базе столько данных хранить невозможно. Было сделано решение на основе BerkeleyDB, про которое Саша рассказывал. Оно довольно неплохо работало в начале проекта. Проблемы мы стали ощущать где-то с 2009-го года.

Вопрос из зала: Скажите, пожалуйста. Вы это новое хранилище сделали для того, чтобы избавиться от проблем со старым... Как проходила миграция со старого хранилища на новое? Сколько времени это заняло? Выводили вы сразу из работы серверы или нет?

Александр Христофоров: Расскажу. Процесс был довольно непростой, небыстрый. Что мы сделали? Сначала мы собрали миникластер, нагенерировали туда фэйковых данных, проверили, что эта схема рабочая. Убедились, что с нагрузками все хорошо, что можем вывести, завести – все отлично. Потом "подняли" продакшн-кластер, написали вещь, которая читает из старого, а пишет в новое. Параллельно шла запись новых данных в новое хранилище. 

Миграцию мы запустили, и решение с переменной нагрузкой в течение дня, чтобы не "положить" существующую инфраструктуру, забирало фотографии просто одну за одной. Суммарно это заняло 7 дней чистого времени. За 7 дней мы "перелили" все данные. Там было 3 машины, которые буквально ночью, когда нагрузка минимальная 3 гигабита писали. Грубо говоря, 1 гигабит читали, 3 гигабита писали. Таким образом, мы за 7 дней справились.

Потом мы переключили чтение. Посмотрели, что все читается по-новому, но запись у нас пока еще идет в 2 места. Уже месяц мы его эксплуатируем. Страшно отключать. Пока записываем в 2 места, как я уже сказал, но на этой неделе как раз планируем отключение старого хранилища.

Вопрос из зала: Чтение уже полностью отключили?

Александр Христофоров: Да, да, чтение давно уже отключили.

Вопрос из зала: Резервные копии, восстановления – запланированы ли эти процедуры, решается?

Александр Христофоров: Создавать резервные копии мы сейчас не планируем. У нас есть 3 реплики. Единственное, что мы копируем сейчас, это индексный файл. Просто делаем копию на системный диск. Он в RAID. Мы не хотим потерять его из-за выпада сервера или какой-то ошибки в коде.

Вопрос из зала: Ошибка ПО не страшна?

Александр Христофоров: Страшно, поэтому сделали резервную копию этого индекса.

Вопрос из зала: Кроме того, что нет резервного копирования, я правильно понимаю, что все 496 винчестеров находятся в одном дата-центре?

Александр Христофоров: Сейчас да.

Вопрос из зала: На самом деле, тогда третья копия данных лишняя, потому что выход из строя всего дата-центра более вероятен, чем выход из строя трех винчестеров одновременно.

Александр Христофоров: Вполне возможно.

Вопрос из зала: Здравствуйте. Скажите, если у вас поменяются размеры фотографий, вы что будете делать? Перегенерируете?

Александр Христофоров: Да, будем "перерезать". Ну, сейчас совпало очень хорошо, что мы будем их переносить.

Вопрос из зала: Сколько времени это может занять?

Александр Христофоров: Много, наверное. Мы не считали. Как раз посчитаем. В ближайшие недели будем запускать "нарезку" нового размера, и посчитаем, сколько это будет занимать.

Реплика из зала: Понятно. Спасибо.

Вопрос из зала: Скажите, пожалуйста. Я так понимаю, вы эту систему писали с нуля?

Александр Христофоров: Да.

Вопрос из зала: Сколько человек писало это и сколько времени?

Александр Христофоров: Один человек, 3 месяца.

Вопрос из зала: Второй вопрос. Если один винчестер ломается, то откуда вы знаете, какие файлы нужно заново скопировать? Вы говорили, что вы не храните, что где лежит.

Александр Христофоров: Да. Все очень просто. Мы можем опросить все ноды. Каждая нода знает, какие у нее есть ID. Все эти ID в памяти. Обойти все ID очень легко. Для каждого ID проверить, на каком диске все 3 реплики должны лежать, тоже очень легко. Обход всех ключей, определение, какие ключи…

Вопрос из зала: То есть вы перебираете все полтора миллиарда…

Александр Христофоров: Да. Но это очень быстро происходит, поверьте.

Вопрос из зала: Скажите, что сейчас мешает расползтись на несколько дата-центров, масштабироваться?

Александр Христофоров: Коллеги есть в зале, они могут ответить.

Реплика из зала: Ничто не мешает, есть более приоритетные задачи.

Александр Христофоров: Вот. Ничто не мешает, есть более приоритетные задачи.

Вопрос из зала: Принципиальных проблем с этим нет?

Александр Христофоров: Принципиальных проблем нет. Мы это сделаем. Просто мы и так уже переезжали из дата-центра в дата-центр. Это не такой простой процесс.

Вопрос из зала: Здравствуйте. Вы рассматривали другие решения... Можно поподробнее рассказать какие, делали ли вы там какие-нибудь замеры? К примеру, в вашем решении отдачи быстрее на столько-то, диски – на столько-то дефрагментируются и так далее.

Александр Христофоров: Очень много решений мы отмели по политическим соображениям, грубо говоря, или по рекомендациям коллег. 

Как я говорил, мы очень плотно тестировали HDFS, но больше для хранения больших данных, типа видео. Отмели мы этот вариант потому, что он недостаточно надежен. High Availability Name Node не доработана до сих пор. Плюс на name-нодах очень много памяти тратится на поддержание этого индекса, указывающего, где какой файл лежит. При нашем количестве нужны терабайты памяти. Это нереально. GGC бы заткнулся уже при десятой части этих файлов. Поэтому мы отмели и его.

Что еще было? Была идея взять VkRate, прикрутить к нему интерфейс удаленного доступа и логику репликации. Просто взять его как ту часть, которая что-то хранит на диске. Я по тестам оказалось все не очень радужно. Там, по факту, приходилось больше чем 1 чтение с диска на запрос. Из-за этого он мог обслужить меньше запросов. Как-то так.

Вопрос из зала: Было ли рассмотрено решение такого типа: маленькие файлы хранить и отдавать где-нибудь в другом месте (из памяти, например), а большие, действительно, хранить как файлы?

Александр Христофоров: Когда я говорил про всю эту картинку целиком, те HTTP-серверы, которые отдают картинки, на самом деле, имеют кэш внутри. Маленькие картинки (аватары на «Одноклассниках» и другие мелкие картинки) практически все лежат в памяти. Перенести 150 тысяч чтений на диск – я думаю, это глупо. Большие практически все попадают на диск, а мелкие – да.

Реплика из зала: Спасибо.

Вопрос из зала: Какие решения используете для кэширования?

Александр Христофоров: У нас свои кэши написаны. Для фотографий конкретно написан маленький кусочек на C.

Вопрос из зала: То есть там работает HTTP-демон, и он сам кэширует все?

Александр Христофоров: Это TomCat, в нем…

Вопрос из зала: Ваш модуль на "Си", который кэширует фотографии.

Александр Христофоров: Да, да.

Вопрос из зала: Как вы очищаете? Есть какая-то политика очистки этого кэша?

Александр Христофоров: Он вытесняет просто.

Вопрос из зала: То есть не вы занимаетесь этим? Это уже готовое…

Александр Христофоров: Это тысячу лет написанная штука. Он просто вытесняет по размеру.

Реплика из зала: Понятно.

Вопрос из зала: Скажите, почему вы храните только бинарные данные в этом хранилище, а какие-то текстовые – нет?

Александр Христофоров: Просто мы его месяц как эксплуатируем полноценно. У нас есть какие-то идеи. Возможно, мы и будем использовать его для чего-то другого. Пока только идеи.

Вопрос из зала: Сейчас текст где?

Александр Христофоров: У нас есть много чего. Есть SQL-сервера, есть по-прежнему Berkeley, для многих задач он хорошо работает. Есть Cassandra, есть Tarantool и Voldemort. У нас много чего есть.

Вопрос из зала: С текстом нет таких проблем, как с бинарными данными?

Александр Христофоров: Я даже не знаю, где у нас текст. Разве что поиск – там мы храним документы.

Реплика из зала: Все равно есть какие-то посты, что-то еще...

Александр Христофоров: Это практически все в Berkeley. В системе сообщений тоже использовали Berkeley, в памяти. Но сейчас мы плавно мигрируем на новое хранилище, где мы используем комбинацию Tarantool и Voldemort.

Вопрос из зала: Подскажите, какой протокол используется для передачи данных в это хранилище?

Александр Христофоров: В это – буквально бинарный протокол, очень простой, самописный.

Вопрос из зала: Тоже на каждой ноде "висит" какой-то демон, который фактически реализует файловую систему?

Александр Христофоров: Нет, что-то слишком сложно, не понял. Там он "слушает" по TCP, у этого демона есть несколько команд. Команды типа «положи на такой-то диск кусок байтов», «дай мне с такого-то диска кусок байтов» и все.

Вопрос из зала: Логика связи имени файла с данными на диске (фактически сама файловая система) где реализуется?

Александр Христофоров: Мы не храним имя файла, у нас этого нет. У нас есть просто ключ. Этот ключ – 8-байтовое число и тип. ID фотографии и тип фотографии – вот наш ключ.

Вопрос из зала: На страницу вы что встраиваете?

Александр Христофоров: Ссылку. В ней ID, в качестве параметра ID и тип.

Вопрос из зала: Трансляцию осуществляет, таким образом, веб-сервер?

Александр Христофоров: Ну, да. Я не очень понял вопрос.

Олег Анастасьев: У нас с этой позицией более простая ситуация. У нас практически все написано на одном языке. У нас нет проблем связи разных подсистем друг с другом. 

Вот этим клиентом на слайде является веб-сервер, который рендерит основной портал. Он имеет у себя в памяти реплицированную копию всех версий таблиц маршрутизации. Соответственно, вопрос генерации ссылки для этой фотки – это просто вопрос просмотра пары таблиц в памяти и делания строчки. Это очень быстро. Это еще одна причина, по которой мы хотели отказаться от какого-то центрального места, где хранится маппинг между фоткой и диском, где она лежит.

Реплика из зала: У меня комментарий к одному из предыдущих вопросов. При перенесении этой архитектуры на несколько дата-центров будут проблемы с кворумом Zookeeper, он быстро работает только в одном дата-центре.

Александр Христофоров: Возможно. Честно, мы не проверяли. С другой стороны, для нас это небольшая проблема, потому что мы туда очень мало пишем по факту. Мы только читаем оттуда.

Вопрос из зала: Скажите, пожалуйста. У вас этот бинарный поток отдает TomCat. Не используете там nginx, ничего такого?

Александр Христофоров: Сейчас мы отдаем с помощью TomCat, но с нативным коннектором. В принципе, мы вполне им довольны. 

Вопрос из зала: У меня вопрос связан с предыдущим, который был по поводу построения ссылок. То есть перед вами не стоит задача защищать ссылки, они не авторизованы, скажем так?

Александр Христофоров: Ссылки на скачивание фотографий?

Вопрос из зала: Да. 

Александр Христофоров: Нет, у нас можно заходить, качать.

Вопрос из зала: Имеется в виду, если пользователь закрыл фотографию и открыл ее только для своих друзей, то, получив ссылку на эту фотографию, не являясь другом пользователя, я смогу ее посмотреть?

Александр Христофоров: В общем, да. Честно говоря, приватность у нас – достаточно новый сервис. Я даже не знаю, в какую сторону он развивается. Нет-нет. Я к тому, что я, правда, даже не знаю, как он работает. Я не пользовался приватностью на «Одноклассниках», поэтому я не знаю, можно ли там защитить фотку. 

Реплика из зала: Понятно. Спасибо.

Вопрос из зала: То есть, в принципе, у вас может возникнуть такая проблема с защитой фотографий?

Александр Христофоров: На самом деле, это не особая проблема. Все равно мы ходим всегда через TomCat. Даже если надо защитить фотку – окей, построим авторизованную ссылку, проверим ее на TomCat. Точно так же мы делаем сейчас при загрузке новых данных. При загрузке мы проверяем, у нас ссылка авторизованная. Это очень быстро можно сделать, это очень легко.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Валентин Нечаев

Валентин Нечаев

Ведущий инженер по разработке программного обеспечения в компании «Massive Solutions Ltd».

Валентин Нечаев (Massive Solutions Inc.) рассказывает о проблемах мониторинга современных вычислительных кластеров.

Георгий Баркан

Георгий Баркан

Руководитель разработки технологической стратегии развития пользовательских продуктов «Лаборатории Касперского».

Рассказ об управлении продуктом с точки зрения успеха этого процесса.

Александр Сидоров

Александр Сидоров

Руководитель группы антивирусных проектов, Яндекс.

Доклад специалистов "Яндекса", содержащий простые правила, которые позволят избежать заражения сайта и справиться с последствиями заражения, если оно произошло.