При разработке реляционной базы данных важно изначально задуматься об объемах данных с которыми придется работать в дальнейшем. Реляционные БД плохо масштабируются и необходимо заранее подумать как с этим справляться.
Как можно добиться масштабируемости БД:
- Шардинг — распределение данных на нескольких серверах.
- Репликация — хранение копий данных на нескольких серверах.
Master-slave репликация хорошо поддерживается реляционными БД. Она позволяет избежать потери данных при потере одного из серверов.
Так же master-slave репликация позволяет разгрузить master передав часть запросов на slave’ы. Но в этом случае возникают проблемы с актуальнустью данных на slave и баги связанные с этим. Т.е. например вы записали данные в master и тут же читаете их из slave. Но до slave они еще не дошли и вы получаете неактульные данные.
Шардинг не поддерживается известными БД. А без него нельзя реализовать полноценную масштабируемость. По-этому мы взяли за основу опыт Pinterest и реализовали шардинг MySQL самостоятельно.
Шардинг MySQL своими руками
Что нам потребуется кроме MySQL:
- NodeJS — невероятно быстрый веб-сервер
- Sequelize — ORM и миграции БД для NodeJS
- Ansible — Удаленное управление набором серверов
Серверная часть MyDataSpace реализована на NodeJS и для доступа к БД мы используем библиотеку Sequelize, благодаря ей мы легко применили модель которая описана ниже.
Мы выделили 2 MySQL-сервера master-1 и master-2 для хранение данных. На каждом из них создали несколько баз (шардов) следующим образом:
master-1:
- db_00001
- db_00002
- db_00003
- db_000…
master-2:
- db_00101
- db_00102
- db_00103
- db_001…
В общем случае БД выглядит так:
Как известно в MyDataSpace данные распределены по независимым репозиториям — корням. Каждый корень располагается в одном из шардов. Информация о том в каком из шардов находится корень хранится в отдельной базе в таблице Routes. Вот так примерно выглядит эта таблица:
shard — root
db_00001 — geonames
db_00101 — spreex
db_00102 — moscow_wifi
db_00001 — mydataspace
С помощью миграций и Ansible накатываем одинаковую структуру на все шарды. Без миграций и Ansible было бы сложно управлять таким количеством шардов.
Все, теперь при запросе на создание нового корня мы случайным образом выбираем шард, создаем запить в Routes и добавляем начальные данные в выбранный шард.
На данный момент таблица Routes находится также в базе MySQL, но в дальнейшем (когда будет написан драйвер для Sequelize) мы перенесем её в Cassandra для получение полноценной репликации master-master.
Данный подход позволяет легко переносить шарды с одного сервера на другой, делать бекапы, добавлять новые сервера и шарды. В качестве бонуса мы получили еще одну возможность, которая описана ниже.
Замороженные шарды
Любой проект меняется со временем и не возможно заранее спроектировать такую структура БД которая не будет меняться в дальнейшем. Но изменение структуры БД в которой хранятся сотни миллионов записей неблагодарное дело. По-этому часто проще оставить все как есть чем возиться с данными, что не есть хорошо. И тут на помощь приходит применённый нами подход. Мы можем “заморозить” тяжелые шарды, а изменения применить на новых или не сильно заполненных шардах.
С помощью Ansible это делается очень просто, добавляем флаг frozen в host_vars для замороженного шарда, а в задаче которая накатывает миграции добавляем условие — не накатывать миграции на сервер если он frozen.