Чому я не використовую реляційні СУБД

«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 року.

Опубліковано: 24/11/16 @ 08:00
Розділ Різне

Рекомендуємо:

3 типу посилань, які дають максимальний SEO ефект
DOU Ревізор в Lucky Labs: «Триповерхова лабораторія ігор» + ВІДЕО
Як я переїхав працювати до Франції
Ринок праці 2016: 100 тис. програмістів, бум ІТ-шкіл, надлишок PM
Front-Еnd дайджест #19: Yarn, Node v7 і Fiber