Почему я не использую реляционные СУБД

«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, люди добавляют в проекты СУБД просто, чтобы сохранить там 1-2 ГБ данных.

Вы можете хранить все в памяти. Для многих приложений этого будет более чем достаточно. Как минимум, этого будет достаточно для ~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 года.

Похожие статьи:
Володимир Стиран займається кібербезпекою з 2005 року, є одним із засновників компанії Berezha Security. Спеціалізується на Penetration Testing, аудиті...
Шведсько-українська ІТ-компанія Sigma Software розширює свої нинішні офіси та відкриває нові локації в західних областях України, а також...
Этот дайджест написан в соавторстве с Сергеем Жуком. В выпуске: обеспечение безопасности данных пользователей, Kotlin 1.4,...
В сети немало споров и дискуссий на тему того, кто должен проводить собеседование с разработчиком — HR или...
Всем привет. Меня зовут Андрей Трубицын, я сотрудничаю с ЕРАМ как Java Solution Architect. В этой статье расскажу, как...
Яндекс.Метрика