Ребалансировка данных при шардинге

Опубликовано admin в Ср, 03/02/2016 - 16:23

Со временем при шардинге неизбежно возникает необходимость перебалансировать данные. Как бы вы не старались точно предсказать рост объема и формы данных, это сделать практически невозможно. Поэтому ребалансировка данных — такая же систематическая операция, как и их хранение. Ее нужно планировать на этапе проектирования, а не на этапе администрирования.

Допустим мы имеем данные, которые распределены между 5 серверами. Пусть это будет таблица личных сообщений:

messages: id | user_id | time | message | sender

Данные в этой таблице распределяются между 5 серверами путем проверки user_id (остаток от деления на 5):

Остаток от деления
user_id % 5 == 1
user_id % 5 == 2
user_id % 5 == 3
user_id % 5 == 4
user_id % 5 == 0

Номер сервера с данными
1
2
3
4
5

Т.е. для получения списка личных сообщений пользователя с id = 47, необходимо выполнить что-то похожее на:
<?

$user_id = 47;
$server = $user_id % 5 ? : 5;

# подключаемся к нужному серверу
$connection = connect($server);

# выполняем обычный запрос (в рамках подключения)
$messages = query($connection, "SELECT * FROM messages WHERE user_id = {$user_id}"); # без инъекций

Подготовка к ребалансированию
Если текущие сервера заняты на 7080% это признак того, что пора расширять их количество. Сколько ставить новых серверов? Прикинуть можно очень просто:

  • Количество занятого места на диске (на всех серверах) не должно быть более 50%, иначе готовность к взрывным нагрузкам никакая.
  • Скорость роста данных не должна приводить к повторной ребалансировке быстрее, чем Вы успеете подготовить новое железо. Это от нескольких дней до недели обычно. Однако ясно, что лучше обеспечить какой-то дополнительный запас (например, месяц).

Допустим мы поставили еще 5 серверов и теперь их у нас 10. Если говорим о записи новых данных, то тут ничего делать не нужно — просто изменить логику шардинга (остаток от деления нужно теперь считать от 10). Но для правильного получения ранее сохраненных данных, необходимо выполнить ребелансировку данных со старых серверов на новые.

Например, в старой схеме с 5 серверами сообщения пользователя с id = 47 лежали на сервере:
[5 серверов]: user_id = 47, server_id = 2
# остаток от деления 47 на 5 = 2

В новой схеме с 10 серверами, данные должны лежать тут:
[10 серверов]: user_id = 47, server_id = 7
# остаток от деления 47 на 10 = 7

Проведение ребалансировки

Есть две стратегии.
Синхронная и асинхронная.

Асинхронная ребалансировка
После установки новых серверов, до включения их в работу, необходимо переместить все данные со старых узлов на новые. Это мегаскрипт, который сделает следующее:
<?

foreach ( $users as $id )
{
# вычисляем номера серверов в старой и новой схеме
$old_server = $id % 5 ? : 5;
$new_server = $id % 10 ? : 10;

if ( $old_server != $new_server )
{
# получаем все сообщения со старого сервера
$all_messages = get_messages($old_server, $id);

# сохраняем все сообщения на новый сервер
save_messages($new_server, $id, $all_messages);
}
}

После перемещения всех данных, приложение начнет работать с новым набором серверов.

Ясно, что на время перемещения необходимо как-то знать, кто из пользователей уже обработан, а кто еще нет.
Это можно сделать путем добавления флага в табличку пользователей и сброса его перед началом перебалансирования:
users: id | email | | messages_rebalanced

Тогда, при перабалансировке необходимо будет обновлять этот флажок:
<?

foreach ( $users as $id )
{
# перемещаем данные со старого на новый сервер
save_user($id, ['messages_rebalanced' => 1]);
}

В приложении это позволит обеспечить нормальную работу прямо во время перемещения данных:
<?

$user_id = 47;

if ( $user['messages_rebalanced'] )
{
# данные уже перемещены, читаем с нового сервера
$server = $user_id % 10 ? : 10;
}
else
{
# данные еще не перемещены, читаем со старого сервера
$server = $user_id % 5 ? : 5;
}

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

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

Синхронная ребалансировка

Синхронная ребалансировка подразумевает, что мы ничего не делаем с данными, пока к ним не прийдет запрос. Например, данные пользователя c id = 47 будут лежать на 2м сервере, хотя серверов уже будет 10.

Итак, добавляем новые сервера и сразу же включаем их в работу.

Если приходит запрос на запись, мы записываем данные на нужный сервер в новой схеме.

Однако, как только приходит запрос на чтение, мы проверяем старую схему и переместим данные со старого сервера на новый:

Допустим мы хотим прочитать данные пользователя с user_id = 47, данные которого пока еще лежат на старом сервере (это мы тоже узнаем по флажку messages_rebalanced). Тогда перед получением данных, мы выполним их перемешение:

<?

$user_id = 47;

if ( !$user['messages_rebalanced'] )
{
# перемещаем сообщения перед их чтением
$old_server = $user_id % 5 ? : 5;
$messages = get_messages($server, $user_id);

$old_server = $user_id % 10 ? : 10;
save_messages($new_server, $user_id, $messages);

save_user($user_id, ['messages_rebalanced' => 1]);
}

# читаем все сообщения с нового сервера
$server = $user_id % 10 ? : 10;
$messages = get_messages($server, $user_id);

# перед первым чтением в новой схеме, данные будут перемещены со старого на новый сервер

Этот метод очевидно удобнее и эффективнее, чем асинхронный. Будут перемещаться только актуальные данные, кроме этого весь процесс сам распределится по времени. Но будьте внимательны, при перемещении больших объемов данных (например, если сообщения хранятся годами и могут заниматься сотни мегабайт), лучше использовать асинхронное ребалансирование. Иначе, приложение может крайне медленно работать для пользователя.

Самое важное
Ребалансировка — часть проектирования, Вам придется ее делать рано или поздно. Подготовьтесь к тому, чтобы иметь удобный механизм ребалансировки до ее первого применения. Зачастую лучше использовать синхронный метод, однако при больших объемах перемещаемых данных, лучше выбрать асинхронный подход.

( categories: )