Масштабирование MySQL

Основные понятия

Для начала нужно определиться с терминами:

  1. Производительность (performance) – способность приложения отвечать таким требованиям, как время реакции, пропускная способность.
  2. Пропускная способность (capacity) – нагрузка, число пользовательских сессий, с которыми может справиться приложение.
  3. Масштабируемость (scalability) – способность, потенциал приложения поддерживать еще большую производительность, способность повысить пропускную способность без снижения производительности.
  4. Доступность (availability) – временной процент, когда приложение способно отвечать на запросы. Например, когда говорят, что приложение доступно на 99.999% в году, это значит, что за весь год оно стоит всего пять минут.
  5. Отказоустойчивость (fault tolerance) – способность приложения управлять падениями. Рано или поздно любое приложение падает. Когда это происходит, приложение должно продолжать вести себя так, как будто ничего не происходит.

Масштабирование MySQL

Когда вы покупаете более мощный сервер с более дорогим железом, вы масштабируете вверх – scaling up. Другой вариант масштабирования – по горизонтали – scaling out – когда база разносится по нескольким серверам. Третий вариант – данные могут быть актуальными и не очень, часть их может быть заархивирована – scaling back. Четвертый вариант – доступ к удаленным данным – federation (в MySQL такой вариант ограничен).

Идеальный вариант для масштабирования – единая логически организованная база данных, которая может содержать столько данных и обслуживать столько запросов, сколько нужно. На ум сразу приходит понятие кластер, распределенная сеть, которая скрывает истинную природу хранилища на уровне приложения. В MySQL есть стандартная технология, называется она NDB Cluster. Но сразу оговоримся – на данный момент она имеет свои ограничения.

Если ваше приложение масштабируемое, вы просто добавляете новый сервер, и производительность автоматически улучшается. Если нет, вы пытаетесь оптимизировать железо либо sql-запросы. На улучшение производительности напрямую влияет корректная индексация таблиц или использование другого движка (storage engine). Но у любой sql-оптимизации рано или поздно наступает предел. Тогда вы начинаете делать апгрейд железа.

Вертикальное масштабирование (scaling up)

Этот вариант работает до определенного уровня. Во-первых – деньги. Scaling up – это, сразу скажем, бездарное финансовое решение. Есть определенный слой высокопроизводительного железа, которое к тому же проприетарное и узкоспециализированное, со своими ограничениями. MySQL архитектурно устроена таким образом, что в ней нет безграничных возможностей для вертикального масштабирования, в этом случае многоядерность хороша до определенного момента. Пределом являются 8 CPU и 14 дисков – превышение этих параметров уже не даст эффекта. Способность мастер-сервера (master server) к эффективному использованию многоядерности не переносится автоматически на слэйв-сервер (slave server), у последнего ограниченные возможности в этом плане.

В общем, масштабирование вверх – это не лучшее решение.

Горизонтальное масштабирование (scaling out)

Есть несколько вариантов для горизонтального масштабирования.

  1. При масштабировании по горизонтали вы "размазываете" данные по нескольким серверам с помощью репликации, и далее используете slave-серверы на чтение. При этом происходит разделение (partition) данных по нескольким нодам. Нода – функциональный блок в MySQL, это может быть отдельный сервер. Ноды бывают четырех типов:
    • активный master-сервер и пассивный репликационный slave-сервер;
    • master-сервер и несколько slave-серверов;
    • активный сервер со специальным механизмом репликации – distributed replicated block device (DRBD);
    • SAN-кластер.
    В большинстве случаев все серверы в ноде имеют одинаковые данные.
  2. Функциональное разделение: данные разбиваются на таблицы так, что они никогда не джойнятся между собой, например в случае портала – это данные для форума, новостей и поддержки, которые разносятся по разным серверам.
  3. Data sharding – механическое разделение огромных объемов однотипных данных на несколько частей (shard). Рассмотрим пример: есть блог, состоящий из трех типов обьектов – пользователи, посты, комментарии. На первом этапе мы выполняем функциональное масштабирование: разносим обьекты по трем разным нодам. При существенном увеличении объема данных внутри каждой из этих трех нод мы выполняем data sharding и разносим каждую из трех нод на отдельные подноды, используя в качестве критерия ID-пользователя. С точки зрения реализации это наиболее трудоемкий вариант. Но для неограниченного повышения пропускной способности (capacity) это наиболее подходящий вариант.

Данный пример с блогами показателен в том плане, что сама модель данных позволяет нам в качестве критерия выбрать всего один ключ – ID-пользователя, и в этом случае мы счастливым образом избегаем дублирования данных. Но так бывает не всегда. Сложная модель имеет несколько ключей для разделения данных, и в таких случаях дублирования данных не избежать.

В качестве примера с дублированием можно привести следующую модель. Имеется сайт для обсуждения книг. Нам понадобятся две основных выборки: все комментарии для данной книги и все комментарии для данного пользователя. Функционально мы разносим пользователей и книги отдельно. Комментарии мы не будем выносить в третий функциональный блок, а поместим его в блок пользователя, при этом в комментариях имеются user ID и post ID. И далее дублируем ID-шники комментариев во второй – книжный – функциональный блок. Далее делаем data sharing внутри каждой ноды.

После того как данные разбиты на порции (shard), возникнет следующая проблема: реализовать запрос, для которого потребуются данные из разных порций (shard). Например, в случае с комментарием для книг может понадобиться выборка по наиболее активным пользователям – так называемые cross-shard запросы. В данном случае вступает в силу распараллеливание запросов на уровне приложения. Можно построить специальное отдельное хранилище, которое будет хранить готовые сложные выборки и периодически обновлять их в фоновом режиме. Все опять упирается в трудозатраты.

Чем хорош шардинг с точки зрения производительности? Представьте себе таблицу на 100 ГБ, которую вы разбиваете на 100 частей по одному гигабайту и разносите на 100 нод. В один прекрасный момент возникнет необходимость в создании индекса. Для одногигабайтовой ноды такой индекс может уместиться в память и работать очень быстро. Или вам понадобилось выполнить команду ALTER TABLE: для стогигабайтовой таблицы она будет вести себя совершенно иначе, чем для одногигабайтовой.

Шардинг хорош для перемещения данных с одной ноды на другую: нода устанавливается в режим read-only, и данные перемещаются – обычно это делается с помощью mysqldump. При этом происходит частичная блокировка нод. Здесь речь идет о полном переносе всего шарда, а не частичном переносе данных из одного шарда в другой, частичный перенос вообще не рекомендуется.

С точки зрения пространства имен шардинг можно делать по-разному:

  1. каждый шард – это отдельная база данных, т.е. доступ к одной таблице, раскиданной по нодам, будет таким: shard_database_1.my_table, shard_database_2.my_table;
  2. таблица разносится по разным нодам внутри одной базы данных: database_name.my_table_1, database_name.my_table_2;
  3. комбинированный метод: shard_database_1.my_table_1,shard_database_1.my_table_2.

На уровне приложения динамическое формирование селектов может выглядеть так:

$sql = "SELECT book_id, book_title FROM shard_database_%d.my_table_%d... ";

$res = mysql_query(sprintf($sql, $shardno, $tableno), $conn);

Фиксированный и динамический sharding

Есть два основных подхода, по которым происходит разделение данных между нодами – фиксированный и динамический. Первый вариант зависит только от партиционного ключа. В качестве примера можно привести диапазон ID-шников от 1 до 1000000, разбитый на 10 частей, при этом ID=543210 попадает в шестую ноду. У фиксированного подхода есть несколько недостатков:

  1. при возрастании объема данных усложняется балансировка нагрузки между нодами;
  2. фиксированный диапазон может оказаться мал, его придется увеличивать, что приведет к перемещению данных внутри нод.

Альтернативой для первого подхода является динамическое разделение данных. Пример: создается таблица с двумя полями – ID пользователя и ID ноды:

CREATE TABLE user_to_shard (

user_id INT NOT NULL,

shard_id INT NOT NULL,

PRIMARY KEY (user_id)

);

По ID-шнику пользователя всегда можно найти его ноду, в противном случае добавляется новая нода. Позже ноду можно изменить и перенести данные. Правда, за все нужно расплачиваться: цена этому – дополнительный ресурс в виде directory server – специальной сканирующей ноды. Конечно, такую таблицу можно закешировать. Динамический подход дает возможность гибко управлять данными, перераспределять их между нодами. Логику управления данными можно будет разбивать на несколько слоев. Динамический подход хорош там, где диапазон сканирования не очень велик. На практике бывает так, что приходится смешивать фиксированный и динамический подходы.

Sharding-структуру не обязательно создавать с нуля, уже есть несколько проектов, в которых это реализовано.

http://shards.hibernate.org – реализация расширения на java для open-source библиотеки Hibernate object-relational mapping (ORM), используется в Google. Этот интерфейс позволяет прозрачно для пользователя хранить и получать данные.

http://www.hivedb.org – open-source фреймворк HiveDB с использованием MySQL. Написан на java и разработан для создания, использования и управления распределенных хранилищ данных.

Кластеризация

Кластер – это набор хостов в локальной сети, который выступает как единый сервер. В качестве варианта используется так называемый federation – виртуальный сервер, выступающий фактически в качестве прокси для других серверов. В MySQL он имеет ограниченные возможности.

NDB Cluster – движок на основе синхронных репликаций и автоматического разделения данных по нодам. Он ведет себя адекватно для небольших наборов данных и простых запросов.

Заключение

Пора подвести итог: за кадром остались другие аспекты – Load Balancing, failover/failback и т.д. Масштабирование в MySQL – это нетривиальный процесс, в котором нет решений на все случаи жизни. Решения, лежащие на поверхности, не всегда приводят к максимальному результату – это касается вертикального масштабирования. Горизонтальное масштабирование предъявляет высокие требования к знанию не только архитектуры MySQL, но и архитектуры на уровне приложений, что приводит к большим трудозатратам. Готовое кластерное решение – NDB Cluster – на данный момент находится на стадии эволюции. Если вы решитесь заняться масштабированием MySQL, вы должны отдавать себе отчет в том, что готовых решений на все случаи жизни нет.


Вы можете поделиться этой статьей в любой из соцсетей, представленных ниже:


Чтобы добавить свой комментарий, необходимо пройти аутентификацию
Комментарии
Ничего не найдено.