Медленная работа сайта не всегда связана с тяжёлыми изображениями, JavaScript или слабым сервером. Значительная часть задержки может возникать на backend: в запросах к базе данных, обработке бизнес-логики, обращениях к внешним сервисам, формировании ответа и настройке среды выполнения.
На тестовом проекте с несколькими записями почти любой код выглядит быстрым. Настоящие проблемы проявляются после роста базы данных, увеличения числа пользователей, подключения интеграций и появления одновременных запросов.
Производительность backend определяется не только языком или фреймворком. Быстрое приложение можно построить на PHP, Python, Node.js, Java или другой подходящей технологии. Медленным его делают неудачные запросы, блокирующие операции, отсутствие ограничений, неправильное кэширование и эксплуатация без измерений.
Ниже рассмотрены десять распространённых ошибок, которые снижают скорость сайта и усложняют масштабирование.
Как измерять скорость backend
До поиска ошибок необходимо определить, что именно считается медленным. Одного среднего времени ответа недостаточно.
Полезно отслеживать:
- время ответа сервера;
- медианное значение;
- 95-й и 99-й процентили;
- количество запросов в секунду;
- число ошибок;
- время выполнения SQL-запросов;
- нагрузку на процессор;
- использование памяти;
- количество занятых соединений с базой данных;
- длительность внешних запросов;
- размер ответа;
- длину очередей фоновых задач.
Среднее значение может выглядеть приемлемо, даже если часть пользователей ждёт ответ несколько секунд. Поэтому важно анализировать распределение времени, а не одну удобную цифру, выбранную для отчёта.
Ошибка 1. N+1-запросы к базе данных
N+1 возникает, когда приложение сначала получает список записей одним запросом, а затем отдельно загружает связанные данные для каждой записи.
Например, приложение получает сто заказов, после чего выполняет отдельный запрос для получения клиента каждого заказа. Вместо одного или нескольких заранее спроектированных запросов база обрабатывает десятки или сотни обращений.
Почему N+1 замедляет приложение
- увеличивается количество обращений к базе данных;
- растёт суммарное время сетевого обмена;
- база повторно анализирует похожие запросы;
- увеличивается нагрузка на пул соединений;
- возрастает время формирования страницы или API-ответа;
- проблема усиливается вместе с количеством записей.
Пример проблемы
$orders = Order::all();
foreach ($orders as $order) {
echo $order->customer->name;
} Если связь customer не была загружена заранее, ORM может выполнить отдельный запрос для каждого заказа.
Как исправить N+1
- использовать eager loading;
- загружать только необходимые связи;
- применять JOIN, когда это соответствует задаче;
- использовать агрегирующие запросы вместо циклов;
- проверять количество SQL-запросов в профайлере;
- добавлять автоматические проверки для критичных сценариев.
$orders = Order::with('customer')->get(); Жадная загрузка также не должна применяться бездумно. Если сразу загрузить десятки тяжёлых связей, приложение избавится от N+1, но создаст огромный запрос и получит больше данных, чем требуется.
На что смотреть при проверке
- сколько запросов выполняется на один HTTP-запрос;
- повторяются ли одинаковые SQL-выражения;
- меняется ли число запросов вместе с количеством элементов;
- какие связи загружаются внутри циклов;
- используются ли запросы в шаблонах представления.
Ошибка 2. Отсутствующие или неправильно составленные индексы
Индекс помогает базе данных быстрее находить строки без полного просмотра таблицы. При росте объёма данных отсутствие подходящего индекса становится одной из основных причин замедления.
Индексы особенно важны для полей, которые участвуют в:
- условиях
WHERE; - соединениях
JOIN; - сортировке
ORDER BY; - группировке;
- проверке уникальности;
- поиске по внешнему ключу.
Почему индекс может не использоваться
- индекса на нужное поле нет;
- порядок полей составного индекса не соответствует запросу;
- к индексированному столбцу применяется функция;
- типы сравниваемых полей различаются;
- условие имеет слишком низкую избирательность;
- запрос возвращает значительную часть таблицы;
- статистика базы данных устарела;
- условие построено так, что оптимизатор не может применить индекс эффективно.
Пример потенциально проблемного условия
WHERE DATE(created_at) = '2026-08-01' Функция над столбцом может мешать эффективному использованию обычного индекса. Часто лучше использовать диапазон:
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-08-02 00:00:00' Как проверять запросы
Используйте план выполнения:
EXPLAIN SELECT ...; Для фактического выполнения в поддерживаемых СУБД применяется EXPLAIN ANALYZE. Он помогает увидеть:
- какой способ доступа выбран;
- сколько строк просматривается;
- какие индексы используются;
- где выполняется сортировка;
- на каком этапе тратится время;
- насколько оценки оптимизатора совпадают с реальностью.
Почему нельзя индексировать всё
Каждый индекс занимает место и требует обновления при вставке, изменении и удалении данных. Избыточные индексы увеличивают стоимость записи и усложняют обслуживание базы.
Индекс создаётся под реальные запросы и проверяется по плану выполнения. Добавление индекса «на всякий случай» является не оптимизацией, а переносом проблемы в другое место.
Ошибка 3. Длинные транзакции и блокировки
Транзакция должна охватывать минимальный набор операций, которые необходимо выполнить атомарно. Если внутри транзакции приложение делает внешние запросы, отправляет письма, формирует файлы или ждёт ввода пользователя, блокировки удерживаются слишком долго.
Чем опасны длинные транзакции
- другие запросы ждут освобождения строк или таблиц;
- возрастает вероятность взаимных блокировок;
- увеличивается время ответа;
- занимаются соединения с базой данных;
- накопление ожиданий может вызвать каскадное замедление;
- повторные запросы пользователей усиливают нагрузку.
Плохая последовательность
- Начать транзакцию.
- Изменить заказ.
- Обратиться к внешнему платёжному API.
- Сформировать документ.
- Отправить письмо.
- Завершить транзакцию.
Вся внешняя работа выполняется, пока база удерживает блокировки.
Более безопасный подход
- Получить и проверить внешние данные до транзакции, если это допустимо.
- Открыть транзакцию.
- Выполнить необходимые изменения в базе.
- Зафиксировать транзакцию.
- Передать письмо, уведомление или создание документа в очередь.
Что проверять
- длительность транзакций;
- запросы, ожидающие блокировки;
- повторяющиеся deadlock;
- объём изменяемых строк;
- внешние вызовы внутри транзакций;
- слишком высокий уровень изоляции;
- порядок обновления таблиц в разных сценариях.
Ошибка 4. Отсутствие кэширования или неправильный кэш
Если приложение многократно вычисляет одинаковый результат или выполняет один тяжёлый запрос для каждого посетителя, кэширование может заметно снизить нагрузку.
Кэшировать можно:
- результаты тяжёлых запросов;
- справочники;
- настройки;
- готовые фрагменты страниц;
- полные HTML-страницы;
- ответы API;
- результаты внешних интеграций;
- сериализованные данные;
- результаты ресурсоёмких вычислений.
Почему отсутствие кэша замедляет backend
Без кэширования каждый запрос повторно обращается к базе, внешней системе или вычислительному модулю. При большом числе одинаковых обращений сервер тратит ресурсы на получение результата, который уже недавно был рассчитан.
Ошибки самого кэширования
- нет стратегии инвалидирования;
- ключи не учитывают пользователя, язык или регион;
- TTL выбран случайно;
- кэшируются персональные данные без изоляции;
- одновременно истекает большое количество ключей;
- после очистки тысячи запросов одновременно пересчитывают значение;
- кэш становится обязательной частью логики, но его отказ не обработан;
- хранятся слишком крупные объекты;
- кэшируется быстро изменяющаяся информация без допустимой задержки.
Проблема cache stampede
Если популярный ключ истёк, множество запросов могут одновременно начать пересчёт одного значения. Для защиты применяются:
- блокировка пересчёта;
- раннее обновление;
- небольшой случайный разброс TTL;
- хранение устаревшего значения до завершения пересчёта;
- фоновое обновление.
Когда кэш не нужен
Не следует кэшировать операцию только потому, что в проекте установлен Redis. Если запрос быстрый, редко выполняется или данные должны быть строго актуальными, дополнительный слой может лишь усложнить систему.
Кэширование должно решать измеренную проблему и иметь понятную стратегию обновления.
Ошибка 5. Тяжёлые операции выполняются внутри HTTP-запроса
Пользователь не должен ждать завершения работы, результат которой не требуется для формирования текущего ответа.
К таким задачам относятся:
- отправка email;
- генерация PDF;
- обработка изображений;
- массовый импорт;
- экспорт отчёта;
- синхронизация с CRM;
- отправка уведомлений;
- пересчёт статистики;
- индексация данных;
- обработка видео;
- создание резервной копии.
Что происходит без очереди
Обработчик последовательно выполняет бизнес-операцию, обращается к внешнему сервису, формирует файл и отправляет уведомление. Всё это время HTTP-соединение остаётся открытым, а рабочий процесс занят.
Как использовать фоновые задачи
- Принять и проверить запрос.
- Сохранить необходимые данные.
- Создать задание в очереди.
- Вернуть пользователю подтверждение.
- Выполнить длительную работу отдельным worker.
- Сохранить результат и уведомить пользователя.
Что требуется от очереди
- ограничение числа попыток;
- интервалы повторного запуска;
- тайм-аут;
- журналирование ошибок;
- идемпотентность;
- очередь неудачных заданий;
- мониторинг длины очереди;
- контроль времени ожидания;
- разделение быстрых и тяжёлых задач.
Почему просто использовать async недостаточно
Асинхронное выполнение не уменьшает объём вычислений. CPU-зависимая задача продолжит нагружать процессор и может блокировать event loop или рабочий процесс. Для неё может потребоваться отдельный worker, процесс или специализированный сервис.
Ошибка 6. Отсутствие лимитов и пагинации
Запрос «вернуть все записи» выглядит безобидно, пока таблица содержит несколько десятков строк. После роста базы тот же endpoint начинает загружать тысячи объектов, занимать память и формировать огромный JSON.
Какие проблемы возникают
- база читает слишком много строк;
- ORM создаёт большое количество объектов;
- сервер расходует память;
- сериализация занимает процессорное время;
- ответ медленно передаётся по сети;
- клиент долго разбирает JSON;
- пользователь всё равно видит только небольшую часть результата.
Обычная пагинация
GET /api/articles?page=2&limit=20 Для небольших и средних наборов данных используется LIMIT и OFFSET. Однако глубокие страницы могут становиться медленными, поскольку база должна пропустить большое количество строк.
Курсорная пагинация
Для больших и постоянно меняющихся списков применяется курсор:
GET /api/articles?after=10500&limit=20 Курсор должен опираться на стабильное и индексированное поле, например идентификатор или сочетание даты и идентификатора.
Обязательные ограничения
- значение по умолчанию;
- максимальный размер страницы;
- проверка входных параметров;
- стабильная сортировка;
- индекс под сортировку и фильтрацию;
- ограничение диапазона дат;
- ограничение сложности фильтров.
Нельзя позволять клиенту передать limit=1000000 только потому, что разработчик проявил безграничное доверие к человечеству.
Ошибка 7. Внешние сервисы вызываются без тайм-аутов и защиты
Backend часто обращается к платёжным системам, CRM, службам доставки, картам, email-провайдерам и другим API. Если внешний сервис отвечает медленно, задержка передаётся пользователю.
Опасные ошибки
- тайм-аут не установлен;
- повтор выполняется бесконечно;
- повторяются запросы, которые нельзя безопасно дублировать;
- не используется повторное соединение;
- ошибка внешнего сервиса останавливает весь сайт;
- нет кэширования справочных данных;
- нет ограничителя частоты;
- нет мониторинга времени ответа;
- все интеграции выполняются последовательно.
Тайм-ауты
Следует отдельно задавать:
- время установки соединения;
- время ожидания ответа;
- общий предел выполнения;
- тайм-аут фонового задания.
Повторные запросы
Повтор допустим только для временных ошибок и идемпотентных операций. Между попытками применяется увеличивающаяся задержка с небольшим случайным отклонением.
Нельзя автоматически повторять создание платежа или заказа без идентификатора идемпотентности. Иначе одна задержка внешнего API способна породить несколько одинаковых операций, а бухгалтерия получит новый источник развлечений.
Circuit breaker
Если сервис регулярно недоступен, приложение временно прекращает обращения и быстро возвращает контролируемую ошибку или резервный результат. Это предотвращает накопление зависших запросов.
Параллельные вызовы
Если несколько независимых данных нужны одновременно, их можно получать параллельно. Но количество параллельных запросов необходимо ограничивать, чтобы не перегрузить собственный и внешний сервис.
Ошибка 8. Backend возвращает слишком много данных
Даже быстрый запрос к базе может завершиться медленным ответом, если приложение выбирает, преобразует и передаёт лишние поля.
Типичные примеры
- использование
SELECT *; - загрузка больших текстов для списка карточек;
- передача всех связей модели;
- возврат внутренних служебных полей;
- многократное вложение одинаковых объектов;
- формирование огромного дерева JSON;
- сериализация файлов в base64;
- отсутствие сжатия ответа;
- возврат полной модели вместо подготовленного DTO.
Выбирайте только необходимые поля
SELECT
id,
title,
slug,
published_at
FROM articles
WHERE status = 'published'
ORDER BY published_at DESC
LIMIT 20; Для списка статей не требуется загружать полный текст, историю изменений и все связанные сущности.
Разделяйте представления
Один объект может иметь разные представления:
- краткое для списка;
- полное для отдельной страницы;
- служебное для административной панели;
- публичное для API;
- экспортное для отчёта.
Контролируйте вложенность
Если заказ включает клиента, клиент включает все заказы, а каждый заказ снова включает клиента, сериализатор может построить огромную или циклическую структуру.
Сжатие ответа
Текстовые ответы можно передавать со сжатием на уровне веб-сервера или приложения. Однако сначала следует убрать лишние данные. Сжимать многомегабайтный ненужный JSON является улучшенной транспортировкой всё той же архитектурной ошибки.
Ошибка 9. Отсутствие профилирования, метрик и нагрузочных тестов
Оптимизация без измерений приводит к исправлению участков, которые не являются причиной задержки.
Что даёт профилирование
Профилировщик показывает:
- какие функции занимают больше времени;
- сколько раз вызывается метод;
- где расходуется память;
- какие запросы выполняются;
- сколько времени занимают внешние обращения;
- какие операции блокируют обработку.
Что нужно измерять в рабочей среде
- время ответа по маршрутам;
- частоту ошибок;
- p95 и p99;
- медленные SQL-запросы;
- использование пула соединений;
- время внешних интеграций;
- нагрузку на CPU;
- потребление памяти;
- перезапуски процессов;
- длину очередей;
- процент попаданий в кэш.
Нагрузочное тестирование
Нагрузочный тест проверяет поведение системы при ожидаемом и повышенном количестве запросов.
Сценарий должен воспроизводить реальные действия:
- открытие страниц;
- авторизацию;
- поиск;
- фильтрацию;
- добавление в корзину;
- оформление заказа;
- отправку формы;
- работу API;
- массовый импорт;
- выполнение фоновых задач.
Почему тест одного endpoint недостаточен
Реальные пользователи одновременно открывают разные страницы, выполняют чтение и запись, запускают интеграции и создают фоновые задания. Изолированный тест одного быстрого маршрута не показывает поведение всей системы.
Тестируйте с реалистичными данными
Запрос к таблице из ста строк не показывает, как он будет работать на миллионе записей. Тестовая база должна воспроизводить объём, распределение и связи рабочих данных без использования реальной конфиденциальной информации.
Ошибка 10. Неправильная настройка рабочей среды
Даже хорошо написанное приложение может работать медленно из-за конфигурации runtime, веб-сервера, процессов и соединений.
Режим отладки включён на рабочем сайте
Debug-режим может собирать дополнительную информацию, подробные трассировки, панели запросов и служебные данные. В production он должен быть отключён.
Не включён кэш байткода
Для PHP-приложений в рабочей среде обычно используется OPcache. Без него PHP повторно разбирает и компилирует файлы при выполнении запросов.
Неподходящее количество workers
Слишком мало процессов создаёт очередь запросов. Слишком много приводит к конкуренции за память, процессор и соединения с базой.
Количество workers подбирается по:
- числу ядер;
- объёму памяти;
- характеру нагрузки;
- среднему потреблению процесса;
- длительности запросов;
- числу соединений базы данных.
Неправильный пул соединений
Если каждый процесс создаёт слишком много соединений, база может исчерпать лимит. Если соединений мало, запросы будут ждать свободного слота.
Отсутствие keep-alive
Повторное использование соединений уменьшает расходы на установку новых подключений к базе и внешним сервисам.
Не настроен reverse proxy
Nginx или другой reverse proxy может обслуживать статические файлы, завершать TLS, применять сжатие, ограничивать запросы и не заставлять приложение выполнять несвойственную ему работу.
Логи блокируют обработку
Синхронная запись большого количества подробных логов может замедлять приложение. Логи должны иметь разумный уровень, структуру, ротацию и неблокирующую доставку там, где это требуется.
Контейнеру не хватает ресурсов
Ограничения CPU и памяти способны вызывать throttling, завершение процессов и нестабильное время ответа. Мониторинг должен учитывать не только сервер целиком, но и конкретные контейнеры или сервисы.
Почему преждевременная оптимизация тоже опасна
Оптимизировать нужно измеренное узкое место. Попытка заранее предусмотреть любую возможную нагрузку приводит к сложному коду, лишним сервисам и дорогой инфраструктуре.
Типичные последствия преждевременной оптимизации:
- микросервисы для проекта, который мог быть монолитом;
- несколько уровней кэша без стратегии инвалидирования;
- асинхронная архитектура для простых операций;
- ручные SQL-оптимизации без подтверждённой проблемы;
- сложные структуры данных ради несуществующей нагрузки;
- ухудшение читаемости и тестируемости кода.
Правильная последовательность:
- Определить требование к скорости.
- Измерить текущее состояние.
- Найти узкое место.
- Сформулировать изменение.
- Проверить корректность.
- Повторить измерение.
- Задокументировать результат.
Как искать причину медленной работы пошагово
Шаг 1. Определите медленный маршрут
Нужно знать конкретную страницу, endpoint, команду или фоновую задачу. Формулировка «сайт тормозит» технически бесполезна, хотя эмоционально безупречна.
Шаг 2. Разделите время выполнения
Определите, сколько занимает:
- ожидание в очереди;
- выполнение приложения;
- SQL;
- обращение к кэшу;
- внешний API;
- сериализация;
- передача ответа.
Шаг 3. Проверьте SQL
Изучите количество запросов, медленные операции, планы выполнения, блокировки и индексы.
Шаг 4. Проверьте внешние зависимости
Отдельно измерьте CRM, платёжную систему, email, файловое хранилище и другие сервисы.
Шаг 5. Проверьте объём данных
Сравните число полученных строк, объектов ORM и размер ответа с тем, что действительно требуется клиенту.
Шаг 6. Проверьте нагрузку
Маршрут может быть быстрым при одном запросе и деградировать при одновременной работе пользователей.
Шаг 7. Проверьте конфигурацию
Изучите workers, пул соединений, лимиты памяти, OPcache, контейнеры и reverse proxy.
Шаг 8. Исправьте одно узкое место
Одновременное изменение запросов, кэша и конфигурации не позволит определить, какое решение дало результат.
Таблица диагностики backend-производительности
| Проблема | Признак | Проверка | Решение |
|---|---|---|---|
| N+1 | Количество SQL растёт вместе со списком | Лог запросов и профайлер | Eager loading, JOIN или агрегирование |
| Нет индекса | Полное сканирование большой таблицы | EXPLAIN | Индекс под реальный запрос |
| Блокировки | Нестабильные задержки записи | Мониторинг транзакций | Сокращение транзакции и единый порядок обновлений |
| Нет кэша | Повторяется тяжёлое вычисление | Профилирование и метрики | Кэш с понятной инвалидизацией |
| Синхронная тяжёлая задача | Пользователь ждёт email, PDF или импорт | Трассировка запроса | Очередь и фоновые workers |
| Нет лимитов | Большой объём памяти и ответа | Размер выборки и JSON | Пагинация и максимальный limit |
| Медленный внешний API | Задержка зависит от интеграции | Тайминги внешних вызовов | Тайм-ауты, retries, circuit breaker |
| Лишние данные | Большой ответ при простой странице | Размер payload и сериализация | DTO и выбор нужных полей |
| Нет наблюдаемости | Причина задержки неизвестна | Метрики, tracing и profiling | Мониторинг и нагрузочные тесты |
| Плохая конфигурация | Код быстрый локально, но медленный в production | Workers, pools, CPU и память | Настройка runtime и инфраструктуры |
Чек-лист проверки backend
- Определены требования к времени ответа.
- Измеряются p50, p95 и p99.
- Включён сбор медленных SQL-запросов.
- Проверено количество запросов на страницу.
- Исключены N+1.
- Планы ключевых запросов изучены через EXPLAIN.
- Индексы соответствуют фильтрации и сортировке.
- Транзакции не содержат внешние вызовы.
- Отслеживаются блокировки и deadlock.
- Кэш используется только для измеренных задач.
- Определена стратегия инвалидирования.
- Защищён пересчёт популярных ключей.
- Тяжёлые задачи вынесены в очередь.
- Задания имеют тайм-аут и лимит попыток.
- Все списки имеют ограничения.
- Для больших таблиц рассмотрена курсорная пагинация.
- Внешние запросы имеют тайм-аут.
- Повторы выполняются безопасно.
- Ответы API не содержат лишние поля.
- Контролируется размер JSON.
- Настроены метрики приложения и инфраструктуры.
- Проводятся нагрузочные тесты.
- Тестовые данные сопоставимы по объёму с рабочими.
- Debug-режим отключён в production.
- Для PHP настроен OPcache.
- Количество workers соответствует ресурсам.
- Пул соединений не превышает возможности базы.
- Настроены ротация и уровень логирования.
- После оптимизации выполняется повторное измерение.
Вывод
Скорость backend складывается из множества решений: структуры SQL-запросов, индексов, транзакций, кэша, очередей, интеграций, объёма ответа и конфигурации рабочей среды.
Наиболее опасны проблемы, которые незаметны на небольшом объёме данных. N+1, отсутствие пагинации, длинные транзакции и лишняя сериализация могут почти не влиять на тестовый проект, но резко ухудшать работу после роста нагрузки.
Оптимизация должна начинаться с измерения. Сначала определяется медленный сценарий, затем время разделяется между базой, приложением, внешними сервисами и передачей ответа. После этого исправляется конкретное узкое место и повторяется проверка.
Быстрый backend — это не результат выбора модного языка и не случайное свойство хорошего сервера. Это следствие контролируемой архитектуры, ограничений, наблюдаемости и регулярной проверки системы под реалистичной нагрузкой.
Частые вопросы
Как быстро найти узкое место в backend?
Начните с конкретного медленного маршрута и разделите время между приложением, SQL-запросами, внешними сервисами, кэшем и сериализацией. Используйте профилировщик, журнал медленных запросов и метрики времени ответа.
Как определить N+1-запросы?
Проверьте журнал SQL или профилировщик. Если количество похожих запросов растёт вместе с числом элементов в списке, вероятно, связанные данные загружаются внутри цикла. Исправлением может быть eager loading, JOIN или отдельный агрегирующий запрос.
Нужно ли добавлять индекс на каждое поле поиска?
Нет. Индекс создаётся под реальные условия фильтрации, соединения и сортировки. Избыточные индексы занимают место и замедляют запись. Решение следует проверять по плану выполнения запроса.
Нужно ли всегда использовать Redis и кэширование?
Нет. Кэш нужен для повторяющихся и достаточно дорогих операций, если допустима задержка обновления данных. Перед внедрением необходимо определить ключ, TTL, правила инвалидирования и поведение при отказе хранилища.
Какие задачи нужно выносить в очередь?
В очередь выносят операции, результат которых не требуется для текущего HTTP-ответа: отправку писем, генерацию документов, обработку изображений, импорт, синхронизацию и уведомления. Задания должны иметь тайм-аут, ограничение попыток и идемпотентность.
Почему приложение быстро работает локально, но медленно на сервере?
Причиной могут быть другой объём данных, слабые индексы, сетевые задержки, внешние сервисы, недостаток workers, неправильный пул соединений, ограничения CPU и памяти, выключенный OPcache или отличающаяся конфигурация production.
Какие показатели backend важнее среднего времени ответа?
Кроме среднего значения следует контролировать медиану, p95, p99, частоту ошибок, пропускную способность, время SQL-запросов, внешних вызовов, длину очередей и использование ресурсов. Они лучше показывают проблемы части пользователей и поведение под нагрузкой.
С чего начинать оптимизацию медленного сайта?
С измерения конкретного сценария. Необходимо определить, где тратится время, исправить самое значимое узкое место и повторить тест. Преждевременная оптимизация без данных усложняет код и не гарантирует заметного результата.