Зоджи-Ла, Индия
Привет, Хабр! Меня зовут Олег Ефимов, я работаю в Badoo в команде «Платформа», занимаюсь задачами хранения фотографий, интерфейсами сервисов и много чем ещё.
Мне часто приходится слышать, что в том, что касается серверных технологий, Badoo – довольно консервативная компания. Отчасти это так, но на самом деле мы используем много молодых языков программирования, новых инструментов и технологий. Одна из них – RocksDB, на основе которой Facebook создал MySQL storage engine – MyRocks. Пост о том, как Facebook осуществлял миграцию одной из своих баз данных с InnoDB на MyRocks, мне и захотелось для вас перевести.
В прошлом году мы представили MyRocks, наш новый движок для баз данных MySQL, который задумывался, чтобы сделать запись и использование дисков более эффективными, чем у compressed InnoDB. Мы решили мигрировать одну из наших основных баз (UDB) с compressed InnoDB на MyRocks и вдвое уменьшить размер хранилища и количество используемых серверов. Была тщательно спланирована и реализована миграция базы данных UDB, который управляет данными о социальном графе Facebook. Работы мы завершили в прошлом месяце и успешно «ополовинили» хранилище. В этом посте мы расскажем о подготовке и осуществлении миграции, а также об извлечённых уроках.
UDB была ограничена объёмом хранилища
Несколько лет назад мы заменили оборудование, на котором крутится UDB, с Flashcache на чистый Flash, чтобы решить некоторые проблемы с производительностью. Связка MySQL с InnoDB работала быстро и надёжно, но мы хотели использовать меньше серверов для обработки тех же нагрузки и объёма данных. При использовании чистого flash-хранилища UDB была ограничена его объёмом. И хотя мы использовали в InnoDB сжатие, имея при этом избыток ресурсов процессора и пропускной способности случайных операций ввода-вывода, вряд ли мы смогли бы заставить InnoDB использовать меньше места на дисках.
InnoDB была ограничена объёмом свободного месте на дисках, а ресурсы CPU/ IO простаивали. Это частая ситуация при использовании flash-хранилищ.
Это было одним из мотивов создания MyRocks – RocksDB storage engine для MySQL. Мы реализовали несколько возможностей, которые есть в InnoDB, где размер базы данных был гораздо больше оперативной памяти:
- кластеризованный индекс;
- транзакции (атомарность, блокировки строк, MVCC, Repeatable Read/ Read Committed, 2PC между binlog и RocksDB);
- устойчивый к поломкам slave и master;
- SingleDelete (эффективное удаление вторичных индексов);
- быстрая загрузка данных, быстрое создание индексов и быстрое удаление таблиц.
Наши ранние эксперименты показали, что MyRocks использует вдвое меньший объём по сравнению с compressed InnoDB, причём без существенного увеличения потребления ресурсов процессора и количества операций ввода-вывода. Поэтому мы решили полностью мигрировать UDB с InnoDB на MyRocks.
Нагрузка на MyRocks с удвоенной плотностью размещений. Всё ещё есть резерв по ресурсам процессора и операциям ввода-вывода.
Упрощение миграции с InnoDB на MyRocks
Для сервисов, в которых пользователи обращаются непосредственно к базам данных, подобная миграция может быть трудной задачей. В целом, для действующей OLTP-базы это можно сделать без остановки сервисов, без увеличения задержки и деградации пропускной способности, без возврата ошибочных данных и без влияния на рабочую нагрузку.
У MyRocks есть уникальное преимущество перед другими базами данных: это MySQL storage engine, что сильно облегчает миграцию с InnoDB. В частности:
- не пришлось вносить изменения в приложение, потому что протоколы клиента MySQL и определения схем используются такие же (на самом деле, есть небольшие отличия, вроде gap-блокировок, но их было относительно легко учесть);
- наши инструменты администрирования MySQL поддерживают и InnoDB, и MyRocks;
- MyRocks может реплицироваться из InnoDB: мы можем добавлять экземпляр MyRocks, запуская его в качестве одного из slave’ов, доступного только для чтения;
- InnoDB также может реплицироваться из MyRocks: мы можем развернуть MyRocks на master, не отказываясь от InnoDB, это позволяет с помощью переключения master откатываться с MyRocks на InnoDB при возникновении проблем;
- бинарные логи с глобальным идентификатором транзакций (Global Transaction Identifier, GTID) позволяют проверять согласованность данных между экземплярами InnoDB и MyRocks без длительной остановки репликации (менее одной секунды).
Проверка согласованности данных
MyRocks/ RocksDB – более молодая технология, поэтому нам пришлось прибегнуть к всесторонней проверке согласованности, чтобы предотвратить возникновение новых багов в нашей базе данных. Проверка ведётся постоянно, как во время миграции, так и после запуска в production.
Проверка согласованности первичного и вторичного ключа. В каждой таблице сравниваются количества строк и контрольные суммы колонок первичных и вторичных ключей. Например, в таблице Т1 есть колонка (id, value1, value2) и вторичный индекс (value1); тогда для value1 сканируются первичный и вторичный ключи, затем сравниваются количества строк и контрольные суммы. Если один из ключей повреждён, контрольные суммы не сойдутся.
Проверка согласованности двух экземпляров. В одном наборе реплик (пара master–slave) два разных экземпляра (MyRocks и InnoDB) сравниваются по количеству строк и контрольной сумме первичного ключа. Благодаря GTID можно начинать транзакции с согласованными снапшотами с одинаковым у обоих экземпляров GTID. Последующие выражения SELECT (возвращающие количества строк и контрольные суммы) у обоих экземпляров должны быть согласованными.
Теневая проверка корректности запросов на двух экземплярах. В MySQL есть фича под названием Audit Plugin, позволяющая регистрировать выполняемые запросы. Мы создали инструмент для теневого повтора этих запросов в нескольких экземплярах и сравнения результатов. Это позволило нам убедиться, что запросы в InnoDB и MyRocks дают одинаковые результаты.
Этапы миграции
Миграция проходила путем, описанным ниже, и нам заметно облегчила работу возможность настроить репликацию между InnoDB и MyRocks.
1) Развернули первый slave MyRocks. У нас был один master и четыре slave’а для каждого набора реплик в регионах по всему миру. В каждом наборе реплик мы создали первый инстанс MyRocks с помощью дампа (mysqldump) из InnoDB в том же регионе. Во время выгрузки и загрузки мы остановили реплицирование в исходных slave-экземплярах InnoDB, чтобы ускорить и упростить миграцию. Также во время выгрузки и загрузки все запросы на чтение перенаправлялись на другие доступные slave-экземпляры InnoDB.
Поскольку MyRocks был оптимизирован для загрузки данных и создания вторичного индекса, выгрузка и загрузка не заняли много времени (движок может копировать из InnoDB сотни гигабайтов в час). После того как репликация догонялась и выполнялась проверка согласованности данных, мы удаляли InnoDB в том же регионе.
2) Развернули второй slave MyRocks. Мы хотели избежать манипуляций с единственным экземпляром MyRocks, поскольку его потеря привела бы к необходимости заново проводить выгрузку и загрузку. При работе с двумя экземплярами после сбоя одного из них можно было бы восстановиться с помощью бинарной онлайн-копии MyRocks (myrocks_hotbackup), что было бы гораздо легче по сравнению с выгрузкой и загрузкой. Перед развёртыванием MyRocks у нас было пять экземпляров InnoDB на каждый набор реплик.
В каждом регионе после удаления двух экземпляров InnoDB мы добавили два экземпляра MyRocks. В результате получилось три экземпляра InnoDB и два – MyRocks на каждый набор реплик. Поскольку MyRocks загружал данные быстрее, то заливка в него проходила куда проще, чем копирование в InnoDB. Это позволило нам мигрировать без увеличения количества физических серверов даже в ходе самой миграции.
3) Сделали slave MyRocks master’ом. Разворачивать master было гораздо сложнее, чем slave, потому что экземпляр master может обслуживать запросы как на запись, так и на чтение. Например, на master одновременно выполняются операции записи в одних и тех же строках, так что нужно реализовать правильную обработку блокировок строк. Плохая реализация согласованности может привести к большому количеству ошибок.
4) Во всех регионах скопировали MyRocks и удалили все экземпляры InnoDB. Последним шагом было копирование MyRocks и удаление InnoDB. Теперь наборы реплик полностью мигрировали с InnoDB на MyRocks.
В production мы сначала выполнили первые два этапа для всех наборов реплик UDB. Затем для большинства наборов выполнили третий этап, и, наконец, последний. Поскольку самым трудным было развёртывание master’ов, мы провели много тестов, прежде чем двигаться дальше. C помощью нашего инструмента дублирования запросов мы проверили и трафик master’ов, чтобы можно было исправить любые баги согласованности перед развёртыванием master’ов в production.
Другие технические моменты
В InnoDB мы использовали direct I/O, но в MyRocks/RocksDB его поддержка ограничена, так что мы использовали буферизованные операции ввода-вывода. В более старых ядрах Linux есть известная проблема, когда при активном использовании буферизованных операций начинается подкачка с диска и возникают сложности при выделении виртуальной памяти. Наша команда, отвечающая за ядро Linux, пофиксила проблему с выделением виртуальной памяти в Linux 4.6, и перед миграцией мы обновили ядро до этой версии.
Также мы запускали на каждой машине несколько экземпляров MySQL. Размер каждого экземпляра был гораздо меньше ёмкости 5TB flash-хранилища, что помогло улучшить осуществление операций с базой данных: резервное копирование, восстановление, создание реплик и предотвращение лагов репликации. Во время миграции мы постепенно создавали экземпляры MyRocks вслед за удалением экземпляров InnoDB. Во flash-хранилищах активное удаление файлов приводит к длительным TRIM-задержкам продолжительностью до десятков секунд. Мы знали об этом ограничении и создали простой инструмент для медленного удаления файлов маленькими кусками. Вместо удаления 100-гигабайтных файлов InnoDB по команде "rm" он делил большие файлы на куски объёмом по ~64 Мб, а затем каждый из них удалял в течение короткого периода ожидания. Мы замедлили скорость удаления примерно до 128 Мб в секунду.
Извлечённые уроки
Для наших операций важна эффективность, и мы были счастливы, когда удалось быстро мигрировать, не создав проблем в production. Но в процессе мы извлекли несколько важных уроков.
- Одним из главных критериев при выборе оборудования является прогнозируемая продолжительность миграции. Например, при пяти годах на миграцию вместо одного года экономия на хранилище может уменьшиться, потому что вам придётся продолжать покупать новое оборудование, пока не закончится миграция. Также процесс облегчают автоматизация и упрощение тестов, например:
- отправка в production-среде теневого трафика на чтение/запись и регрессии мониторинга (особенно количество ошибок и зависаний а также уровень задержки);
- проверка согласованности данных в InnoDB и MyRocks;
- намеренное обрушение slave- и master-экземпляров MyRocks и проверка их способности восстановиться.
- Нужно понимать, как используется целевой сервис. Нам было важно решить, какие фичи нужно в первую очередь добавить в MyRocks. Для облегчения миграции мы добавили несколько важных возможностей вроде определения запросов с помощью gap-блокировок, пакетной загрузки (bulk loading), уровней изоляции Read Committed. Правильная расстановка приоритетов упрощает и ускоряет миграцию.
- Начните с малого. В идеале новое программное обеспечение сначала нужно разворачивать на очень маленьком, доступном только для чтения экземпляре, который при необходимости можно откатить. Инженерам, ответственным за бесперебойную работу сайта, не стоит переключать весь сервис через конфигурационный файл и надеяться, что всё будет работать.
- MyRocks – это open-source-инструмент, позволяющий работать с другими продуктами для создания новых возможностей, а также поиска и исправления багов.
Планы на будущее
Миграция нашей основной базы данных с InnoDB на MyRocks позволила вдвое уменьшить используемый объём хранилища. Мы продолжаем совершенствовать MyRocks и RocksDB, в том числе агрессивнее консолидируя экземпляры для освобождения серверов под другие задачи. Мы также работаем над поддержкой разных движков. MyRocks оптимизирован для экономии объёма хранилища и замедления роста объёма записываемых данных, а InnoDB оптимизирован для чтения и имеет более узкоспециализированные возможности вроде gap-блокировки, внешних ключей, полнотекстовых и пространственных индексов.
Наша текущая разработка MyRocks не подразумевает внедрения подобных вещей, но мы планируем обеспечить надёжную поддержку нескольких движков. Использование в одном экземпляре одновременно InnoDB и MyRocks позволит применять InnoDB для маленьких, активно читаемых страниц, а MyRocks – для всего остального (мы часто слышим просьбы о реализации этой возможности). Наконец, мы будем работать над поддержкой MyRocks грядущего MySQL 8.0.
Комментарии (8)
rotor
16.11.2017 23:05RocksDB использую уже давно. Я как-то писал о своей серверной обёртке над RocksDB, позволяющей использовать её на бэкенде как NoSQL решение. С тех пор у меня появились рабочие проекты на ней.
Работает очень стабильно. Ни разу не было проблем. Даёт потрясающую производительность.
Давно наблюдаю за развитием MyRocks. К сожалению, в последнее время не использую MySQL. Так что, MyRocks попробовать не довелось. Но, зная качество фэйсбучных продуктов, не сомневаюсь что проблем с ней не будет.
crocodile2u
Sannis, есть у вас нормальный докер с MyRocks? Я уже свой забацал (https://hub.docker.com/r/crocodile2u/mariadb-rocks/), но хотелось бы вариант от тех, кто хорошо шарит в докерах. Кроме того, еще вопрос: для InnoDB есть Handlersocket, который в Баду по крайней мере раньше использовался. Есть что-то похожее для MyRocks? Это было бы естественно, учитывая, что RocksDB это key-value storage.
Sannis Автор
crocodile2u, мы не запускаем MySQL в Docker, придерживаемся пока что старой практике по одному MySQL сервису на сервер. Единственно, что контейнеризировано — это pinba, но там нет необходимости хранить данные.
С HandlerSocket ситуация сейчас довольно интересная: в MySQL 5.7 он не поддерживается, ванильный Memcache Protocol не поддерживает всего что умел HandlerSocket (например multi-get), а на подходе уже возможность использовать с клиентов X Protocol. Так что пока что держим HandlerSocket на MySQL 5.6.
Насколько я знаю, для MyRocks легковесного способа читать делать не планируется, думаю в том числе из-за того что этот движок больше оптимизирован под запись и там где его будут использовать нет необходимости выжимать максимум из чтения.
Merkat0r
БД в докере это не самое умное решение. Не стоит все подряд совать в контейнеры ради моды.
crocodile2u
Очень удобно на поиграться и/или в качестве решения для локального (development) окружения.
bearad
А аргументы почему «не самое умное решение»? Плотность контейнеров значительно выше (на том же железе) гипервизоров, из чего следует что их оверхед меньше. Или ваши доводы не в производительности?
larrabee
В одном из последних выпусков Percona server 5.7 добавили экспериментальную поддержку MyRocks, ставится из репов в 1 клик. Пока тестируем этот движок, полет нормальный.
Sannis Автор
Мы, кстати, именно эту сборку и используем.