MySQL – это лучшая NoSQL-система
При рассмотрении сценариев использования NoSQL, таких как хранение пар ключ-значение, оказывается, что MySQL более предпочтительна с точки зрения производительности, легкости использования и стабильности. MySQL — это основательная система с обилием онлайн-материалов, которые охватывают все темы от основных операций и разбора ошибок до репликации и различных паттернов использования. Это дает MySQL преимущество перед более молодыми NoSQL-системами, у которых нет такого опыта.
За последние годы NoSQL-системы стали господствующим направлением. Многие разработчики видят в NoSQL-системах, таких как MongoDB, Cassandra, Redis или Hadoop, оптимальный вариант для построения своих приложений, считая их единой семьей продуктов, которая обесценивает старые SQL-системы.
Зачастую, в основе решения об использовании базы данных NoSQL лежит рекламная шумиха или ошибочное убеждение, что реляционные базы данных не могут обеспечить такую же производительность, как базы данных NoSQL. Когда доходит до выбора базы данных, инженеры часто упускают из виду эксплуатационные расходы, а также соображения стабильности и зрелости технологии. Чтобы узнать больше об ограничениях и изъянах различных NoSQL (а также SQL) систем, обратите внимание на серию статей проекта Jepsen, опубликованную на Aphyr.com.
В этой статье мы объясним, почему, по нашему мнению, использовать MySQL для хранения пар ключ-значение лучше, чем большинство специализированных NoSQL-систем, а также предоставим инструкции для использования MySQL.
Определение сайтов Wix
Когда кто-нибудь нажимает ссылку на сайте Wix, его браузер посылает на сервер Wix HTTP-запрос с адресом сайта. Так происходит и с премиум-сайтом Wix, имеющим собственный домен (напр. domain.com), и с бесплатным сайтом на субдомене Wix (напр. user.wix.com/site). Сервер должен распознать запрашиваемый сайт по адресу сайта, выполняя поиск типа ключ-значение для пары URL-сайт. В дальнейшем обсуждении мы будем обозначать URL как «путь» (route).
Таблица routes используется, чтобы преобразовать адрес сайта в объект site. Поскольку для попадания на сайт могут использоваться различные пути, здесь имеет место отношение «многие к одному». Когда сайт найден, приложение загружает его, чтобы работать с ним. Объект site, в свою очередь, имеет сложную структуру, которая включает два списка дочерних объектов — различные сервисы, используемые сайтом. Ниже приведена примерная модель наших объектов. Здесь мы предполагаем, что используется стандартная SQL база данных с нормализованной структурой:
При обновлении сайта с традиционной нормализованной моделью нам необходимо использовать транзакцию для обновления множественных таблиц, чтобы обеспечить целостность данных (обратите внимание, что транзакция использует блокировку на уровне базы данных, что предотвращает одновременную запись, а иногда и чтение из задействованных таблиц). Продолжая работать с такой моделью, мы, вероятно, дойдем до ключа типа SERIAL в каждой таблице, внешних ключей и индекса в поле URL таблицы routes.
Однако, моделирование данных на основе нормализованной схемы чревато рядом осложнений:
• блокировки ограничивают доступ к таблице, поэтому при больших объемах данных это может ограничить нашу производительность;
• чтение объекта требует либо нескольких SQL запросов (4 в нашем случае) либо использования JOIN — и это тоже влияет на время задержки;
• ключи с атрибутом SERIAL требуют блокировки, что снова ограничивает производительность записи.
Эти проблемы ограничивают пропускную способность и распараллеливание запросов, которые нам способна обеспечить MySQL (или любая другая SQL-система). Из-за этих слабых мест, а также в силу того факта, что речь идет по сути о паре ключ-значение, многие разработчики предпочитают искать NoSQL-решение, которое обеспечивает лучшую производительность и распараллеливание, даже ценой стабильности, целостности и доступности.
Мы в Wix обнаружили, что MySQL, если ее творчески использовать в качестве хранилища пар ключ-значение, может работать лучше, чем MySQL с нормализованной моделью данных (приведенной выше), а также лучше, чем большинство NoSQL-систем. Наша нынешняя система обеспечивает такие параметры масштабирования, пропускной способности, распараллеливания запросов, времени задержки, которые сделали бы честь любой NoSQL-системе. Вот некоторые данные нашей системы:
• активная установка на три дата-центра (active-active-active);
• пропускная способность порядка 200 000 RPM;
• таблица routes имеет объем порядка 100 000 000 записей, 10 ГБ дискового пространства;
• таблица sites имеет порядка 100 000 000 записей, 200 ГБ дискового пространства;
• средняя задержка чтения — 1,0–1,5 миллисекунды (фактически, 0,2–0,3 мс. в одном дата-центре).
Заметьте, задержка порядка 1,0 мс. считается впечатляющим показателем в большинстве систем, построенных на паре ключ-значение, включая как облачные системы, так и системы с открытым кодом. И мы достигли этого, используя MySQL (простейшую, как принято считать, SQL-систему).
Вот схема, которую мы используем:
CREATE TABLE `routes` (
`route` varchar(255) NOT NULL,
`site_id` varchar(50) NOT NULL,
`last_update_date` bigint NOT NULL,
PRIMARY KEY (`key`),
KEY (`site_id`)
)
CREATE TABLE `sites` (
`site_id` varchar(50) NOT NULL,
`owner_id` varchar(50) NOT NULL,
`schema_version` varchar(10) NOT NULL DEFAULT '1.0',
`site_data` text NOT NULL,
`last_update_date` bigint NOT NULL,
PRIMARY KEY (`site_id`)
) /*ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=16*/;
Все поля, которые не используются в качестве условий запроса, были свернуты в единое поле типа blob (текстовое поле site_data). Оно включает таблицы sub-obj, а также любые поля самого объекта table. Заметьте, мы не используем ключей типа SERIAL, вместо этого мы используем поле типа VARCHAR (50), которое содержит созданные пользователем переменные GUID (об этом подробней в следующем разделе).
Ниже приведен используемый нами запрос, у него высокая пропускная способность и низкое время задержки:
select * from sites where site_id = (
select site_id from routes where route = ?
)
Здесь сначала выполняется запрос к таблице routes по уникальному индексу, запрос должен вернуть только одно значение. Затем мы просматриваем сайты по первичному ключу, снова ищем одно значение. Вложенный синтаксис позволяет обработать оба SQL-запроса за одно обращение к базе данных.
Результат, показанный выше, требует примерно 1 мс. последовательной работы в условиях высокого трафика и высокой частоты обновлений. Обновления полутранзакционные, даже без использования транзакций. Это становится возможным благодаря тому, что мы вводим весь сайт одной командой INSERT и, пока мы не введем данные в routes, запросы не будут его обнаруживать. То есть, когда мы вводим сначала данные о сайте, а затем о путях, мы уверены в целостности своих данных, даже в пограничной ситуации, пока наши данные в таблице sites не связаны.
Инструкции по использованию MySQL в качестве NoSQL-системы
Используя опыт, приобретенный в описанном выше примере (и других подобных случаях из практики Wix), мы разработали короткий список рекомендаций для использования MySQL в качестве NoSQL системы.
Главное, что следует помнить при использовании MySQL в качестве NoSQL-системы — это избегать блокировок на уровне базы и сложных запросов.
• Не используйте транзакции, которые предполагают блокировки. Вместо этого, используйте транзакции в приложении;
• не используйте ключи типа SERIAL. Такие ключи влекут за собой блокировки и усложняют конфигурацию вида active-active;
• используйте уникальные ключи, созданные клиентами. Мы используем GUID.
Оптимизируя структуру для чтения, обратите внимание еще на несколько дополнительных рекомендаций:
• не проводите нормализацию;
• если есть поле — оно должно индексироваться. Если поле для индекса не нужно, сохраните его в одном поле типа BLOB / TEXT (как JSON или XML);
• не используйте внешние ключи;
• проектируйте вашу структуру так, чтобы по запросу можно было прочитать отдельный ряд;
• не используйте оператор ALTER TABLE. Эти команды влекут за собой блокировки и периоды временной неработоспособности. Вместо этого, используйте перенос данных Live Migration.
При запросе данных:
• ищите записи по первичному ключу или по индексу;
• не пользуйтесь JOIN;
• не пользуйтесь функциями агрегирования;
• запускайте функции проверки (BI, исследование данных) на репликах, а не на головной базе.
Мы планируем написать еще одну статью, где расскажем подробней о переносе данных Live Migrations и транзакциях через приложение.
Вы можете думать по-новому
Пожалуй, это важнейший вывод из этой статьи. Это здорово — использовать MySQL в качестве NoSQL-системы, то есть не так, как она была спроектирована. Как показано в этой статье, примером служит использование MySQL для работы с парами ключ-значение вместо специально спроектированных для этого NoSQL-систем. Мы в Wix выбрали MySQL для работы с парами ключ-значение (и не только), поскольку ее легко использовать, ей легко управлять, и это отличная экосистема. В качестве бонуса, она обеспечивает показатели задержки, пропускной способности и распараллеливания, едва ли не превосходящие большинство NoSQL-систем.
Главный архитектор программного обеспечения конструктора для создания сайтов Wix,
Йоав Абрахами
Оригинал статьи: блог инженеров компании Wix