С какими основными техническими проблемами вы столкнулись на последнем проекте?

Ответ

Основные проблемы были связаны с масштабированием и производительностью высоконагруженного сервиса:

  1. Бутылочное горлышко в базе данных (PostgreSQL):

    • Проблема: Высокий RPS (Requests Per Second) приводил к блокировкам (locks) при конкурентных UPSERT-операциях.
    • Решение: Внедрили шардинг по ключу клиента, добавили частичные индексы и пересмотрели уровень изоляции транзакций.
  2. Проблемы с кэшированием (Redis):

    • Проблема: Один инстанс Redis не справлялся с нагрузкой, возникали таймауты.
    • Решение: Перешли на Redis Cluster и добавили двухуровневое кэширование: Redis (распределенный) + Caffeine (in-memory, локальный для каждого экземпляра приложения).
  3. Проблема N+1 в ORM (Hibernate):

    • Проблема: Некоторые API-эндпоинты генерировали десятки запросов к БД из-за ленивой загрузки связанных сущностей.
    • Решение: Использовали JOIN FETCH в JPQL и @EntityGraph для eager-загрузки только необходимых данных за один запрос.

Пример оптимизации запроса N+1:

// БЫЛО: При получении списка пользователей для каждого отдельно выполнялся запрос за его заказами.
@Query("SELECT u FROM User u WHERE u.department = :dept")
List<User> findUsersByDepartment(String dept); // N+1 запрос!

// СТАЛО: Все данные загружаются за один запрос.
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.department = :dept")
List<User> findUsersByDepartmentWithOrders(String dept); // 1 запрос!
  1. Долгие сборки в CI/CD: Решили переходом на Gradle с использованием конфигурационного кэша и репозитория для зависимостей (Nexus).

Ответ 18+ 🔞

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

Ну, во-первых, база данных, наша PostgreSQL-матушка, просто взвыла. Представь: все лезут с этими своими UPSERT-запросами, как пьяные в единственную дверь в час пик. Блокировки, локи, всё это добро — RPS зашкаливает, а база тупо встаёт колом, как будто её током ударило. Решение? Да пошли мы её шардить, блядь! Размазали данные по ключу клиента, как масло по хлебу. Добавили индексы не на всю таблицу, а точечно, как снайпер. И с изоляцией транзакций поигрались — нечего там в Serializable сидеть, если можно Read Committed.

А потом, сука, Redis наш! Один инстанс, бедолага, просто сгорел на работе. Таймауты сыпались, как из дырявого мешка. Мы ему: «Ну что, дружок, не тянешь?». Перевели на кластер, чтоб он не один страдал, а компанией. И сверху, для полного счастья, накатили двухуровневое кэширование: Redis Cluster для всех общих данных, а каждому экземпляру приложения — свой локальный Caffeine, прямо в памяти, чтоб по своим делам не бегать лишний раз. Ёперный театр, а не архитектура!

Но это, блядь, цветочки. А ягодки — это наш Hibernate, который устроил нам N+1 запросов. Ты только вдумайся: вызываешь один API, а он, сука, в ответ генерит не один запрос к базе, а десятки! Как будто за каждым пользователем отдельно посылает курьера за его заказами, вместо того чтобы одной машиной всё привезти. Чистый пиздец, а не эффективность.

Вот смотри, как было и как стало:

// БЫЛО: Каждый пользователь — отдельный поход в базу за заказами. Экономия, блядь, на спичках!
@Query("SELECT u FROM User u WHERE u.department = :dept")
List<User> findUsersByDepartment(String dept); // N+1 запрос! Пиздец, а не логика.

// СТАЛО: Всё за один раз, как умные люди делают. Один запрос — и все довольны.
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.department = :dept")
List<User> findUsersByDepartmentWithOrders(String dept); // 1 запрос! Уже лучше, ёпта.

Использовали JOIN FETCH и @EntityGraph — теперь Hibernate не выёбывается с ленивой загрузкой, а сразу тащит всё, что нужно, одним здоровым куском.

Ну и вишенка на торте — это наши сборки в CI/CD. Ждали мы их, блядь, как второго пришествия. Решили проблему радикально: переползли на Gradle, включили конфигурационный кэш (чтоб каждый раз мир заново не пересобирать) и поставили свой Nexus для зависимостей. Теперь не тянем всякую хуйню из интернета каждый раз, а берём со своей полки. Скорость выросла так, что мы сами от себя офигели.

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