Почему я не использую реляционные СУБД
«Rather than making my app easy to deploy, I’ll just do a bunch of gnarly shit in Docker» Some CEO
Если вы стартап и начинающий бизнес, то одна из первых задач, которая будет стоять перед вами, — выкатить на вчера хоть что-нибудь. Так как ~90% стартапов умирают в первый же год (цифра 90% разная в зависимости от типа исследования и источника), это требование является довольно разумным и очевидным. Нужно как можно быстрее проверить гипотезы, понять, нужен ли ваш продукт хоть кому-нибудь и попытаться подписать первых клиентов уже хотя бы на стадии MVP, в общем — продать хоть что-нибудь. В условиях постоянной спешки и постоянно меняющихся требований нет никакого смысла проектировать крутое и гибкое ядро. Эта инвестиция никогда не окупится, кроме случая, когда это ядро является самим продуктом.
В аутсорс мире разработка нового проекта обычно начинается так: находится клиент с деньгами, он дает требования, бизнес-аналитики переваривают требования и отдают техническую спецификацию конечной команде разработчиков, работу которых потом проверяет команда тестеров и деплоем которой занимается команда девопсов. Это отлично и работает. Особенно, когда у клиента есть ресурсы на все это.
В мире стартапов и начинающего бизнеса денег нет. Есть только немного человеческого ресурса. Спека? Пффф... Это непозволительная роскошь. Тестеры? Пфф... Девопсы? Ну вы поняли.
Все приходится делать самому. И базы данных тут сплошная помеха. И сразу по нескольким причинам.
СУБД медленные
Уверен, что каждому опытному разработчику приходилось тюнить какой-нибудь запрос или саму базу. Переключать движки с InnoDB на MyISAM, изменять размер кеша под конкретное железо, смотреть на план выполнения запросов, думать о правильных индексах и искать компромисс между нормализацией, дубликацией данных и скоростью.
Уже давно не секрет: современная СУБД 90% времени тратит на обслуживание своих подсистем — логирование, блокировки, менеджмент памяти, планировщик запросов, парсер, лог транзакций и т.д. И лишь 10% собственно на выполнение нужного клиенту запроса. То есть огромное количество ресурсов тратится впустую. Просто потому, что для большинства приложений из прошлого это было необходимым функционалом.
Каждый раз, когда вы делаете:
userDao.save(user); //insert into users (id, name) values (?, ?);
Я делаю:
try (BufferedWriter writer = Files.newBufferedWriter(fileTo, UTF_8)) { writer.write(user.toJson()); }
Я храню все данные в файловой системе, как простой json файл. Это очень быстро, просто, и это отлично работает.
Запросы — это дорого
Если решить вопрос с медленными СУБД еще возможно переходом на заточенные под конкретную задачу решения, то с выполнением самого запроса уже труднее. Даже если у вас есть пул соединений, и вы не тратите ресурсы на их открытие.
Только представьте себе длину пути всего запроса в типичном java приложении: request → ORM → jdbc driver → network → DB, и в обратную сторону.
Есть лишь два частичных выхода из ситуации: не выполнять запросы к БД на каждый реквест от пользователя, например, с помощью кеша приложения, или делать батчинг, чтобы исключить оверхед на постоянные раундтрипы по сети.
Каждый раз, когда вы делаете:
user.setName("'Peter'"); userDao.update(user); //update users set name = 'Peter' where id = ?;
Я делаю:
user.name = "'Peter'";
Я не сохраняю каждое изменение модели на диск в момент, когда модель меняется. Вместо этого я сохраняю измененные объекты на диск раз в 1 минуту в отдельном потоке. Все апдейты я делаю исключительно батчами, потому что одиночные запросы — дорогие. Я сохраняю данные прямо на диск, потому что не хочу передавать данные по сетевому интерфейсу каждый раз, когда изменилась модель. Это дорого, медленно, и нужно хендлить ошибки соединения.
RAMа так много, что диск нужен лишь меньшинству
Люди инертны, но мир меняется очень быстро. И он уже изменился. Смиритесь с этим. В то время, когда Amazon запускает сервера с 2 ТБ RAM, а средний java сервер — это 32 ГБ RAM, люди добавляют в проекты СУБД просто, чтобы сохранить там
Вы можете хранить все в памяти. Для многих приложений этого будет более чем достаточно. Как минимум, этого будет достаточно для ~90% стартапов, которые умрут, так и не став звездой единорогом. Прикрутить базу вы всегда успеете — когда попадете в те 10%.
Каждый раз когда вы делаете:
User user = userDao.get(name); //select * from users where name = ?;
Я делаю:
Map<String, User> users …; User user = users.get(name);
Все пользовательские данные я храню в памяти. Конечно, данных может быть очень много, и все не уместить. Например, в моем случае в памяти хранится все, кроме данных репортинга — так как их действительно много. Их дешевле писать/читать напрямую из диска. 100К активных пользователей — ровно столько я могу обслуживать с 1 ГБ RAM на виртуалке за 10 у.е.
Схемы, схемы, схемы
Чтобы начать работу с СУБД, мне нужна схема. Без схемы я ничего не могу сделать. Я не сажусь писать бизнес-логику — я сажусь и описываю мапинг, зависимость между классами и на основе мапинга генерю схему (ну хоть на этом спасибо ORM). Но бизнес не интересует схема. Ему нужно сделать на вчера. Я мог бы спроектировать крутую нормализованную схему как на рисунке ниже. Но зачем?
Схему нужно постоянно поддерживать. Когда у вас нет продакшена — это легко. Но как только у вас появился первый клиент, вы в тупике. Luiquibase, flyway — классные тулы. Но вы их используете, когда вы уже в тупике. Вы не можете сделать изменение в модели, ничего не поломав. И еще вам нужны роли, пользователи, разграничение прав доступа, разные базы данных. А бизнесу нужно на вчера.
Когда вы пишите:
@Entity @Table(name = "Users") public class User { private Long id; @Id @GeneratedValue(generator="increment") @GenericGenerator(name="increment", strategy = "increment") public Long getId() { return id; } }
Я пишу
public class User { public long id; }
Нет БД? Это несерьёзно
«Это несерьёзно» — фраза, которую я постоянно слышу от матерых сениоров. И это забавно, ведь в большинстве случаев сениор даже и близко не подозревает о потребностях бизнеса.
Несерьёзно два года делать продукт, который так и не попадет в продакшн. Несерьёзно делать системы, которые умирают при нагрузке в 10 рек-сек. Несерьёзно строить системы, где половину можно просто выкинуть, потому что фабрики фабрик, интерфейс от интерфейса, и вся эта универсальная гибкость никому не нужна.
Если бизнес спрашивает: «Какую БД вы используете?» — вы общаетесь не с бизнесом. Бизнесу это не интересно. Клиента интересует лишь то, решает ли система его проблему. И если решает, то по какой цене. Остальное никого не интересует. Ни один наш бизнес-клиент не спросил: «Какая у Вас БД?» И это при том, что все наши клиенты — технические компании!
Когда Вы делаете:
create table users;
Я пишу бизнес-логику.
Базы данных нужно поддерживать
В аутсорсе было хорошо. Мой код — моя ответственность. Все что дальше — не мое дело. Мне легко было добавлять postgres, redis и sharding для них. Сегодня, когда я все делаю сам, я понимаю, что у каждого дополнительного модуля системы есть цена деплоя и стоимость поддержки. Я не буду добавлять БД в проект, пока без нее могу решить бизнес-задачу минимальной ценой.
Добавить БД в проект легко. Но потом ее нужно развернуть локально, развернуть в тестовой среде, развернуть на стейджинге, развернуть на продакшене. А это все работа.
БД нужно мониторить. Она может упасть, там может закончиться диск, выполнение запроса может «подвиснуть», процесс может скушать 100% CPU, БД может начать тормозить, delete может не очищать диск и нужно выполнять vacuum, данные могут быть испорчены и т.д.
Если вы продаете не только облако как сервис, но и услугу развертывания приватных серверов, как это делаем мы, то каждая дополнительная инструкция увеличивает стоимость и время развертывания. Да, есть докер, но проект тоже не стоит на месте. Нужно постоянно обновлять, поддерживать и следить за контейнерами.
Пока вы делаете:
https://github.com/docker-library/postgres/blob/e4942cb0f79b61024963dc0ac196375b26fa60dd/9.6/Dockerfile
Я делаю:
java -jar server.jar
О проекте. Наш проект Blynk — IoT платформа с мобильными приложениями. 30К MAU. Текущая нагрузка на систему — 4500 рек-сек. Почти 3000 девайсов постоянно в сети. Всего периодически подключается около 10К девайсов. Аптайм 99,99% за полтора года. Вся система обходится в 120$ (50$ из них Geo DNS) в месяц. Запас прочности — 10х на текущем железе; + 10х возможность вертикального роста. Проект опенсорс, глянуть можно тут.
P. S. Я не призываю вас отказываться от БД. Есть класс задач, где без БД никак, и они действительно упрощают жизнь. Но не стоит бездумно лепить БД в проект лишь потому, что вам нужно сохранить данные на диск.
P. P. S. Статья пролежала в черновиках полгода. Сейчас мы уже используем Postgres (как backup систему) и Redis для лоад балансинга (не бизнес-логика). Все это входит в 120$. Тем не менее, система успешно просуществовала без баз данных 2,5 года.