В этой статье я расскажу о первых результатах работы приложения для хранения прочитанных книг в первый месяц жизни.
Всем привет. Чуть более месяца назад я выпустил релиз своего приложения BookDesk: Читательский дневник для хранения всех своих прочитанных книг. Почитать про историю создания можно в первой части.
Первые проблемы
После публикации статьи об истории создания приложения я столкнулся с некоторыми проблемами.
Получил «проверку» моего 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, который я арендую в одной из хостинговых компаний. На нем я размещаю свои контентные сайты на WordPress.
На сервере 3 ядра и 8 Гб оперативной памяти, система Debian 9. Ну и туда же я поставил MongoDB + node.js для развертывания API, т.к. решил, что не имеет смыла покупать новый VPS чтобы отдельно на нем держать приложение и платить дополнительные деньги, ведь приложение будет полностью бесплатным (но тут я глубоко ошибался).
Я мониторил нагрузку и видел (использовал 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 года чтобы получить какие‑то первые понятные результаты. Я не планирую пока закупать рекламу, ориентируюсь только на органический траффик.
Выводы
Под вашй проект/приложение сразу лучше оранизовать отдельный сервер/VPS чтобы избежать проблем с увеличением проекта и/или увеличением нагрузки. И неважно, платное оно или бесплатное. Я считаю, что если Вы взялись что‑то делать, а еще если это 'что‑то' нацелено на общий доступ и подразумевает использование его реальными людьми, то Ваша главная обязанность обеспечить бесперебойную и быструю работу приложения.
Если запускаете свой проект/приложение — в первую очередь позаботьтесь о защите Вашего API от злоумышленников.
Ставьте rate‑limiter чтобы ограничить количество запросов в единицу времени с одного IP.
Делайте жесткую валидацию входяших параметров, это поможет избежать нежелательных инъекций в базу данных.
На UI также нужна хорошая валидация для всех форм, чтобы избежать XSS атак (если речь идет о веб‑приложениях).
Всем спасибо за внимание и буду очень рад если кому‑нибудь мое приложение BookDesk будет полезно.
Комментарии (14)
webhamster
30.11.2023 13:47+1Атлант расправил плечи
Как закалялась сталь
Какие разные вкусы у человека!
chervital
30.11.2023 13:47Где у вас фактически хранятся загруженные пользователями книги: локально на устройстве или на vps?
AlexMeshock Автор
30.11.2023 13:47На vps
LeshaRB
30.11.2023 13:47+3А модерирует кто?
Создать книгу , с матовым названием, обложка из фильма для взрослыхА потом это увидят другие пользователи?
Salmoney
30.11.2023 13:47Возможно до модерации это будет видеть только сам добавивший. Но тогда возникают новые вопросы:
Модераторы нужны будут.
Дубликаты добавленных книг.
AlexMeshock Автор
30.11.2023 13:47На дубликаты делается проверка, не даст добавить дубль. По поводу модерации - она сейчас в пост формате, при большом потоке перейдем на премодерацию, пока тестируем это.
aush99
Ну правда не понимаю. 100 пользователей(не одновременных). 200 тыс книг. Как это может грузить проц и память на 90+%?
Еще и такое долгое время запросов.
AlexMeshock Автор
Грузит проц mysql и apache от wordpress сайтов (это я про старый сервер)
aush99
Размер БД? да она вся в памяти должна висеть! Или какие-то меганеоптимальные запросы?
ReaZzon
Человек без опыта разработки бекенда. Надо не такие вопросы задавать, а подсказывать в каком направлении ему двигаться, чтобы снизить нагрузку.
aush99
Ну а как можно давать подсказки не видя реализации? Вон у меня на firebird табличка на ~550млн записей дает ответ почти мгновенно(ну да, сервер побыстрее, но запросов там 1-2 в месяц и CPU+RAM заняты другими БД и запросами). Тут чему так тормозить?
AlexMeshock Автор
Из таблицы 550млн речь идет о каком ответе? Выборка по параметрам?
aush99
Естественно. Выборка по одному-некоторым полям.