Не секрет, что именно в Discord сейчас принято вести беседы; каждый день через эту платформу проходит 4 миллиарда сообщений от миллионов людей. На наш взгляд – убедительно. Но текстовый чат – лишь малая толика тех возможностей, что поддерживает Discord. Здесь предусмотрены серверные роли, пользовательские эмодзи, видеозвонки и многое другое. Вся эта информация складывается в терабайты данных, которые Discord доставляет клиентам.
Для предоставления такого колоссального объема данных эксплуатируется набор кластеров NoSQL-баз данных (на основе ScyllaDB), и каждый из этих кластеров является источником истины для соответствующего множества данных. Поскольку Discord – это платформа для чатов в реальном времени, требуется, чтобы базы данных справлялись с плотным потоком запросов настолько быстро, насколько возможно.
Масштабирование сверх имеющегося аппаратного обеспечения
Сильнейшим образом на производительности базы данных сказываются задержки при отдельных дисковых операциях – сколько времени требуется для считывания данных с физического носителя и для записи на носитель. Когда активность запросов к базе данных не превышает некоторого порога, задержка на уровне диска остаётся незаметной, поскольку базы данных отлично справляются с распараллеливанием запросов (без блокирования на единственной дисковой операции). Но такой параллелизм ограничен. После превышения некоторого порога база данных должна дожидаться, пока не закончится операция, ожидающая выполнения; только затем можно переходить к следующей операции. Если приплюсовать это время к характерной дисковой задержке (на выполнение операции диск тратит одну-две миллисекунды), то рано или поздно база данных придёт к тому, что немедленно выбирать данные для вновь поступающих запросов станет невозможно. В результате дисковые операции и запросы начинают «откладываться», замедляя клиент, с которого пришёл запрос – в результате снижается производительность приложения в целом. В худшем случае можем получить лавинообразно нарастающую очередь дисковых операций, задержка которых зависит от того, в течение какого периода диск остаётся доступен. Именно такая ситуация случается на серверах Discord – база данных сообщает о постоянно нарастающей очереди операций на считывание, и запросы начинают выполняться с задержкой.
Но постойте: уделять целую миллисекунду, а то и две, на выполнение дисковой операции? Почему так вообще происходит, если обычно задержка диска измеряется в микросекундах?
Большая часть аппаратных мощностей Discord работает на Google Cloud, а в этом облаке предоставляется оперативный доступ к «локальным SSD» — энергонезависимым твердотельным дискам, для которых характерны кратчайшие задержки. К сожалению, при тестировании были выявлены многочисленные проблемы с надёжностью, поэтому не хотелось зависеть от такого решения при хранении критически важных данных. Пришлось возвращаться к маркерной доске и размышлять: как добиться минимальных задержек, если не приходится рассчитывать на сверхбыстрое хранилище данных на устройстве?
Другое главное средство дискового хранения данных в GCP называется «персистентные диски». Такие диски можно на лету прикреплять к серверам и откреплять от них на лету. Их размеры можно изменять без вывода из эксплуатации, в любой заданный момент на их основе можно генерировать мгновенные снимки. Также они по определению поддаются репликации (чтобы предотвратить потерю данных в случае, если погибнет какая-либо единица аппаратного обеспечения). Недостаток этих дисков заключается в том, что эти диски не прикрепляются непосредственно к серверу, а подключаются из какой-нибудь смежной локации (например, из того же здания, в котором расположен сервер) по сети.
Притом, что задержка при передаче данных по локальной сети низкая, но она и близко не сравнима по краткости с задержкой при соединении по PCI или SATA, когда длина кабелей составляет менее метра. Таким образом, средняя задержка при дисковых операциях (с точки зрения операционной системы) может составлять порядка пары миллисекунд. В случае, если диски подключаются напрямую, то задержка составляет около половины миллисекунды.
С локальными SSD могут возникать и другие проблемы. У этих устройств есть недостаток, также присущий и традиционным жёстким дискам: при возникновении аппаратной проблемы с таким диском или его контроллером мы сразу же теряем все данные с этого диска. Но ситуация с SSD ещё сложнее, чем с обычными жёсткими дисками, так как SSD отказывают, когда проблемы возникают на хосте. Если возникают критические проблемы с тем хостом, к которому подключены локальные SSD, то безвозвратно гибнут и сами диски, и записанные на них данные. Также теряется возможность создавать в намеченное время мгновенные снимки целого диска, а для некоторых потоков задач, применяемых в Discord (например, в некоторых случаях с резервным копированием данных), такая возможность критически важна. Именно в силу такого ограничения возможностей почти на всех серверах Discord применяются именно персистентные диски, а не локальные SSD.
Оценка проблемы
В идеальном мире мы держали бы наши базы данных на диске, в котором сочетались бы наилучшие свойства персистентных дисков и локальных SSD. К сожалению, таких дисков не существует, как минимум, в экосистеме привычных нам облачных провайдеров. При стремлении добиться столь низкой задержки, которая достигается на напрямую подключаемых дисках, приходится отказываться от уровня абстракции, благодаря которому персистентные диски приобретают присущую им изумительную гибкость.
Но что, если нам не нужна вся эта гибкость? Например, при наших рабочих нагрузках задержка при записи некритична — а вот задержка при чтении сильнее всего сказывается на производительности приложения (операций считывания у нас хватает). Возможность менять размер диска без отключений сервиса также не столь важна — мы хорошо умеем оценивать темпы роста наших хранилищ данных и заблаговременно обзаводимся всё более крупными дисками.
Тщательно продумав, какие эксплуатационные качества баз данных нам наиболее ценны, мы сузили круг требований, ограничившись следующими болевыми точками:
Оставаться на Google Cloud (т.e. активно опираться на предлагаемые дисковые возможности GCP).
Продолжать использовать для резервного копирования данных мгновенные снимки, которые делаются в заданное время.
Из всех метрик диска наивысший приоритет отдать низкой задержке при считывании.
Не жертвовать теми гарантиями доступности базы данных, что имеются в настоящий момент.
Диски различных типов, имеющиеся в GCP, по-своему удовлетворяют этим требованиям. Было бы слишком легко, если бы мы могли скомбинировать диски обоих типов в один супердиск. Поскольку основным показателем производительности диска для нас является низкая задержка при считывании, мы хотели бы считывать данные с локальных SSD-дисков, предоставляемых GCP (низкая задержка), а писать всё равно на персистентные диски (создание мгновенных снимков, обеспечение избыточности при помощи репликации). Но возможно ли создать такой супердиск не на аппаратном, а на программном уровне?
Создание супердиска
Учитывая вышеизложенные требования, нам, в сущности, требуется кэш прямой записи, в котором локальные SSD-диски из GCP послужат кэшем, а персистентные диски – уровнем хранения данных. Наши серверы баз данных работают под Ubuntu, поэтому нам посчастливилось обнаружить, что ядро Linux умеет разнообразными способами кэшировать данные на уровне диска, предоставляя такие модули как dm-cache, lvm-cache и bcache.
К сожалению, экспериментируя с кэшем, мы обнаружили парочку подводных камней. Серьёзнейший из них заключается в том, как обрабатываются отказы диска с кэшем. Если под считывание попадал неисправный сектор кэша, то вся операция считывания заканчивалась отказом. Локальные SSD-диски, тонким слоем расположенные поверх энергонезависимого SSD-оборудования, подвержены появлению плохих секторов в точности, как и любой другой физический диск. Можно исправить такие плохие сектора, затерев данные сектора в кэше информацией, взятой с уровня хранения данных. Однако, рассмотренные нами решения для кэширования диска либо не предоставляли такой возможности, либо требовали настраивать более сложную конфигурацию, чем мы готовы были рассматривать на данном этапе исследований. Если не предусмотреть кэша, который исправлял бы плохие сектора, то они могут попасться вызывающему приложению, а наши базы данных по соображениям безопасности отключаются, когда под считывание данных попадает плохой сектор:
storage_service – разрыв коммуникации из-за ошибок ввода/вывода, требует вмешательства оператора storage_service – ошибка диска: std::system_error (error system:61, данные недоступны)
Мы добавили в список наших требований «выживаемость после попадания в поврежденные сектора на локальных SSD» и исследовали совершенно новый тип системы для ядра Linux: md
md позволяет Linux создавать программные RAID-массивы, превращая множество дисков в один «массив» (виртуальный диск). Простой массив RAID1, отзеркаливаемый между локальными SSD и персистентными дисками, не решил бы нашей проблемы; в таком случае операции считывания всё равно попадали бы по персистентным дискам примерно при половине всех операций. Но md предлагает дополнительные возможности, каких нет у обычного RAID-контроллера, ориентированного «в основном на запись». В man-справке по ядру эта возможность резюмирована лучше всего:
Отдельные устройства в RAID1 можно пометить как «в основном на запись». Эти диски исключаются из балансировки обычной нагрузки на считывание; с них считывание будет происходить лишь в тех случаях, когда иного варианта нет. Это может быть полезно при работе с устройствами, подключёнными по медленному каналу.
Поскольку формулировка «устройства, подключённые по медленному каналу» идеально описывает персистентные диски, именно такая стратегия показалась нам наиболее перспективной при создании супердиска. Массив RAID1, содержащий локальный SSD и персистентный диск, предназначенный «в основном для записи», удовлетворил бы всем нашим требованиям.
Оставалось решить последнюю проблему: размер всех локальных SSD-дисков в GCP составляет ровно по 375 Гб. Для определённых приложений Discord требует хранилища данных по терабайту или более на инстанс базы данных, то есть, выделяемого по умолчанию места и близко не хватит. Можно было бы подключить к серверу множество локальных SSD, но нам требовалось как-нибудь превратить кучу мелких дисков в один более крупный.
В md предлагается ряд RAID-конфигураций, позволяющих разделить данные на несколько дисков. Простейший метод RAID0 распределяет сырые данные по всем дискам, и, если один из дисков ломается, то выходит из строя весь массив, и все данные в нём теряются. При более сложных методах (RAID5, RAID6) поддерживается чётность, что позволяет потерять как минимум один диск, отделавшись простым снижением производительности. Это отличный вариант для поддержания безотказной работы – просто извлекаем отказавший диск и заменяем его новым. Но в мире GCP не предусмотрено такой операции, как замена локального SSD, так как все эти диски запрятаны глубоко в недрах дата-центров Google. Кроме того, GCP предоставляет интересную «гарантию» на случай отказа локальных SSD: если какой-нибудь локальный SSD откажет, то весь сервер мигрирует на новый комплект аппаратного обеспечения, и на исходном SSD все данные этого сервера, фактически, стираются. Поскольку мы не занимаемся заменой локальных SSD (да и не можем этого сделать), то, чтобы снизить влияние отрыва локальных RAID-массивов на общую производительность, мы остановились на RAID0: превращать множество локальных SSD в один виртуальный диск, характеризующийся низкой задержкой.
Когда RAID0 реализован на основе локальных SSD, а RAID1 находится между персистентным диском и массивом RAID0, можно сконфигурировать базу данных с дисковым приводом так, чтобы обеспечить считывание с низкой задержкой, но в то же время располагать и всеми достоинствами персистентных дисков.
Производительность базы данных
При тестировании данная новая конфигурация диска показала себя хорошо, но как она сработает, когда поверх неё будет находиться реальная база данных?
На практике наши ожидания полностью оправдались: при пиковых нагрузках уже не возникали очереди дисковых операций, связанных с запросами к базе данных, изменений в задержке при запросах также не наблюдалось. На уровне метрик это выражалось в уменьшении количества ожидающих операций считывания на супердиске в сравнении с персистентным диском, так как меньше времени тратилось на операции ввода/вывода.
Такой рост производительности помог нам обслуживать больше запросов при помощи имеющихся серверов, что не может не радовать наш отдел по поддержке серверов баз данных (а также бухгалтерию).
Заключение
В ретроспективе признаём, что уже на самом раннем этапе развёртывания базы данных задержка при дисковых операциях — это повод для озабоченности. В мире облачных вычислений очень много систем, от которых совершенно непонятно, чего ждать (в сравнении с аналогичными устройствами из дата-центров). Исследования и тесты, которые привели нас к разработке нашего решения с супердиском, позволили нам выявить множество полезных метрик производительности, за которыми нужно следить. Команда многое узнала о внутреннем устройстве дисковых устройств (как в Linux, так и в GCP), а также мы развили в компании культуру тестирования и валидации архитектурных изменений. Когда супердиски были выведены в продакшен, масштабирование наших баз данных продолжало поспевать за ростом пользовательской аудитории Discord.
Все, кто уже имеет опыт работы с RAID, может с недоверием отнестись к тезису, что такая конфигурация «просто работает»: ведь в облачной среде много систем, порой отказывающих самым нетривиальным и неожиданным образом. Конечно, поддержка такой дисковой конфигурации не сводится к тому, чтобы просто правильно настроить md. Возможно, у этого поста будет и вторая часть, в которой мы рассмотрим конкретные пограничные случаи, возникшие в облачной среде, и расскажем, как мы с ними справились.
Комментарии (8)
pharrell
26.09.2022 18:35+3>> Не секрет, что именно в Discord сейчас принято вести беседы
...среди команды разработки Discord. А нормальные люди пользуются чем-то более удобным.
QuAzI
>> именно в Discord сейчас принято вести беседы
Если хочется половину беседы решать проблемы дискорда, ага. И некоторым настолько лень попробовать что-то другое, что они готовы эту дичь терпеть...
Особенно классно, когда он рвёт звонок и показывает абсолютно чистую морду, без единой кнопки или менюшки, до его полного перезапуска. А при перезапуске начинается: баннеры, обновления, обновлённые баннеры, реклама свистоперделок до которых никому никогда не будет дела и которую невозможно отключить, как и автообновления этой багодельни...
Nikita22007
1) это какой то оффтопик. Статья не о «Мы такие классные, идите сюда», статься о «вот как мы решили такую то проблему». Если вам не нравится софт — не используйте
2) За всё время использования такая проблема была только на очень ужасном интернет соединении (обвалы серверов не в счёт). Вы в деревне живёте? Или у вас модемная связь?
QuAzI
Хотите не оффтопик? Не нужно врать, что "именно в Discord принято вести беседы".
Хотите не оффтопик? Не нужно врать, что там никаких проблем нет. Я далеко не один в команде, кто их наблюдает на постоянной основе, при Ethernet-подключении и на нормальном железе. Почему-то у Skype/MS Teams/Telegram/NextCloud Talks/Jitsi/Google Meet настолько отровенного глючелова и такого заградительного количества баннеров нет. Я вообще не припомню ни один IM который бы настолько упоролся по всплывашкам вместо того чтобы просто надёжно выполнять свою основную функцию.
Всем бы такого диалапа
А скачивание всех присланных в IM аттачей через браузер - это вообще какая-то лютая наркомания за которую отдельно нужно бить палкой в 2022-ом.