Собери сам: как мы сделали хранилище Amazon-style для небольших хостеров
Все больше российских интернет-проектов хотят сервер в клауде и посматривают в сторону Amazon EC2 и его аналогов. Бегство крупных клиентов на Запад мы воспринимаем как вызов для хостеров рунета. Им бы ведь тоже «свой Amazon», с преферансом и поэтессами. Чтобы удовлетворить спрос хостеров на распределенное хранилище данных для развертывания на относительно маленьких мощностях мы сделали Parallels Cloud Server (PCS).
В посте под катом я расскажу об архитектуре storage-части — одной из главных изюминок PCS. Она позволяет организовать на обычном железе систему хранения данных, по скорости и отказоустойчивости сравнимую с дорогостоящими SAN-хранилищами. Второй пост (он уже готовится) будет интересен разработчикам, в нем пойдет речь о вещах, которые мы узнали в процессе создания и тестирования системы.
Интро
Подавляющее большинство читателей Хабра знают о Parallels как о разработчике решения для запуска Windows на Mac. Еще часть аудитории знает про наши продукты для контейнерной виртуализации — платные Parallels Virtuozzo Containers и Parallels Server Bare Metal, а также опенсорсный OpenVZ. Идеями, которые лежат в основе контейнеров, в той или иной степени пользуются провайдеры облаков масштаба Яндекса и Google. Но вообще конёк Parallels — софт для небольших и/или быстрорастущих сервис-провайдеров. Контейнеры позволяют хостерам исполнять несколько копий операционных систем на одном сервере. Это дает в 3-4 раза большую плотность виртуальных сред, чем гипервизоры. Дополнительно контейнерная виртуализация позволяет осуществлять миграцию виртуальных сред между физическими машинами, и для клиента хостера или облачного провайдера это происходит незаметно.
Как правило, сервис-провайдеры для хранения клиентских данных используют локальные диски имеющихся серверов. С локальным хранилищем есть две проблемы. Первая: перенос данных часто осложняется размером виртуальной машины. Файл может быть очень большим. Вторая проблема: высокую доступность виртуальных машин сложно достичь, если сервер выключится, скажем, в результате сбоя энергоснабжения.
Обе проблемы можно решить, но цена решения довольно высока. Существуют системы хранения данных типа SAN/NAS storage. Это такие большие коробки, целые стойки, а в случае с крупным провайдером — дата-центры на много-много петабайт. Серверы подключаются к ним по какому-либо протоколу и складывают туда данные, либо берут их оттуда. SAN storage обеспечивает избыточное хранение данных, отказоустойчивость, в нем есть свой мониторинг состояния дисков, а также функция самовосстановления и исправления обнаруженных ошибок. Все это очень круто за одним исключением: даже простейший SAN storage потянет никак не менее чем на $100 тыс., что для наших партнеров – небольших хостеров – довольно большие деньги. Вместе с тем, им хочется обладать чем-то подобным, и они нам постоянно об этом говорят. Задача нарисовалась сама собой: предложить им решение аналогичной функциональности с заметно меньшей ценой покупки и владения.
Архитектура PCS: первые штрихи
Мы оттолкнулись от того, что наша интерпретация распределенного хранилища должна быть максимально простой. И исходили от задачи, которая также была несложной: система должна обеспечивать функционирование виртуальных машин и контейнеров.
Виртуальные машины и контейнеры предъявляют определенные требования к согласованности (consistency) хранимых данных. То есть результат операций над данными как-то соответствует тому порядку, в котором они производятся.
Вспомним, как работает любая журналируемая файловая система. У нее есть журнал, у нее есть метаданные и собственно данные. Файловая система заносит данные в журнал, дожидается окончания этой операции, после чего только данные пишутся на жесткий диск туда, где они должны быть. Затем на освободившееся в журнале место попадает новая запись, которая спустя какое-то время отправляется в отведенное ей место. В случае выключения питания или в случае падения системы все данные в журнале будут находиться там до закоммиченной точки. Их можно будет проиграть, записав уже в конечное место.
Существует большое количество видов consistency. Тот порядок, в котором все файловые системы ожидают видеть свои данные, называется immediate/strict consistency. Переведем это как «строгая согласованность». Отличительных особенностей строгой согласованности две. Во-первых, все чтения возвращают только что записанные значения: никогда не видно старых данных. Во-вторых, данные видны в том же порядке, в котором они были записаны.
Большинство клаудных решений для хранения данных таких свойств, как это ни удивительно, не предоставляют. В частности, Amazon S3 Object Storage, используемый для различных web-ориентированных сервисов, предоставляет совершенно другие гарантии – так называемую eventual consistency, или конечную во времени согласованность. Это такие системы, в которых записанные данные гарантированно видны не сразу, а спустя какое-то время. Для виртуальных машин и файловых систем это не подходит.
Дополнительно мы хотели, чтобы наша система хранения данных обладала еще рядом замечательных свойств:
- Она должна уметь расти по мере надобности. Объем хранилища должен увеличиваться динамически при добавлении новых дисков.
- Она должна уметь выделять пространства большее, чем есть на отдельно взятом диске.
- Она должна уметь разбивать объем выделяемого пространства на несколько дисков, потому что 100 Тб положить на один диск невозможно.
В истории с несколькими дисками есть подводный камень. Как только мы начинаем распределять массив данных по нескольким дискам, вероятность потери данных резко возрастает. Если у нас один сервер с одним диском, то вероятность того, что диск выйдет из строя, не очень большая. Но если в нашем хранилище 100 дисков, вероятность того, что сгорит хотя бы один диск, сильно увеличивается. Чем больше дисков, тем выше такой риск. Соответственно, данные нужно хранить избыточно, в нескольких копиях.
В распределении данных на много дисков есть и свои плюсы. Например, имеется возможность восстановить исходный образ сломавшегося диска параллельно с множества живых дисков одновременно. Как правило, в клаудном хранилище такая операция занимает считанные минуты, в отличие от традиционных RAID-массивов, которым на восстановление понадобится несколько часов или даже дней. Вообще вероятность потери данных обратно пропорциональна квадрату времени, требуемоего на их восстановление. Соответственно, чем быстрее мы восстановим данные, тем ниже вероятность того, что данные будут потеряны.
Решено: весь массив данных мы разбиваем на куски фиксированного размера (64-128 Мб в нашем случае), будем их реплицировать в заданном количестве копий и распространять по всему кластеру.
Далее мы решили всё по-максимуму упрощать. В первую очередь было ясно, что нам не нужна обычная POSIX-совместимая файловая система. Требуется всего-навсего оптимизировать систему для больших объектов – образов виртуальных машин – которые занимают несколько десятков гигабайт. Поскольку сами образы нечасто создаются/удаляются/переименовываются, то изменения метаданных происходят редко и эти изменения можно не оптимизировать, что очень существенно. Для функционирования контейнера или виртуальной машины внутри объектов есть своя файловая система. Она-то и оптимизирована для изменения метаданных, предоставляет стандартную POSIX-семантику и т.д.
Атака клонов
Часто можно слышать суждения о том, что у провайдеров выходят из строя только отдельные узлы кластеров. Увы, случается и так, что даже дата-центры целиком могут лишаться электропитания – падает весь кластер. С самого начала мы решили что нужно учитывать возможность таких сбоев. Вернемся немного назад и подумаем, почему в распределенном хранилище данных сложно обеспечить строгую согласованность хранимых данных? И почему крупные провайдеры (тот же Amazon S3) начинали с хранилища типа eventual consistency?
Проблема простая. Предположим, у нас есть три сервера, которые хранят по одной копии объекта. В какой-то момент в объекте происходят изменения. Эти изменения успевают записать два сервера из трех; третий же был по какой-то причине не доступен. Дальше на нашей стойке или в нашем ЦОДе пропадает питание, а когда возвращается, сервера начинают загружаться. И может случиться так, что сервер, на который не записались изменения объекта, загрузится первым. Если не предпринимать каких-то специальных мер, клиент может получить доступ к старой, неактуальной копии данных.
Если мы распиливаем большой объект (образ виртуальной машины в нашем случае) на несколько кусков, все становится только сложнее. Допустим, распределенное хранилище делит образ файла на два фрагмента, для записи которых понадобятся шесть серверов. Если не все сервера успевают внести изменения в эти фрагменты, то после сбоя питания, когда сервера загружаются, у нас появится уже четыре комбинации фрагментов, причем две из них никогда не существовали в природе. Файловая система на такое совсем не рассчитана.
Выходит, что надо каким-то образом приписывать версии к объектам. Это реализуемо несколькими способами. Первый – использовать транзакционную файловую систему (например, BTRFS) и вместе с апдейтом данных обновлять версию. Это правильно, но при использовании пока еще традиционных (вращающихся) жестких дисков – медленно: производительность падает в разы. Второй вариант – использовать какой-либо алгоритм консенсуса (например, Paxos), чтобы сервера, которые производят модификацию, договаривались между собой. Это тоже медленно. Сами же по себе сервера, которые отвечают за данные, не могут отслеживать версионность изменения фрагментов объекта, т.к. они не знают, менял ли данные кто-то еще. Поэтому мы пришли к выводу, что версии данных должны обновляться где-то на стороне.
За версиями будет следить metadata-сервер (MDS). При этом версию обновлять при успешной записи не нужно, нужно лишь когда один из серверов не совершил запись по какой-либо причине и мы его исключаем. Поэтому в нормальном режиме запись данных идет с максимально возможной скоростью.
Собственно, из всех этих выводов складывается архитектура решения. Она состоит из трех компонентов:
- Клиенты, которые общаются с хранилищем через обычный Ethernet.
- MDS, который хранит информацию о том, где какие файлы лежат и где находятся актуальные версии фрагментов.
- Сами фрагменты, распределенные по локальным жестким дискам серверов.
Очевидно, что во всей архитектуре MDS получается самым узким местом. Он знает о системе всё. Поэтому его нужно сделать highly available – чтобы в случае выхода какой-либо машины из строя система была доступна. Был соблазн «положить» MDS в базу данных типа MySQL или в другую популярную БД. Но это не наш метод, потому SQL-сервера довольно ограничены по числу запросов в секунду, что сразу накладывает крест на масштабируемость системы. К тому же сделать надежный кластер из SQL-серверов еще сложнее.Мы подглядели решение в статье про GoogleFS. В оригинальном исполнении GoogleFS под наши задачи не подходила. Она не обеспечивает желаемую strict consistency, поскольку предназначена для добавления данных к поисковыми серверам, но не для модификации этих данных. Решение получилось таким: MDS хранит полное состояние об объектах и их версиях в памяти целиком. Получается не так много. Каждый фрагмент описывают всего 128 байт. То есть описание состояния хранилища объемом в несколько петабайт влезет в память современного сервера, что приемлемо. Изменения состояния MDS записывает в журнал метаданных. Журнал растет, пусть и относительно медленно.
Наша задача – сделать так, чтобы несколько MDS’ов могли между собой как-то договариваться и обеспечивать доступность решения даже при падении одного из них. Но есть проблема: файл журнала не может расти бесконечно, с ним надо будет что-то делать. Для этого создается новый журнал, и все изменения начинают записываться туда, а параллельно с этим создается снапшот памяти, и состояние системы начинает асинхронно писаться в снапшот. После того как снапшот создан, можно старый журнал удалять. В случае падения системы нужно «проиграть» снапшот и журнал к нему. Когда новый журнал снова вырастает, процедура повторяется.
Еще хинты
Вот еще пара трюков, которые мы реализовали в процессе создания нашего распределенного облачного хранилища.
Как достигнута высокую скорость записи фрагментов? Как правило, в распределенных хранилищах используют три копии данных. Если писать «в лоб», то клиент должен разослать трем серверам запрос на запись с новыми данными. Получится медленно, а именно в три раза медленнее пропускной способности сети. Если у нас гигабитный Ethernet, то за секунду мы передадим всего лишь 30-40 Мб данных на каждую копию, что не является выдающимся результатом, потому что скорость записи даже на HDD существенно выше. Чтобы использовать ресурсы железа и сети более эффективно, мы применили chain-репликацию. Клиент передает данные первому серверу. Тот, получив первую часть (64 Кб), пишет ее на диск, и тут же параллельно отправляет остальным серверам по цепочке. В итоге большой запрос начинает писаться так рано, как это возможно, и асинхронно передается другим участникам. Получается, что железо используется на 80% от максимальной производительности, даже если речь идет о трех копиях данных. Все классно работает еще и потому, что Ethernet может одновременно принимать и отправлять данные (full duplex), т.е. в реальности выдает два гигабита в секунду, а не один. И сервер, принявший данные от клиента, с такой же скоростью отправляет их по цепочке дальше.
SSD-кэширование. SSD-диски помогают ускорить работу любого хранилища. Идея не нова, хотя надо отметить, что в open source действительно хороших решений нет. SAN-хранилища давно такое кэширование используют. Хинт основан на способности SSD выдавать на случайном доступе производительность на порядки выше, чем может выдать HDD. Начинаешь кэшировать данные на SSD – получаешь скорость в десятки раз выше. Дополнительно мы считаем контрольные суммы всех данных, и храним их тоже на SSD-дисках. Периодически вычитывая все данные, проверяем их доступность и соответствие контрольным суммам. Последнее называется еще иногда скраббингом (если помните, скраб для кожи удаляет с нее отвалившиеся частицы) и повышает надежность системы, а так же позволяет обнаруживать ошибки раньше, чем данные потребуются в реальности.
Существует еще одна причина, по которой SSD-кэширование важно для виртуальных машин. Дело в том, что у хранилищ объектов, например, Amazon S3 и его аналогов задержка доступа к объекту не очень важна. Как правило, доступ к ним осуществляется через интернет, и задержка в 10 мс там просто не заметна. Если же мы говорим о виртуальной машине на сервере хостинг-провайдера, то когда ОС выполняет ряд последовательных синхронных запросов, задержка накапливается и становится очень даже заметной. Более того, все скрипты и большинство приложений по своей природе синхронные, т.е. выполняют операцию за операцией, а не параллельно. В итоге секунды и даже десятки секунд уже заметны в пользовательском интерфейсе или каких-то откликах на пользовательские действия.
Результаты & выводы
В результате мы получили систему хранения данных, которая по свойствам подходит хостерам для развертывания на их инфраструктуре, потому что обладает следующими свойствами:
- Возможность исполнять виртуальные машины и контейнеры прямо из хранилища, т.к. наша система предоставляет семантику strong consistency.
- Возможность масштабироваться до петабайтных объемов на множество серверов и дисков.
- Поддержка контрольных сумм данных и SSD-кэширования.
Пара слов о производительности. Она получилась сравнимой с производительностью энтепрайзных хранилищ. Мы брали на тест у компании Intel кластер на 14 серверов с 4x HDD и получили 13 тыс. случайных операций ввода/вывода в секунду с очень мелкими (по 4 Кб) файлами на обычных жестких дисках с интерфейсом SATA. Это довольно много. SSD-кэширование ускорило работу того же хранилища практически на два порядка — мы приблизились к 1 млн. операций i/o в секунду. Скорость восстановления одного терабайта данных в составила 10 мин, причем чем больше количество дисков, тем быстрее идет восстановление.
Аналогичное по параметрам SAN-хранилище будет стоить от нескольких сотен тысяч долларов. Да, его покупку и обслуживание может себе позволить крупная компания, но мы делали свое распределенное хранилище для хостеров, которые хотели бы получить решение аналогичных параметров за несравненно меньшие деньги и уже на существующем оборудовании.
***
Как было сказано в начале, «продолжение следует». Сейчас готовится пост о том, как разрабатывалась storage-часть Parallels Cloud Server. В комменты принимаются предложения, о чем вам было бы интересно прочитать. Я постараюсь их учесть.