ПроектыP03

SaaS для упаковочной отрасли

Full-stack работа над веб- и мобильным SaaS в продакшене для упаковочной отрасли, где растущий объём данных ухудшил отзывчивость для всех клиентов.

Роль
Full-Stack Engineer
Организация
Nagashima Konpo Co., Ltd.
Период
2024/12–2025/121 г. 1 мес.
Статус
Завершён
  1. R1Веб

    Веб-клиент, он же бэкенд для фронтенда (BFF).

    • React
    • Next.js
    1. dev
    2. staging
    3. production
  2. R2Мобильный

    Мобильный клиент.

    • React Native
    • Expo
    1. dev
    2. staging
    3. production
  3. R3Бэкенд

    Специфическая тяжёлая обработка, которой не место в клиенте.

    • Lambda
    1. dev
    2. staging
    3. production

Общие данные

  • AWS AppSync (GraphQL)
  • DynamoDB
  • S3
Три репозитория, у каждого своё приложение Amplify со своими средами dev, staging и production: всего девять развёртываний поверх одного общего слоя данных на GraphQL. Реконструкция по моему собственному описанию, не продакшен-схема.

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

Во-вторых, я бы оценивал влияние изменения на другие компоненты до начала работы, а не во время проверки. Три раздельно развёртываемых репозитория поверх одного слоя данных — повод нарисовать такую карту заранее.