Подписка

Стратегии масштабирования MySQL

Продолжая тему распределения нагрузки, рассмотрим стратегии масштабирования базы данных, а именно партиционирование, шардинг и репликацию. Как всегда с практическими примерами - придумаем парочку проблем и попробуем их решить.

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

Репликация

В веб приложениях обычно доминируют запросы на получение информации - чтение комментариев, постов, пользовательских данных и т.д. Таким образом слабым местом является операция чтения и именно ее нужно масштабировать. Для решения этой задачи используется механизм репликации - один сервер назначается главным (мастер-сервером) и выполняет все запросы модификации данных, а другие сервера (слейвы) обрабатывают только запросы получения данных. При изменении или добавлении данных, мастер сервер уведомляет все слейв сервера, таким образом поддерживая актуальность данных.

Кроме того, репликация используется для географического распределения серверов (например один сервер в Америке, другой в Европе).

Принцип работы репликации

Мастер сервер при выполнении модификаций пишет все сделанные изменения в лог, slave сервера с некоторой периодичностью проверяют лог на предмет появления новых данных и если они есть - выполняют аналогичные действия со своими данными.

Репликационные схемы

1 мастер много слейвов

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

Обычно в приложении указывается список серверов (пул) предназначенных для чтения, из которого mysql клиент некоторым образом (случайным или по указанному алгоритму) выбирает сервер для выполнения запроса, таким образом балансируя нагрузку между всеми серверами.

Недостаток схемы очевиден - при выходе из строя мастер сервера, все запросы на модификацию и добавление данных не будут выполняться.

Цепочка мастер серверов

Очень удобна для географического распределения данных. Например, ставим сервера в Европе, Азии и Америке, настраиваем их в цепочку и учим приложение выбирать сервер в зависимости от региона. Таким образом получаем выигрыш за счет уменьшения пути путешествия запроса к серверу.

При увеличении нагрузки на один из региональных серверов к нему запросто можно добавить несколько слейвов.

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

2 мастера, много слейвов

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

Рассмотрим на примере настройку репликации между мастером и слейвом.

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

CREATE USER 'repl'@'%' IDENTIFIED BY 'replpass';
GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO repl@'%';

Каждый сервер в репликационной схеме должен иметь уникальный номер (целое число), который нужно прописать в my.cnf в секции [mysqld].

Например для мастера:

server-id = 1

Для слейва:

server-id = 2

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

log-bin

Рестартуем оба сервера, после чего мастер уже готов к работе, а слейву нужно указать кто его мастер, для этого:

CHANGE MASTER TO MASTER_HOST='master.example.com', MASTER_USER='repl', MASTER_PASSWORD='replpass';
START SLAVE;

Все, репликация между серверами настроена, теперь после исполнения запроса модификации на мастере, данные обновятся и на слейве. Просто и сердито.

Если репликацию нужно настроить для уже существующей базы, перед запуском оба сервера должны иметь одинаковые данные.

Партиционирование

Иногда встречаются таблицы с некоторой логической группировкой данных, например, список покупок пользователя или лог действий можно сгруппировать по дате. И когда большинство запросов работает с группами (например интересует только статистика за год), тогда есть смысл хранить данные разбитые на группы непосредственно в БД. Процесс разделения данных и хранение их в виде некоторых групп и называется партиционированием.

Рассмотрим на примере таблицы:

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)

4 типа партиционирования:

1. По диапазону

Каждая партиция содержит данные принадлежащие указанному диапазону значений колонки.

PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    PARTITION p3 VALUES LESS THAN (21)
);

В партицию p0 попадут все строки в которых store_id<6.

2. По списку значений

Каждая партиция содержит данные содержащие определенное значение в колонке.

PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (3,5,6,9,17),
    PARTITION pEast VALUES IN (1,2,10,11,19,20),
    PARTITION pWest VALUES IN (4,12,13,14,18),
    PARTITION pCentral VALUES IN (7,8,15,16)
);

Например в партицию pNorth попадут все строки в которых store_id=3, 5, 6, 9, 17.

3. По хешу

Таблица разбивается по хешу значения некоторой колонки.

4. По ключу

Аналогично предыдущему методу, но по ключу.

Последние два метода не дают возможности указать в какую из партиций попадет строка, а только количество партиций, механизм распределения MySQL берет на себя.

Партиции можно разбивать на подпартиции, которые можно дальше партиционировать и т.д.

При этом при составлении запроса не нужно думать где и в каком виде хранятся данные, просто пишем запрос к таблице employees, а MySQL определит для каких партиций его нужно выполнить.

В реальной жизни партиции используются не часто, так как в большинстве случаев индекса достаточно, но все же нужно помнить о их существовании.

Шардинг

Шардинг - развитие партиционирования - разбиение данных на группы и хранение каждой группы на отдельном сервере (шарде). В данном случае группа не обязательно включает одну таблицу, это может быть несколько таблиц содержащих одно целое. Например, имея много зарегистрированных пользователей, данные которых лежат в нескольких таблицах (регистрационные данные, история покупок, лог действий), на каждой шарде будет храниться полная инфраструктура (все таблицы), но с данными только пользователей текущей шарды (например одна шарда - пользователи с фамилиями от А до Д, вторая Д-К и т.д).

MySQL не поддерживает автоматического шардинга, поэтому его приходиться делать на уровне приложения, выбирая в зависимости от запроса нужный сервер. Обычно создается параметризованный пул серверов (в примере выше таким параметром была бы первая буква фамилии) и при выполнении каждого запроса, по этому параметру выбирается нужный сервер.

Шардинг это последняя мера масштабирования (когда репликация уже не спасает), не только из-за сложности реализации, но и из-за усложнения работы с данными. Так как работа с шардами происходит на уровне приложения, мы автоматически отказываемся от триггеров, хранимых процедур и прочих встроенных в БД плюшек которые должны работать со всеми данными, так как на уровне БД каждой шарды существует только ее набор данных и она ничего не знает о других.

Поэтому перед тем как внедрять MySQL шардинг, нужно очень хорошо подумать и быть точно уверенным что другого пути нету.

В тему:

Если пост понравился - нажмите на +1 - мне будет приятно.

@kkooler

@kkooler

Занимаюсь разработкой высоконагруженных проектов и распределенных систем на PHP.
В свободное время разрабатываю нано-проекты:

Следить за блогом

RSS канал Twitter