Первый месяц жизни приложения BookDesk или как меня пытались взломать

В этой статье я расскажу о первых результатах работы приложения для хранения прочитанных книг в первый месяц жизни.

Всем привет. Чуть более месяца назад я выпустил релиз своего приложения BookDesk: Читательский дневник для хранения всех своих прочитанных книг. Почитать про историю создания можно в первой части.

Первые проблемы

NoSQL injection

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 сервер

У меня уже был готовый настроенный VPS, который я арендую в одной из хостинговых компаний. На нем я размещаю свои контентные сайты на WordPress.

На сервере 3 ядра и 8 Гб оперативной памяти, система Debian 9. Ну и туда же я поставил MongoDB + node.js для развертывания API, т.к. решил, что не имеет смыла покупать новый VPS чтобы отдельно на нем держать приложение и платить дополнительные деньги, ведь приложение будет полностью бесплатным (но тут я глубоко ошибался).

htop мониторинг

htop мониторинг

Я мониторил нагрузку и видел (использовал htop), что приложение работает медленно. Загрузка книг, авторизация и все другие операции работали не так как хотелось бы. Но самым долгим процессом была работа загрузки каталога с книгами. Тому виной было использование и так нагруженного сервера, ну и конечно же ошибки в аггрегации данных и в логике работы (о чем я расскажу ниже).

Напомню, что это мое первое полноценное приложение с бэк-эндом, базой данных и API с большим количеством данных. В первой версии приложения база содержала около 60 тыс. записей книг, с этим количеством все запросы справлялись неплохо. Поиск и фильтрация, да и просто подгрузка контента при скроллинге. В среднем, время выполнения запроса составляло около 1–2 секунд, что конечно-же тоже далеко от идеала.

Увеличение базы книг

ee3b485f2d7a656d3dd3083dcfb0cd8e.webp

Получив первые отзывы, я понял, что база слишком мала для такого рода проекта. Ведь 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, это не реклама, просто люблю давать заслуженные рекомендации. Сейчас, судя по логам — нагрузка на сервер минимальная.

Новые функции приложения

За месяц работы приложения я успел выпустить достаточное количество улучшений, исправлений и новых функций.

Изменяемый рейтинг для книг, теперь юзеры могут ставить лайки книгам. картинка оповещения

Изменяемый рейтинг для книг, теперь юзеры могут ставить лайки книгам.
картинка оповещения

Добавил функцию оповещения пользователей о новой версии приложения картинка полное описание

Добавил функцию оповещения пользователей о новой версии приложения
картинка полное описание

Добавил возможность зайти в книгу и посмотреть ее полное описание и другую информацию картинка английская версия

Добавил возможность зайти в книгу и посмотреть ее полное описание и другую информацию
картинка английская версия

Английская версия

Английская версия

Добавление своих книг

f22a5c27825ed9a10777897768350fee.webp

Отдельно хочу рассказать о функции добавления своих книг. Эта функция была одной из первых для добавления, т.к. я понимал, что невозможно держать базу в актуальном состоянии да еще и хранить все книги. И единственным решением являлась реализация функции добавления своих книг. Сейчас любой желающий может добавлять свои любые книги которых нет в базе. Удобная форма добавления имеет пошаговую логику.Также есть возможность автоматического подбора обложек.

Статистика

На данный момент приложение имеет 95 активных установок. Ежедневно добавляется 1–3 аккаунта (это я вижу по базе данных), есть активные пользователи, которые пользуются приложением часто. Я не планирую добавлять рекламу и платные функции, хочу сделать приложени полностью бесплатным и последить за результатами.Работаем дальше и следим за результатами. Мой горизонт планирования 1–2 года чтобы получить какие-то первые понятные результаты. Я не планирую пока закупать рекламу, ориентируюсь только на органический траффик.

Выводы

1. Под вашй проект/приложение сразу лучше оранизовать отдельный сервер/VPS чтобы избежать проблем с увеличением проекта и/или увеличением нагрузки. И неважно, платное оно или бесплатное. Я считаю, что если Вы взялись что-то делать, а еще если это 'что-то' нацелено на общий доступ и подразумевает использование его реальными людьми, то Ваша главная обязанность обеспечить бесперебойную и быструю работу приложения.

2. Если запускаете свой проект/приложение — в первую очередь позаботьтесь о защите Вашего API от злоумышленников.

3. Ставьте rate-limiter чтобы ограничить количество запросов в единицу времени с одного IP.

4. Делайте жесткую валидацию входяших параметров, это поможет избежать нежелательных инъекций в базу данных.

5. На UI также нужна хорошая валидация для всех форм, чтобы избежать XSS атак (если речь идет о веб-приложениях).

Всем спасибо за внимание и буду очень рад если кому-нибудь мое приложение BookDesk будет полезно.

© Habrahabr.ru