Опишите самую сложную техническую задачу в вашей практике и как вы ее решали.

Ответ

Одной из самых сложных была задача оптимизации времени отклика критического API-метода с 2 секунд до <200 мс под высокой нагрузкой.

Проблема: Метод агрегировал данные из нескольких источников (основная БД, кэш Redis, внешний REST-сервис) и выполнял сложные in-memory вычисления. Профилирование выявило узкие места:

  1. Последовательные (непараллельные) запросы к внешним сервисам.
  2. N+1 запрос к БД в цикле.
  3. Неэффективный алгоритм агрегации данных в памяти (O(n²)).

Решение и предпринятые шаги:

  1. Параллелизация независимых операций: Использование CompletableFuture для одновременного вызова внешнего сервиса и чтения из Redis.

    CompletableFuture<ExternalData> futureExternal = CompletableFuture.supplyAsync(() -> externalService.call());
    CompletableFuture<CacheData> futureCache = CompletableFuture.supplyAsync(() -> cacheService.get());
    CompletableFuture.allOf(futureExternal, futureCache).join();
  2. Исправление N+1: Замена цикла с отдельными запросами на один batch-запрос с WHERE IN (...) или использование JOIN на уровне БД.

  3. Оптимизация алгоритма: Замена вложенных циклов на использование HashMap для поиска за O(1), что снизило сложность с O(n²) до O(n).

  4. Введение многоуровневого кэширования: Добавлен локальный кэш (Caffeine) для часто запрашиваемых и редко меняющихся данных поверх Redis.

Результат: После всех оптимизаций среднее время отклика составило 150 мс, а пропускная способность (throughput) выросла в 10 раз. Задача заняла 3 спринта (6 недель) и потребовала глубокого анализа, работы с профилировщиком (Async Profiler) и тестирования под нагрузкой (JMeter).

Ответ 18+ 🔞

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

В чём была, сука, засада: Этот умник должен был собрать данные отовсюду — из основной базы, из Redis, ещё и внешний сервис потревожить, а потом в памяти какую-то хуйню сложную посчитать. Начали смотреть по логам и профайлером тыкать, а там, мать его, три косяка, один другого краше:

  1. Он всё делал по очереди, как последний тормоз! Сначала к внешнему сервису, потом в Redis, потом ещё куда-то. Сидит и ждёт, пока один запрос упырится, чтобы следующий начать. Ну ёпта, да ты ж на этом одни только таймауты и ловишь!
  2. В базе данных он творил какую-то дичь — N+1 запрос в цикле. Представляешь? Берёт список ID и для каждого, блядь, отдельный запрос шлёт! База просто рыдала в голос.
  3. Ну и вишенка на торте: алгоритм агрегации в памяти был написан через одно место. Два вложенных цикла, сложность O(n²) — на тысяче записей уже начинался трэш и угар.

Что сделали, чтобы не прослыть лузерами:

  1. Параллелим всё, что можно. Зачем ждать-то, если можно одновременно? Завернули вызовы в CompletableFuture и пустили их вразнос.

    CompletableFuture<ExternalData> futureExternal = CompletableFuture.supplyAsync(() -> externalService.call());
    CompletableFuture<CacheData> futureCache = CompletableFuture.supplyAsync(() -> cacheService.get());
    CompletableFuture.allOf(futureExternal, futureCache).join();

    Пока один сервис думает, другой уже ответ несёт. Красота!

  2. Вылечили N+1. Вместо тысячи мелких пинков базе сделали один здоровенный и правильный запрос с WHERE IN (...) или JOIN'ом. База вздохнула с облегчением, а мы сэкономили кучу времени.

  3. Переписали алгоритм с блекджеком и HashMap'ами. Выкинули эти ёбаные вложенные циклы нахуй. Завели мапу, где поиск за O(1), и сложность сразу с O(n²) рухнула до O(n). Волшебство, а не работа!

  4. Добавили кэширования, как будто завтра не наступит. Поверх Redis накрутили ещё и локальный кэш на Caffeine для данных, которые почти не меняются. Чтоб лишний раз даже до сети не ходить.

И что в сухом остатке? После всех этих танцев с бубном средний отклик упал до 150 мс, а пропускная способность выросла, блядь, в 10 раз! Правда, пришлось попотеть — задача растянулась аж на 3 спринта (6 недель), с кучей профилирования через Async Profiler и стрельбы из JMeter'а под нагрузкой. Но оно того стоило, ёпта!