Пример
Представим себе БД услуг барбершопа в Южном Бутово. Данные лежат в одной табличке в PostgreSQL и используются внутренней системой для составления расписания мастеров, расчета премий и т. д.
Бородачей в Бутово оказалось сильно больше, чем предполагалось в изначальном бизнес-плане.
Владельцам барбершопа пришлось выкупить соседний спортзал и нанять еще 100 барберов.
Бизнес процветает. Роскошные бороды заполонили Бутово. Однако, БД начала подводить:• из-за выхода из строя сервера БД 400 людей остались не побритыми• в самое загруженное время, чтобы узнать расписание мастера,администратор вынужден ждать до 60 секунд ответа БД
Такие потери недопустимы для бизнеса. Что ж, давай разбираться. Технически есть 2 проблемы:
• нет отказоустойчивости
• слишком много чтения из БД
Для решения этих проблем можно использовать репликацию.
Репликация
Репликация - полное копирование БД на такой же сервер.
Основной сервер назовем "Мастер", а дополнительные - "Реплика". Любой из этих серверов будем называть "Нода" (node).
Писать мы сможем только в мастер. А реплики будут вытягивать из мастера все изменения (синхронизироваться).
Как это решит наши проблемы?
Отказоустойчивость
Если мастер умрёт, то одна из реплик сходу станет новым мастером
Чтение
Читать мы можем из любой ноды. Поэтому теперь мы можем читать в три раза больше.
Похоже, все снова счастливы. Но не тут то было...
Масштабируемся дальше
Спортзал и днем и ночью забит модными бруталами. Генеральный директор решает открывать филиалы по всей Москве.
После 30-го филиала стали учащаться жалобы от администраторов на тормоза нашей системы.
Оказывается:• теперь БД не может переварить так много записей• да и место на диске стремительно заканчивается
Что будем делать? Конечно, шардироваться!
Горизонтальное шардирование
В отличии от репликации, шардирование не дублирует данные, а разделяет их по нодам.
Давай разложим все наши записи на 4 ноды.
Ключом шардирования будет barber_id. Так мы будем уверены, что все записи к одному мастеру будут физически находиться на одной ноде.
Остаток от деления barber_id на 4 (количество шардов) = номеру шарда, в который мы положим запись.
Теперь наша БД из 12 нод будет выглядеть так
Теперь
• записывать мы можем в 4 раза больше данных одновременно
• диска стало тоже в 4 раза больше
Недостаток горизонтального шардирования
У горизонтального шардирования есть большой недостаток - оно позволяет хорошо масштабировать только запросы, опирающиеся на ключ шардирования.
Например запросы:
select client, time from clients where barber_id = 100;
select price from clients where barber_id = 102 and client = 'fedor';
будут всегда ходить только в один шард. Значит, что с увеличением нагрузки мы сможем увеличивать количество шардов.
А запросы типа
select barber_id from clients where client = 'yaroslav';
select max(price) from clients;
придется выполнять на каждом шарде.
Если таких "кросс-шардовых" запросов окажется больше чем "одно-шардовых", то скорее всего схема шардирования наносит только вред нашей системе.
Вертикальное шардирование
Пусть у нас появилась еще одна табличка с премиями барберов
Мы решили и её пошардировать по barber_id, чтобы вся информация о конкретном барбере лежала в одном шарде.
Самым популярным запросом по этой таблице с большим перевесом стал запрос, выбирающий суммарную премию отделения. Отделение - список барберов, например (101, 201, 301, 401, 501, 601).
Вот сам запрос:
select sum(bonus) from bonuses where barber_id in (101, 201, 301, 401, 501, 601);
Такой запрос почти всегда будет выполняться на всех шардах. Это плохо. Может быть мы можем лучше?
Можем!
Мы можем положить всю эту таблицу в отдельный шард.
Так мы шардировались вертикально. Запомнить легко:
• горизонтальное деление - горизонтальное шардирование
• вертикальное деление - вертикальное шардирование
Наша схема шардирования - смешанная (и вертикальная, и горизонтальная)
Таким образом, частые запросы всегда попадают в один шард. Бизнес может развиваться дальше, например открывать барбершопы в деревнях и сёлах Нижегородской области.
Недостаток вертикального шардирования
Вертикальное шардирование разрывает таблицы по разным серверам. Из-за этого теперь нельзя писать JOIN-операции.
Например, мы не можем больше сделать такой запрос
select c.client, b.bonus
from clients c
join bonuses b
on c.barber_id = b.barber_id;
К нашей радости, он не имеет смысла в этой предметной области.
Общий недостаток шардирования
Разделение данных по разным машинам делает невозможными локальные транзакции.
Чтобы гарантировать атомарность изменений в распределенных системах существуют распределенные транзакции (например, использующие двухфазный коммит). Это довольно сложные штуки. Крайне не рекомендую реализовывать их самостоятельно.
Выводы
1. Распределенные БД сильно сложнее не распределенных. Несколько раз подумай, будет ли твой барбершоп настолько популярным, прежде чем туда лезть.
2. Универсального способа масштабироваться не существует. Всегда присутствует trade-off. Выбор между репликацией, горизонтальным и вертикальным шардированием нужно делать, опираясь на предметную область и статистику использования БД.
3. Все же можно придумать некие гайдлайны по выбору:
Шардирование vs Репликация
• отказоустойчивость -> репликация
• много чтения -> репликация или шардирование
• много записи -> шардирование
Горизонтальное vs Вертикальное шардирование
• в основном запросы по ключу -> горизонтальное шардирование
• таблицы тесно связаны -> горизонтальное шардирование
• батчёвые запросы -> вертикальное шардирование
• поиск по разным наборам столбцов -> вертикальное шардирование