Хабрахабр

Как правильно использовать доступный объем хранилища

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

Каждая игра может весить десятки или сотни гигабайт, например, «Ведьмак 3» занимает 50 Гбайт, а «Call of Duty: Black Ops III» — 113. Для облачного сервиса все иначе — у него возникают проблемы хранения данных. Кроме хранения сотен игр, сервису нужно решать, какой объем хранилища выделять на одного игрока, и масштабироваться, когда их будут тысячи. При этом игроки не будут пользоваться сервисом с 2-3 играми, как минимум нужно несколько десятков.

Покупать «облака»? Хранить ли все это на своих серверах: сколько их нужно, где ставить дата-центры, как «на лету» синхронизировать данные между несколькими дата-центрами? Можно ли хранить данные пользователей со сжатием в 5 раз и предоставлять их в real-time? Использовать виртуальные машины? Как исключить любое влияние пользователей друг на друга при последовательном использовании одной и той же виртуальной машины?

Владимир Рябов — руководитель отдела системного администрирования — подробно расскажет о технологии ZFS для FreeBSD, которая в этом помогла, и ее свежем форке ZOL (ZFS on Linux).
Все эти задачи успешно удалось решить в Playkey.net — облачной игровой платформе.

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

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

Игры надо хранить и обновлять

Основные данные для облачного игрового сервиса — это дистрибутивы игр, которые могут переваливать за сотню Гбайт, и пользовательские сохранения.

Все данные мы хранили локально на серверах, обновлялись вручную, все устраивало. Когда мы были маленькие, у нас был всего десяток серверов и скромный каталог на 50 игр. Но пришло время расти и мы отправились в облака AWS.

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

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

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

Проблемы роста

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

На тот момент в Amazon он был платный даже между внутренними виртуалками. BTSync создала еще одну проблему с избыточным сетевым трафиком. В результате обновление даже на 15 Мбайт могло породить десятки Гбайт трафика синхронизации. Если классический лаунчер игры вносит небольшие изменения в большие файлы, то BTSync сразу же считает, что весь файл изменился, и начинает полностью перекачивать его на все ноды.

Как раз вышла новая игра World of Warships. Ситуация стала критична, когда хранилище подросло до 1 ТБайта. BTSync никак не могла ее переварить и раздать на все остальные серверы — это тормозило раздачу остальных игр. В ее дистрибутиве было несколько сотен тысяч мелких файлов.

Все эти факторы создали две проблемы:

  • плодить локальные хранилища дорого, неудобно и трудно актуализировать;
  • облака оказались очень дорогими.

Мы решили вернуться обратно к концепции своих физических серверов.

Своя система хранения данных

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

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

Мы подумали над проблемой, проверили несколько вариантов решения, и пришли к ZFS — Zettabyte File System на FreeBSD.

ZFS на FreeBSD

Это не совсем обычная файловая система. Классические системы изначально устанавливаются на одно устройство, а для работы с несколькими дисками уже требуют менеджер томов.

ZFS изначально строится на виртуальных пулах.

Они называются zpool и состоят из групп дисков или RAID-массивов. Весь объем этих дисков доступен для любой файловой системы в рамках zpool. Все потому, что ZFS изначально разрабатывалась как система, которая будет работать с большими объемами данных.

Как ZFS помогла решить наши проблемы

В этой системе есть замечательный механизм создания снимков и клонов. Они создаются моментально, и весят всего несколько Кбайт. Когда мы вносим изменения в один из клонов, он увеличивается на объем этих изменений. При этом данные в остальных клонах не меняются и остаются уникальными. Это позволяет раздать диск объемом 10 Тбайт с монопольным доступом конечному пользователю, затратив всего несколько Кбайт.

Нет, мы выяснили, что даже в достаточно длинных игровых сессиях набор изменений редко превышает 100-200 Мбайт — это не критично. Если клоны растут в процессе внесения изменений на игровой сессии, то не займут ли они столько же места, сколько все игры? Поэтому, мы можем выдать полноценный доступ к полноценному жесткому диску большого объема нескольким сотням пользователей одновременно, потратив всего лишь 10 Тбайт с хвостиком.

Как работает ZFS

Описание кажется сложным, но работает ZFS достаточно просто. Разберем его работу на простом примере — создадим zpool data из доступных дисков zpool create data /dev/da /dev/db /dev/dc.

На продакшн так делать не надо, потому что если хотя бы один диск умрет, весь пул уйдет в небытие вместе с ним. Примечание. Лучше используйте группы RAID.

Устройство доступно по адресу /dev/zvol/data/games/disk как обычный диск — с ним можно проводить те же манипуляции. Создаем файловую систему zfs create data/games, а в ней блочное устройство с названием data/games/disk объемом 10 Тбайт.

Отдаем этот диск по iSCSI нашему мастеру обновлений — обычной виртуальной машине под управлением Windows. Дальше начинается самое интересное. Подключаем диск, и ставим на него игры просто из Steam, как на обычном домашнем компьютере.

Теперь осталось раздать эти данные на 200 серверов для конечных пользователей. Заполняем диск играми.

  • Создадим моментальный снимок этого диска и назовем его первой версией — zfs snapshot data/games/disk@ver1. Создаем его клон zfs clone data/games/disk@ver1 data/games/disk-vm1, который уйдет в первую виртуальную машину.
  • Отдаем клон по iSCSI и KVM запускает виртуалку с этим диском. Она загружается, уходит в пул доступных серверов для пользователей, и ожидает игрока.
  • Когда пользовательская сессия завершена, забираем с этой виртуалки все пользовательские сохранения и складываем их на отдельный сервер. Саму виртуальную машину выключаем и уничтожаем клонzfs destroy data/games/disk-vm1.
  • Возвращаемся к первому шагу, снова создаем клон и запускаем виртуальную машину.

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

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

Цифру 200 мы вычислили экспериментальным путем: это то количество серверов, при котором не возникают критические нагрузки на диски СХД. По этой схеме игры раздаются всего на 200 серверов. Если у вас профиль нагрузки отличается, то и цифра будет другая. Все потому, что у игр достаточно специфический профиль нагрузки: они много читают на этапе запуска или на этапе загрузки уровня, а в процессе игры, наоборот, практически не используют диск.

Сейчас мы можем потратить чуть больше 10 Тбайт под основной набор данных, и в запасе еще есть 0,5 Тбайта под пользовательские изменения. В старой схеме на одновременное обслуживание 200 пользователей нам потребовалось бы 2000 Тбайт локального хранилища. Хотя ZFS и любит, когда у нее в пуле остается хотя бы 15% свободного места, мне кажется, что мы существенно сэкономили.

Что если у нас несколько дата-центров?

Этот механизм будет работать только внутри одного дата-центра, где серверы с системой хранения данных соединены хотя бы 10 гигабитными интерфейсами. Что делать, если ДЦ несколько? Как обновлять основной диск с играми (датасет) между ними?

Команда на выполнение очень простая:
Для этого у ZFS есть свое решение — механизм Send/Receive.

zfs send -v data/games/disk@ver1 | ssh myzfsuser@myserverip zfs receive data/games/disk

Механизм позволяет передать с одной СХД на другую моментальный снимок с основной системы. В первый раз придется отправить на пустую СХД все 10 Тбайт данных, которые записаны на master-ноде. Но при следующих обновлениях мы будем отправлять только изменения с момента создания предыдущего моментального снимка.

В результате получаем:

  • Все изменения вносятся централизованно на одной СХД. Дальше они разъезжаются по всем остальным дата-центрам в любом количество, а данные на всех нодах всегда идентичны.
  • Механизму Send/Receive не страшен обрыв связи. Данные к основному датасету не применяются до тех пор, пока они полностью не передадутся наslave-ноду. При обрыве связи повредить данные невозможно, и достаточно лишь повторить процедуру отправки.
  • Любая нода легко может стать master-нодой во время аварии всего за несколько минут, так как данные на всех нодах всегда идентичны.

Дедупликация и бэкапы

В ZFS есть еще одна полезная функция — дедупликация. Эта функция помогает не хранить два одинаковых блока данных. Вместо этого хранится только первый блок, а на месте второго хранится ссылка на первый. Два одинаковых файла будут занимать место как один, а при совпадении на 90%, будут заполнять 110% от изначального объема.

В одной игре у разных пользователей сохранения похожи, многие файлы совпадают. Функция нам очень помогла в хранении пользовательских сохранений. Наш коэффициент дедупликации 5,22. За счет применения дедупликации мы можем хранить в пять раз больше данных. Это хорошо экономит место, позволяя не хранить дубли.
Физически у нас занято 4,43 Тбайта, умножаем на коэффициент, и получаем, что практически 23 Тбайта реальных данных.

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

Например, они хотят испытать потенциально опасную миграцию на боевой базе. Мы используем клоны для помощи нашим разработчикам. Поэтому мы просто снимаем клон с диска базы и добавляем его мгновенно в новый инстанс. Разворачивать классический бэкап базы, которая подбирается к 1 Тбайту, не быстро. Теперь разработчики могут совершенно спокойно там все протестировать.

ZFS API

Конечно, все это надо автоматизировать. Зачем лезть на серверы, работать руками, писать скрипты, если это можно отдать программистам? Поэтому мы написали свое простенькое Web API.

Теперь все операции с дисками строго централизованы и выполняются кодом, а мы всегда знаем статус каждого диска. В него мы завернули все стандартные функции ZFS, отрезали доступ к тем, которые потенциально опасны и могут сломать всю СХД, и отдали все это программистам. Все работает замечательно.

ZoL — ZFS on Linux 

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

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

В этом нам сильно помог относительно свежий форк ZFS on Linux — ZoL.

Теперь каждый сервер сам себе СХД.

Только он хранит не по 10 Тбайт данных, как в случае централизованной инсталляции, а лишь 1-2 дистрибутива тех игр, которые обслуживает. Для этого достаточно одного SSD-диска. Все это прекрасно работает: каждый следующий пользователь получает всегда чистую виртуалку, как и на боевой инсталляции.

Однако тут мы столкнулись с двумя проблемами.

Как обновлять?

Обновлять централизованно по SSH, как мы делаем в дата-центрах не получится. Пользователи могут быть подключены к локальной сети или просто выключены, в отличие от СХД, да и поднимать столько SSH-соединений со своей стороны не хочется.

Однако торренты поверх ZFS уже не получить. Мы столкнулись с теми же проблемами, что и при использовании rsync. Почему бы не записать данные в файл, вместо того, чтобы отправлять их до конечного пользователя? Мы внимательно подумали, как работает механизм Send: он отправляет все изменившиеся блоки данных на конечную СХД, где Receive применяет их к текущему датасету.

Это файл, в который последовательно записаны все измененные блоки между двумя последними моментальными снимками. В результате получилось то, что мы называем diff. Мы выкладываем этот diff на CDN, и по HTTP отдаем всем нашим пользователям: он включил машину, увидел, что есть обновления, выкачал и применил к локальному датасету, используя Receive.

Что делать с драйверами?

У централизованных серверов конфигурация одинакова, а у конечных пользователей всегда разные компьютеры и видеокарты. Даже если мы максимально заполним дистрибутив ОС всеми возможными драйверами, при первом запуске она все равно захочет поставить эти драйвера, потом обязательно перезагрузится, а потом, возможно, еще раз. Так как мы каждый раз предоставляем чистый клон, то вся эта карусель будет происходить после каждой пользовательской сессии — это плохо.

Но проблема в том, что если внести изменения в основной датасет, сломаются обновления, так как данные на источнике и на приемнике будут различаться, и diff просто не применится. Мы хотели провести какой-то инициализационный запуск: дождаться, пока Windows загрузится, установит все драйвера, выполнит все, что ей хочется, и уже потом оперировать с этим диском.

Однако ZFS гибкая система и позволила нам сделать небольшой костыль.

  • Как обычно создаем моментальный снимок: zfs snapshot data/games/os@init
  • Создаем его клон — zfs clone data/games/os@init data/games/os-init — и запускаем в режиме инициализации.
  • Ждем пока установятся все драйвера и все перезагрузится.
  • Выключаем виртуальную машину и снова создаем снимок. Но на этот раз уже не с изначального датасета, а с инициализационного клона: zfs snapshot data/games/os-init@ver1.
  • Создаем клон снимка уже со всеми установленными драйверами. Он больше не будет перезагружаться: zfs clone data/games/os-init@ver1 data/games/os-vm1.
  • Дальше работаем по классической связке.

Сейчас эта система находится на этапе альфа-тестирования. Мы проверяем ее на реальных пользователях без знаний Linux, но у них получается все это развернуть у себя. Наша конечная цель — чтобы любой пользователь мог просто воткнуть загрузочную флешку в свой компьютер, подключить дополнительный SSD-диск и сдать его в аренду на нашей облачной платформе.

Эта система умеет гораздо больше интересного и разного, но о ZFS мало кто знает — пользователи не стремятся рассказывать о ней. Мы обсудили только малую часть функциональности ZFS. Надеюсь, что после этой статьи в ZFS-community появятся новые пользователи.

В рассылке кроме того собираем новости готовящихся конференций и рассказываем, например, что интересного для DevOps-фанатов будет на Saint HighLoad++. Подпишитесь на телеграм-канал или рассылку, чтобы узнавать о новых статьях и видео с конференции DevOpsConf.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»