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

«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 года.

Похожие статьи:
«Лаборатория Касперского» и швейцарская компания WISeKey приступили к совместной разработке защитной технологии для носимых...
В Интернет уже не раз попадали слухи о новом 4-дюймовом смартфоне от Apple. Модель сначала называли iPhone 7c или iPhone 6c, затем iPhone 5se, а...
16 серпня Prozorro відновлює програму Bug Bounty. Державне підприємство запрошує до співпраці фахівців із кібербезпеки, які зможуть...
Нещодавно monobank організував перший за шість років Bug Bounty, який тривав з 17 листопада до 1 грудня. Chief Information Officer Fintech Band Максим...
Львів’янин Арсен Костенко переїхав до США 6 років тому, де спочатку працював у Sony, а останні 2 роки займає позицію Software...
Яндекс.Метрика