SaaS для упаковочной отрасли
Full-stack работа над веб- и мобильным SaaS в продакшене для упаковочной отрасли, где растущий объём данных ухудшил отзывчивость для всех клиентов.
Веб-клиент, он же бэкенд для фронтенда (BFF).
- React
- Next.js
- dev
- staging
- production
Мобильный клиент.
- React Native
- Expo
- dev
- staging
- production
Специфическая тяжёлая обработка, которой не место в клиенте.
- Lambda
- dev
- staging
- production
00 / Обзор
Контракт с декабря 2024 по декабрь 2025 года: SaaS в продакшене для упаковочной отрасли, с веб- и мобильным клиентом. По текущему CV, во время этой работы продукт использовали более 10 японских компаний; команда проекта — около 10 человек.
Моей основной задачей было вернуть отзывчивость после того, как растущий объём данных замедлил продукт для всех клиентов. Параллельно я перевёл среду выполнения Node.js с версии 16 на 20 и помогал внешним разработчикам входить в кодовую базу.
01 / Проблема
Продукт перерос предпосылки, на которых его строили. По мере накопления данных время загрузки и общая отзывчивость ухудшались по всей платформе — не у одного клиента и не на одном экране, а у всех, кто ею пользовался.
Причина лежала глубже симптомов. Структуру базы данных собрали быстро, ещё до того, как продукт распространили на другие компании, и её документация устарела. У команды не было общего точного представления о том, как ведёт себя существующая модель данных, поэтому менять её безопасно можно было только после того, как станет понятно, что она уже делает.
02 / Ограничения
- Живые клиенты. Компании-клиенты зависели от системы: изменения должны были попадать в продакшен, не нарушая их работу.
- Раздельные развёртывания. Веб-клиент, мобильный клиент и бэкенд жили в отдельных репозиториях, у каждого своё приложение Amplify и свои среды dev, staging и production. Изменение общих данных должно было оставаться согласованным во всех трёх.
- Нет надёжной карты. Документация устарела, поэтому единственными источниками истины оставались код и работающая система.
03 / Диагностика
Картину я восстанавливал по фактам, а не по документации: читал код, профилировал запросы, смотрел поведение запросов в Amazon CloudWatch. Там, где код читался тяжело, я рисовал поток данных схемами, прежде чем что-то менять.
Замедление было не одним дефектом, а набором паттернов, которые усиливают друг друга по мере роста данных:
- Операции Scan в DynamoDB, которые читают таблицу целиком, там, где хватило бы Query по ключу.
- Отсутствующие глобальные вторичные индексы (GSI) — у частых паттернов доступа не было эффективного пути.
- Ключи партиционирования и сортировки, не совпадавшие с тем, как данные читались на самом деле.
- Паттерн N+1 в резолверах AppSync: один запрос за списком и ещё по одному на каждый элемент.
- Загрузка полных наборов данных на клиент вместо того, что нужно экрану.
- Отсутствие пагинации.
По отдельности каждое терпимо на маленькой таблице и с одним клиентом. Вместе, при растущем числе компаний, они замедляли всю платформу.
04 / Изменения
- Индексы под те запросы, которые реально выполняет поиск, и паттерны доступа под то, как продукт читает свои данные.
- Пагинация: экраны запрашивают только то, что показывают.
- Отдельный механизм поиска.
- Кеширование.
- Правки резолверов, убирающие паттерн N+1.
- Удаление запросов, которые клиентам вообще не нужно было делать.
- Миграция таблицы заказов — записи, которая ведёт заказ на упаковку через весь его путь, — на новую структуру.
- Работа с таймаутами на путях экспорта больших объёмов данных — там старая структура отказывала заметнее всего.
Закончив, я обновил документацию модели данных, чтобы следующее изменение не приходилось снова начинать с реверс-инжиниринга.
05 / Процесс работы
Изменения выходили серией этапов. Каждый этап шаг за шагом проверяли на staging, параллельно инженеры команды делали код-ревью. Релизы в продакшен шли ночью, вне часов, когда системой пользовались клиенты.
Миграция Node.js 16 → 20 упёрлась в проблемы с зависимостями и в среду выполнения Lambda. Я проверял её автоматическими тестами и вручную — по заранее заданному сценарию: завести тенант (одно упаковочное предприятие со своими заказами внутри) и дальше пользоваться SaaS так, как это делала бы эта компания.
Я также помогал внешним подрядчикам входить в проект: ревью кода, онбординг, документация, разбор первых задач вместе с ними.
Производительностью работа не ограничивалась. Продукт ведёт заказ на упаковку от начала до конца: заказ заводят, считают смету, проектируют упаковку, упаковывают, выставляют счёт, планируют размещение в контейнере и грузят в машину. Я делал и дорабатывал функции по всей этой цепочке — перезагрузку расписания, сценарии сметы, счёта и упаковки, автоматические переходы между экранами, уведомления, — чинил проблемы с мобильной вёрсткой и навигацией по спискам и проводил PoC по дальнейшему переносу продукта на мобильные устройства.
06 / Результат
После изменений таймаутов в поиске стало меньше, а жалоб клиентов — существенно меньше. Продукт и его распространение — работа всей команды примерно из 10 человек; мой вклад — инженерная работа, описанная выше.
07 / Ретроспектива
Два пункта, которые я бы сделал иначе.
Во-первых, для каждого изменения я бы заранее планировал, как вернуть продакшен в прежнее состояние, если релиз пойдёт не так. Прохождение через dev, staging и production снижает риск, но не гарантирует, что релиз в продакшен пройдёт успешно, а миграция данных — ровно тот случай, где этот разрыв бьёт больнее всего.
Во-вторых, я бы оценивал влияние изменения на другие компоненты до начала работы, а не во время проверки. Три раздельно развёртываемых репозитория поверх одного слоя данных — повод нарисовать такую карту заранее.