Хорошее видео с не очень хорошим звуком. Сначала посмотрите.
RetailCRM - специализированная crm-система для электронной коммерции.
2500 тысячи подключенных магазинов, 10 миллионов товаров, 25 миллионов SKU (на момент презентации).
Используемые технологии и инструменты:
- Symfony2
- PHP7
- Go
- PostgreSQL
- Redis
- Memcache
- Beanstalkd
- Deployer
- gossha
При реализации любого SaaS-проекта возникает вопрос хранения пользовательских аккаунтов. Существует два основных подхода:
- Храним все аккаунты в одной большой базе данных
- Храним каждый аккаунт в отдельной базе данных
- Вариации первых двух методов
Аккаунты в RetailCRM не связаны и не взаимодействуют друг с другом - выбрали второй вариант.
Первая задача, которая возникает какой бы фреймоврк мы не использовали - научить наше приложение динамически подключаться к разным базам данных в зависимости от текущего аккаунта. Это касается как веб-части приложения, так и консольных команд. Если проект реализуется на Yii 2.x, один из вариантов такой реализации предложил Дмитрий Елисеев. Ребята из RetailCRM используют Symfony и попробовали несколько вариантов реализации.
1-ый метод AppKernel::registerContainerConfiguration
Достоинства:
- Простая реализация
Недостатки:
- Для каждого аккаунта свой контейнер
- Разбухание кеша если аккаунтов много
- Проблема прогрева кеша
2-ой метод Единственный контейнер с динамическими параметрами. Для этого варианта пришлось реализовать свой PhpDumper. Хотя есть похожие готовые решения: Использование Expression Language или DynamicParametrsBundle
Преимущества:
- Фиксированное время деплоя
- Не забивается OpCache
Проблема с динамическим выбором базы данных решена. Однако, по мере роста проекта, все создаваемые базы данных уже не умещаются на одном сервере. Возникает необходимость в добавление новых серверов. Теперь наше приложение должно знать не только название базы данных, но и сервер, на котором она находится. Для хранения подобного “маппинга” используется простой php-файл вида:
<?php
return [
‘Account_1’ => [
‘H’ => ‘dbserv_1’, //host name
‘D’ => ‘db_1’ //databse name
]
……….
];
Разработан сервис, отвечающий за регистрацию новых аккаунтов и управления ими. В момент регистрации сервис определяет на каком из серверов создать базу данных для нового клиента. После чего делает запись в “файл-маппинг” и раскладывает новую версию файла по всем application-серверам. Используется именно статичный маппинг, а не хеш-функция для определения сервера баз данных. Причин несколько:
- Аккаунты очень неравномерны по объему данных и по нагрузке
- Разные сервера могут содержать разное количество аккаунтов
- Имеется возможность переноса аккаунта между серверами
С ростом количества баз данных на одном сервере возникает новая проблема - лимиты на количество одновременных соединений с PostgreSQL-сервером. Задача решается подключением PgBouncer и переходом к схеме, когда для каждого нового аккаунта создается не отдельная база данных, а отдельная схема. На каждом СУБД-сервере разворачивается несколько баз данных, каждая из которых содержит по 30 схем для аккаунтов.
Следующая проблема - обновление проекта и накат миграций (deploy). Поскольку баз данных много - накат новых миграций может занять продолжительное время. Может возникнуть ситуация, когда код приложения уже обновился, но миграции “раскатились” еще не по всем базам данных. Т.е. произойдет рассогласованность версий кода и баз данных. Решение довольно простое - каждый аккаунт работает со своей версией кода. Создается символическая ссылка, которая указывает на определенную версию. Deployer последовательно переключает символьную ссылку для каждого аккаунта и накатывает миграции. Таким образом, каждый аккаунт становится независимым и по коду, и по базе данных.
Итого. Минусы.
- Приложение должно уметь работать с несколькими базами данных
- Необходимо создавать базу под каждого нового клиента
- Отдельный VACUUM для каждой базы данных
- Очень много одновременных коннектов к СУБД-серверу
- Сложно делать сквозные отчеты
Итого. Плюсы.
- Шардинг баз данных без дополнительных ухищрений
- Перенос аккаунтов между серверами
- Восстановление любого аккаунта из бекапа
- Удаление мертвых аккаунтов - просто удаляем БД
- Плавный накат миграций
- Разные аккаунты на разных версиях приложения