Первый месяц жизни приложения BookDesk или как меня пытались взломать
В этой статье я расскажу о первых результатах работы приложения для хранения прочитанных книг в первый месяц жизни.
Всем привет. Чуть более месяца назад я выпустил релиз своего приложения BookDesk: Читательский дневник для хранения всех своих прочитанных книг. Почитать про историю создания можно в первой части.
Первые проблемы
NoSQL injection
После публикации статьи об истории создания приложения я столкнулся с некоторыми проблемами.
Получил «проверку» моего API на стрессоустойчивость и высоконагруженность. Собственно, по-умолчанию все виды тестирования API должны быть проведены до релиза, но я конечно-же этого не сделал и получил проблемы: нестабильную и медленную работу приложения и плюс ко всему сервер падал, а в частности падала база данных из-за огромной нагрузки. Я держал в голове тот момент, что после запуска такое вполне себе может быть, но подумал, что это задача точно не первой необходимости т.к. большого количества пользователей я не ожидаю да и кому я нужен, хотя очень просчитался.
В режиме продакшена пришлось лезть и изучать как сделать API защищенным. В качестве веб-сервера API у меня express.js и я выполнил официальные рекомендации от разработчика и стал спать спокойнее.
А если конкретнее, то первым делом поставил rate-limiter, который устанавливает лимит на определенное количество запросов за определенный отрезок времени. Также, поставил ряд пакетов для защиты. Конечно, от чего-то серьезного это не спасет, но для школьников-ддосеров чуть прикроет двери. Дальше буду решать проблемы по мере их поступления.
По логам запросов я видел странные значения в query и body параметрах запросов к API, и тут меня осенило, что идут активные попытки инъеккций в базу данных.
На стороне API у меня была простейшая самописная валидация в стиле проверки на пустоту, однако, этого конечно-же недостаточно на сегодняшний день.
Ведь есть такое понятие как SQL/NoSQL инъекции, когда через API в вашу базу могут записать нежелательные данные в виде запросов через которые злоумышленники могут получить доступ к защищенным данным или просто положить Вам сверер и базу данных. И от этого надо иметь защиту. я поставил пакет express-validator и создал валидаторы для каждой конечной точки и для каждого параметра. Например, у меня есть параметр bookStatus, который может иметь только 4 String [all, inProgress, planned, completed] параметра и я делаю на это проверку, если значение будет отличное от одного из этих параметров, API вернет 500 ошибку. Я рад, что за столь небольшое количество времени, получил такие уроки.
Сервер
VPS сервер
У меня уже был готовый настроенный VPS, который я арендую в одной из хостинговых компаний. На нем я размещаю свои контентные сайты на WordPress.
На сервере 3 ядра и 8 Гб оперативной памяти, система Debian 9. Ну и туда же я поставил MongoDB + node.js для развертывания API, т.к. решил, что не имеет смыла покупать новый VPS чтобы отдельно на нем держать приложение и платить дополнительные деньги, ведь приложение будет полностью бесплатным (но тут я глубоко ошибался).
htop мониторинг
Я мониторил нагрузку и видел (использовал htop), что приложение работает медленно. Загрузка книг, авторизация и все другие операции работали не так как хотелось бы. Но самым долгим процессом была работа загрузки каталога с книгами. Тому виной было использование и так нагруженного сервера, ну и конечно же ошибки в аггрегации данных и в логике работы (о чем я расскажу ниже).
Напомню, что это мое первое полноценное приложение с бэк-эндом, базой данных и API с большим количеством данных. В первой версии приложения база содержала около 60 тыс. записей книг, с этим количеством все запросы справлялись неплохо. Поиск и фильтрация, да и просто подгрузка контента при скроллинге. В среднем, время выполнения запроса составляло около 1–2 секунд, что конечно-же тоже далеко от идеала.
Увеличение базы книг
Получив первые отзывы, я понял, что база слишком мала для такого рода проекта. Ведь 60 тыс. книг это капля в море. На сегодняшний момент, суммарное количество выпущенных книг на русском языке составляет миллионы экземпляров. Я решил заняться расширением базы. После расширения, база составила более 250 тыс. книг. Таким образом база приросла на, без малого, 200 тыс. книг.
Я начал тестировать скорость работы и получив первые результаты, был немного шокирован. Скорость выполнения составляла ~7–10 секунд, это огромная цифра.
Для вытаскивания данных из базы я использую аггрегацию. Это значит, что у меня есть коллекция под названием книги где, собственно, хранятся сами книги и есть коллекция пользовательские_книги где хранится информация о книгах которые пользователи добавляют себе, и эта коллекция содержит id книги, статус и другие служебные поля. И чтобы вывести в общем списке книги мне необходимо соединить эти 2 коллекции в одну по разным условиям и показать пользователю, а для этого, база должна пройти по этим двум коллекциям и вернуть данные. Погуглив я нашел полезную статью от самой MongoDb Aggregation Pipeline Optimization довольно полезный материал, я применил ряд советов.
C MongoDB я работаю первый раз, да и вообще с базами данных на таком уровне. У MongoDB есть такая возможность как создание индексов, которая помогает быстрее возвращать результаты при сортировках, фильтрации и т.д.
Посидев и подумав, меня осенила интересная мысль, а зачем делать манипуляции со всеми книгами в базе, чтобы потом отдать пользователю только первые 50 результатов. По умолчанию, сейчас подгрузка идет по 50 результатов. И тогда я решил, что можно ограничить все это дело только первыми 10 тыс. результатов, ведь мало пользователей будет скроллить так глубого каталог с рекомендациями (это надо совершить 200 подгрузок по 50 резульатов), ну, а дальше буду решать по ходу дела если возникнут вопросы. Так и сделал, по итогу я получил скорость выполнения запроса в ~1 сек, что в ~7–10 раз быстрее чем было.
Переезд на новый сервер
Я понимал, что помимо нагрузки от приложения, существует нагрузка от других сайтов, которые хранятся на сервере, а они используют MySQL, что дает суммарно немалую нагрузку.
Обычно процессор и оперативная память нагружены на 70–95%. И поэтому, я решил арендовать отдельный VPS под приложение. Выбрал сервер с 3 ядрами + 8 Гб памяти + NVME диск на Debian 11. Да и вообще, если начал что-то делать надо делать это хорошо. И после переезда на новый сервер скорость выполнения запросов выросла до 500 мс в среднем с высокой стабильностью.
Я давно пользуюсь разными хостингами, но отдельно хотелось бы упомянуть данного хостера, т.к. у ребят все организовано очень четко, впервые за 10 лет аренды серверов сталкиваюсь с таким отношением к клиенту и продуманным кабинетом. Хостер timeweb, это не реклама, просто люблю давать заслуженные рекомендации. Сейчас, судя по логам — нагрузка на сервер минимальная.
Новые функции приложения
За месяц работы приложения я успел выпустить достаточное количество улучшений, исправлений и новых функций.
Изменяемый рейтинг для книг, теперь юзеры могут ставить лайки книгам.
картинка оповещения
Добавил функцию оповещения пользователей о новой версии приложения
картинка полное описание
Добавил возможность зайти в книгу и посмотреть ее полное описание и другую информацию
картинка английская версия
Английская версия
Добавление своих книг
Отдельно хочу рассказать о функции добавления своих книг. Эта функция была одной из первых для добавления, т.к. я понимал, что невозможно держать базу в актуальном состоянии да еще и хранить все книги. И единственным решением являлась реализация функции добавления своих книг. Сейчас любой желающий может добавлять свои любые книги которых нет в базе. Удобная форма добавления имеет пошаговую логику.Также есть возможность автоматического подбора обложек.
Статистика
На данный момент приложение имеет 95 активных установок. Ежедневно добавляется 1–3 аккаунта (это я вижу по базе данных), есть активные пользователи, которые пользуются приложением часто. Я не планирую добавлять рекламу и платные функции, хочу сделать приложени полностью бесплатным и последить за результатами.Работаем дальше и следим за результатами. Мой горизонт планирования 1–2 года чтобы получить какие-то первые понятные результаты. Я не планирую пока закупать рекламу, ориентируюсь только на органический траффик.
Выводы
1. Под вашй проект/приложение сразу лучше оранизовать отдельный сервер/VPS чтобы избежать проблем с увеличением проекта и/или увеличением нагрузки. И неважно, платное оно или бесплатное. Я считаю, что если Вы взялись что-то делать, а еще если это 'что-то' нацелено на общий доступ и подразумевает использование его реальными людьми, то Ваша главная обязанность обеспечить бесперебойную и быструю работу приложения.
2. Если запускаете свой проект/приложение — в первую очередь позаботьтесь о защите Вашего API от злоумышленников.
3. Ставьте rate-limiter чтобы ограничить количество запросов в единицу времени с одного IP.
4. Делайте жесткую валидацию входяших параметров, это поможет избежать нежелательных инъекций в базу данных.
5. На UI также нужна хорошая валидация для всех форм, чтобы избежать XSS атак (если речь идет о веб-приложениях).
Всем спасибо за внимание и буду очень рад если кому-нибудь мое приложение BookDesk будет полезно.