Оптимизация извлечения Markdown с помощью асинхронной обработки

Оптимизация извлечения Markdown с помощью асинхронной обработки

1. Обзор

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

Особенно если пользователь выбирает несколько документов, система была спроектирована так, чтобы преобразовать содержимое каждого документа в формате Markdown и передать его на сервер ИИ для последующего анализа или обработки.

На первоначальном этапе реализации функций основное внимание уделялось завершению самой функции, поэтому логика обработки документов была разработана в относительно простой структуре.

То есть, когда поступает запрос, документы обрабатываются по одному последовательно, и запрос передается на сервер ИИ после завершения обработки всех документов.

Функция сама по себе работала нормально, но на практике в операционном окружении начала постепенно выявляться проблема с производительностью.

Проблема особенно усугубилась в следующих ситуациях.

- Когда пользователь запрашивает несколько документов одновременно.

- Это случай, когда размер документа увеличивается.

- Это случай, когда время обработки преобразования в Markdown увеличивается.

- Это случай, когда время ожидания становится длительным на этапе запроса ИИ.

Ключевая проблема заключалась в том, что по мере увеличения числа документов общее время ответа увеличивается линейно.

Например, если обработка одного документа занимает около 500 мс, то при наличии 10 документов общее время обработки увеличивалось бы на несколько секунд из-за простой накопительной структуры.

В частности, работа по извлечению Markdown не была короткой, поскольку в нее входили как операции ввода-вывода, так и обработка данных.

В конечном итоге пользователи испытывали долгие задержки в процессе подготовки документов, а не сами функции ИИ, что привело к ухудшению общего пользовательского опыта.

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

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

В частности, было решено внедрить асинхронную параллельную обработку, которая может одновременно обрабатывать множество независимых задач, чтобы улучшить общий поток обработки.

2. Существующая структура и проблема

Существующая структура была очень типичным последовательным обработкой.

Запросный поток работал следующим образом:

1. Принимаем запрос от клиента.

2. Извлекаем Markdown из первого документа.

3. Извлекаем Markdown из второго документа.

4. Извлекаем Markdown третьего документа.

5. Завершаем обработку всех документов.

6. Запрос к AI серверу.

7. Возвращает ответ на результат.

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

Этот подход имел преимущество в простоте реализации. Кроме того, поток обработки исключений также мог быть относительно интуитивно понятным.

Однако в реальной операционной среде возникли следующие проблемы с производительностью.

Первая проблема заключалась в увеличении времени ответа.

Время обработки увеличивалось практически пропорционально по мере увеличения количества документов.

Например, если обработка одного документа занимает 1 секунду, то получаются следующие результаты.

- Один документ занимает около 1 секунды.

- Пять документов занимают около 5 секунд.

- Обработка 10 документов занимает около 10 секунд.

То есть общее время обработки имело простую накопительную структуру.

Вторая проблема заключалась в неэффективном использовании серверных ресурсов.

Фактические работы по обработке документов были независимыми друг от друга.

Таким образом, не было необходимости ожидать документ B во время обработки документа A.

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

В конечном итоге возникла неэффективная структура, которая не использует достаточно ресурсов CPU и потоков.

Третья проблема заключается в ухудшении пользовательского опыта.

Пользователи столкнулись с ситуацией, когда время ожидания завершения извлечения Markdown казалось дольше, чем само использование функций ИИ.

Особенно, с увеличением числа документов, задержка в ответах UI становилась серьезнее, и некоторые пользователи неверно полагали, что система сама по себе зависла.

Четвертая проблема заключалась в нехватке масштабируемости.

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

Однако существующая структура была очень уязвима к увеличению объема запросов.

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

3. Улучшение структуры на основе асинхронной обработки

Для решения проблемы проект был преобразован с синхронной обработки извлечения Markdown из документов на асинхронную параллельную обработку.

Основная идея была очень проста.

Каждая обработка документа может быть выполнена одновременно, поскольку они независимы друг от друга.

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

Улучшенная структура работала в следующем порядке.

1. Получаем запрос от клиента.

2. Создаем задачу по извлечению Markdown для каждого документа.

3. Выполняйте каждую задачу параллельно.

4. Ожидайте завершения всех задач.

5. Собираем результатные данные.

6. Отправляем запрос на AI сервер.

7. Возвращает ответ.

В проекте была использована функция @Async фреймворка Spring для организации асинхронной структуры выполнения.

Каждый метод обработки документов был спроектирован для выполнения в отдельном асинхронном потоке.

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

Конечно, простое параллельное выполнение было недостаточно.

Самое важное заключалось в том, чтобы "безопасно перейти к следующему этапу после завершения всех задач".

Для этого я использовал CompletableFuture.

Каждая асинхронная задача возвращалась в виде CompletableFuture, а на конечном этапе для точной синхронизации момента завершения всех задач использовался CompletableFuture.allOf().

Благодаря этой структуре мы смогли достичь следующих эффектов.

- Мы можем одновременно обрабатывать независимые задачи.

- Можно сократить общее время обработки.

- Можно повысить использование ресурсов ЦП.

- Можно сократить задержку ответа.

Прежде всего, в прошлый раз время обработки документов просто накапливалось, но после улучшения общее время ответа начало определяться по времени выполнения самой долгой задачи.

Например, если необходимо обработать 10 документов по 1 секунде каждый, то в существующей структуре это занимало около 10 секунд, но в параллельной обработке это время удалось сократить до примерно 1-2 секунд.

4. Применяемые технологии и методы реализации

Ключевыми технологиями, использованными в данном улучшении производительности, стали @Async от Spring и Java CompletableFuture.

Прежде всего, @Async — это функция асинхронной обработки, предоставляемая Spring Framework.

Если вы объявите @Async в конкретном методе, этот метод будет выполняться асинхронно в отдельном потоке.

В проекте мы применили @Async к методу извлечения Markdown для каждого документа.

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

Например, это можно было реализовать в следующем виде.

- вызывается extractMarkdownAsync(document).

- CompletableFuture возвращает.

- Обрабатывается в параллельной структуре.

Но просто использование @Async затрудняло управление общим процессом, так как нужно было точно контролировать время завершения каждой задачи.

Для решения этой проблемы я активно использовал CompletableFuture.

CompletableFuture является объектом, представляющим результат асинхронной задачи, и предоставляет очень мощные функции для комбинирования или синхронизации завершения нескольких задач.

В проекте он использовался следующим образом.

- Создает асинхронные задачи для документов.

- Собирает список CompletableFuture.

- Ожидаем завершения всех с помощью CompletableFuture.allOf().

- Сводим результаты данных.

Особенно CompletableFuture значительно более гибок в управлении потоками, чем простой Future.

Например, можно было легко реализовать такие функции.

- Вы можете подключить последующие действия по завершении.

- Вы можете настроить цепочку обработки исключений.

- Вы можете комбинировать параллельные задачи.

- Вы можете управлять асинхронными потоками.

Также было важно управлять пулом потоков при внедрении параллельной обработки.

Безлимитное параллельное выполнение может, наоборот, увеличить нагрузку на систему.

Таким образом, в проекте была настроена соответствующая ThreadPoolTaskExecutor для ограничения максимального числа одновременных запусков.

Это позволило предотвратить системную нестабильность, вызванную чрезмерной параллельной обработкой.

5. Учетные элементы и операционная надежность

Асинхронная обработка и параллельная обработка предлагают очень сильные преимущества с точки зрения производительности, но одновременно увеличивают операционную сложность.

Таким образом, в проекте рассматривались не только простые улучшения скорости, но и стабильная операционная структура.

Первым рассматриваемым аспектом было управление пулом потоков.

Бездумное увеличение числа параллельных процессов может привести к снижению производительности.

Например, могут возникнуть следующие проблемы.

- Потоки могут быть созданы в избытке.

- Может увеличиться переключение контекста ЦП.

- Может увеличиться использование памяти.

- Может увеличиться нагрузка на сервер.

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

Вторым моментом, который необходимо учитывать, была обработка исключений.

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

Например, может потерпеть неудачу лишь одна из десяти обрабатываемых документов.

В этом случае была необходима политика о том, следует ли обрабатывать весь запрос как неудачный или использовать лишь некоторые успешные результаты.

В проекте была разработана структура, позволяющая минимизировать влияние возникших исключений на общий процесс.

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

Третьим элементом была синхронизация времени завершения.

В параллельной обработке важно, чтобы «последующий этап начинался только после точного завершения всех задач».

Если запрос к серверу ИИ возникает до завершения некоторых задач, это может привести к проблемам с целостностью данных.

Таким образом, в проекте использовалась структура на основе CompletableFuture.allOf() для точного контроля момента полного завершения.

Четвертым аспектом были мониторинг и отслеживаемость.

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

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

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

6. Результаты улучшения и выводы

После введения архитектуры на основе асинхронной обработки в проекте были зафиксированы значительные улучшения в производительности в нескольких аспектах.

Первое, что бросилось в глаза, - это сокращение времени отклика.

В существующей структуре время обработки увеличивалось линейно с увеличением числа документов, но после улучшений благодаря параллельной обработке общее время отклика значительно сократилось.

Особенно эффект улучшения был более заметен при большом количестве документов.

Например, раньше обработка 10 документов занимала больше нескольких секунд, а после параллельной обработки она могла быть завершена за гораздо более короткое время.

Вторым моментом стало увеличение эффективности использования ресурсов сервера.

В существующей структуре последовательная обработка не позволяла полностью использовать ресурсы ЦП и потоков.

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

Третьим было улучшение пользовательского опыта.

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

Четвертым было структурное расширение.

На этом опыте я осознал, что архитектуры асинхронной обработки и параллельной обработки являются не только простыми способами повышения производительности, но и весьма важными проектными подходами с точки зрения дальнейшей масштабируемости.

Мы особенно отметили, что это очень эффективно в таких условиях.

- Это структура, в которой много независимой работы.

- Это структура с высокой долей обработки I/O.

- Это среда, обрабатывающая множество запросов параллельно.

- Структура предварительной обработки для интеграции с ИИ.

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

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

messi

Site footer