Сделать ревью метода поиска товаров по фильтру

21. Сделать ревью метода поиска товаров по фильтру

Условие задачи:
Интернет-магазин обрабатывает около 200 запросов в секунду. Пользователь задаёт фильтры по цене, рейтингу и другим параметрам, после чего сервис ищет подходящие товары.

Примерно в половине запросов подходящих товаров нет. Нужно провести code review метода и определить, как уменьшить лишнюю нагрузку на БД и улучшить API метода.

Код:

class ServiceImpl {

    // 200 RPS
    // 50% -> return new ArrayList<>();
    ArrayList<Product> find(Filter filter) {
        int count = productRepo.count(filter);

        if (count > 0) {
            return productRepo.find(filter);
        }

        return new ArrayList<>();
    }
}

Спойлеры к решению

Подсказки
💡 Предварительный count() здесь не нужен.
💡 Для найденных товаров сейчас выполняются два запроса в БД.
💡 Лучше зависеть от List<Product>, а не от конкретного ArrayList.
💡 Для большого каталога стоит подумать о пагинации и индексах.

Решение

Проблемы:

  • перед поиском выполняется лишний count();

  • для непустого результата получается два запроса: COUNT + SELECT;

  • при 200 RPS и 50% непустых результатов это примерно 300 запросов к БД вместо 200;

  • возвращается конкретный ArrayList<Product> вместо интерфейса List<Product>;

  • вручную создаётся пустой список, хотя поиск сам может вернуть пустой результат;

  • нет проверки filter;

  • при большом количестве результатов отсутствует пагинация;

  • для часто используемых условий фильтрации могут потребоваться подходящие индексы.

Как лучше исправить

Предварительный count() не нужен — сразу выполняем поиск:

class ServiceImpl {

    List<Product> find(Filter filter) {
        if (filter == null) {
            throw new IllegalArgumentException(
                    "Filter must not be null"
            );
        }

        return productRepo.find(filter);
    }
}

Если товаров нет, нормальный контракт repository — вернуть пустую коллекцию.

Для большого каталога лучше добавить пагинацию:

Page<Product> find(
        Filter filter,
        Pageable pageable
) {
    return productRepo.find(filter, pageable);
}

Если общее количество найденных товаров клиенту не требуется, лучше рассмотреть Slice<Product>: тогда не нужен дополнительный запрос для подсчёта общего количества записей.

Также стоит проверить индексы по наиболее селективным и часто используемым полям фильтрации и ограничить максимальный размер страницы.

Главное исправление — не выполнять COUNT перед SELECT, если данные всё равно нужно получить.