Масштабирование MySQL — применяем опыт Pinterest

07.01.2018

При разработке реляционной базы данных важно изначально задуматься об объемах данных с которыми придется работать в дальнейшем. Реляционные БД плохо масштабируются и необходимо заранее подумать как с этим справляться.

Как можно добиться масштабируемости БД:

  • Шардинг — распределение данных на нескольких серверах.
  • Репликация — хранение копий данных на нескольких серверах.

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. Вот так примерно выглядит эта таблица:

shardroot
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.