1. 개요
문서 관리 시스템에는 여러 문서의 데이터를 추출하고, 이를 기반으로 추가적인 AI 처리를 수행하는 기능이 존재했습니다.
특히 사용자가 다수의 문서를 선택하면 시스템은 각 문서의 내용을 Markdown 형태로 변환한 뒤 AI 서버로 전달하여 후속 분석이나 가공 작업을 수행하도록 설계되어 있었습니다.
초기 기능 구현 단계에서는 기능 자체를 우선적으로 완성하는 데 초점을 맞추었기 때문에 문서 처리 로직은 비교적 단순한 구조로 개발되었습니다.
즉, 요청이 들어오면 문서를 하나씩 순차적으로 처리하고, 모든 문서 처리가 완료된 이후 AI 서버로 요청을 전달하는 방식이었습니다.
기능 자체는 정상적으로 동작했지만, 실제 운영 환경에서는 성능 문제가 점차 드러나기 시작했습니다.
특히 다음과 같은 상황에서 문제가 심화되었습니다.
- 사용자가 여러 개의 문서를 동시에 요청하는 경우입니다.
- 문서 크기가 커지는 경우입니다.
- Markdown 변환 작업 시간이 길어지는 경우입니다.
- AI 요청 이전 단계에서 대기 시간이 길어지는 경우입니다.
문제의 핵심은 문서 개수가 증가할수록 전체 응답 시간이 선형적으로 증가한다는 점이었습니다.
예를 들어 하나의 문서를 처리하는 데 약 500ms가 소요된다면, 문서가 10개일 경우 전체 처리 시간이 단순 누적으로 인해 수 초 이상 증가하는 구조였습니다.
특히 Markdown 추출 작업은 I/O 및 데이터 처리 작업이 함께 포함되어 있었기 때문에 처리 시간이 결코 짧지 않았습니다.
결국 사용자는 AI 기능 자체보다 “문서 준비 과정”에서 긴 대기 시간을 경험하게 되었고, 이는 전체 사용자 경험 저하로 이어졌습니다.
또한 서버 측면에서도 하나의 요청이 장시간 점유되면서 리소스 활용 효율이 떨어지는 문제가 발생했습니다.
이에 따라 프로젝트에서는 단순 기능 구현 수준을 넘어 구조적인 성능 개선이 필요하다고 판단했습니다.
특히 다수의 독립 작업을 동시에 처리할 수 있는 비동기 기반 병렬 처리 구조를 도입하여 전체 처리 흐름을 개선하기로 결정했습니다.
2. 기존 구조와 문제 상황
기존 구조는 매우 전형적인 순차 처리 기반 방식이었습니다.
요청 흐름은 다음과 같이 동작했습니다.
1. 클라이언트 요청을 수신합니다.
2. 첫 번째 문서의 Markdown을 추출합니다.
3. 두 번째 문서의 Markdown을 추출합니다.
4. 세 번째 문서의 Markdown을 추출합니다.
5. 모든 문서 처리를 완료합니다.
6. AI 서버에 요청합니다.
7. 결과 응답을 반환합니다.
즉, 하나의 문서 처리가 끝난 이후 다음 문서를 처리하는 구조였습니다.
이 방식은 구현이 단순하다는 장점이 있었습니다. 또한 예외 처리 흐름 역시 비교적 직관적으로 구성할 수 있었습니다.
하지만 실제 운영 환경에서는 다음과 같은 성능 문제가 발생했습니다.
첫 번째 문제는 응답 시간 증가였습니다.
문서 수가 증가할수록 처리 시간이 거의 비례하여 증가했습니다.
예를 들어 문서 하나 처리에 1초가 걸리는 경우 다음과 같은 결과가 나타났습니다.
- 문서 1개는 약 1초가 소요됩니다.
- 문서 5개는 약 5초가 소요됩니다.
- 문서 10개는 약 10초가 소요됩니다.
즉, 전체 처리 시간이 단순 누적되는 구조였습니다.
두 번째 문제는 서버 자원 활용 비효율이었습니다.
실제 문서 처리 작업은 서로 독립적이었습니다.
즉, 문서 A를 처리하는 동안 문서 B를 기다릴 이유가 없었습니다.
하지만 기존 구조에서는 단일 스레드 흐름 안에서 모든 작업이 순차적으로 실행되었습니다.
결국 CPU와 스레드 자원을 충분히 활용하지 못하는 비효율적인 구조가 발생했습니다.
세 번째 문제는 사용자 경험 저하였습니다.
사용자는 AI 기능 자체보다 Markdown 추출 완료를 기다리는 시간이 더 길게 느껴지는 상황을 경험했습니다.
특히 문서 개수가 많아질수록 UI 응답 지연이 심해졌고, 일부 사용자는 시스템 자체가 멈춘 것으로 오해하는 경우도 있었습니다.
네 번째 문제는 확장성 부족이었습니다.
프로젝트가 성장할수록 처리 대상 문서 수는 계속 증가할 가능성이 높았습니다.
하지만 기존 구조는 요청량 증가에 매우 취약한 형태였습니다.
결국 단순 기능 구현 수준을 넘어 구조 자체를 병렬 처리 기반으로 전환할 필요성이 명확해졌습니다.
3. 비동기 처리 기반 구조 개선
문제 해결을 위해 프로젝트에서는 문서별 Markdown 추출 작업을 비동기 기반 병렬 처리 구조로 전환했습니다.
핵심 아이디어는 매우 단순했습니다.
“각 문서 처리는 서로 독립적이므로 동시에 처리할 수 있습니다”라는 점이었습니다.
즉, 기존처럼 하나의 요청 흐름에서 순차적으로 처리하는 것이 아니라, 각 문서 처리 작업을 개별 비동기 작업으로 분리했습니다.
개선된 구조는 다음과 같은 흐름으로 동작했습니다.
1. 클라이언트 요청을 수신합니다.
2. 문서별 Markdown 추출 작업을 생성합니다.
3. 각 작업을 병렬로 실행합니다.
4. 모든 작업 완료 시점까지 대기합니다.
5. 결과 데이터를 취합합니다.
6. AI 서버에 요청합니다.
7. 응답을 반환합니다.
프로젝트에서는 Spring Framework의 @Async 기능을 활용하여 비동기 실행 구조를 구성했습니다.
각 문서 처리 메서드는 별도의 비동기 스레드에서 실행되도록 설계했습니다.
즉, 문서 10개 요청 시 10개의 작업이 동시에 처리될 수 있는 구조였습니다.
물론 단순 병렬 실행만으로는 충분하지 않았습니다.
가장 중요한 것은 “모든 작업이 완료된 이후 다음 단계로 안전하게 진행하는 것”이었습니다.
이를 위해 CompletableFuture를 활용했습니다.
각 비동기 작업은 CompletableFuture 형태로 반환되었고, 최종 단계에서는 CompletableFuture.allOf()를 사용하여 전체 작업 완료 시점을 정확하게 동기화했습니다.
이 구조 덕분에 다음과 같은 효과를 얻을 수 있었습니다.
- 독립 작업을 동시에 처리할 수 있습니다.
- 전체 처리 시간을 단축할 수 있습니다.
- CPU 자원 활용도를 높일 수 있습니다.
- 응답 지연을 줄일 수 있습니다.
특히 기존에는 문서 처리 시간이 단순 누적되었지만, 개선 이후에는 가장 오래 걸리는 작업 시간 중심으로 전체 응답 시간이 결정되기 시작했습니다.
예를 들어 문서 10개를 각각 1초씩 처리해야 하는 경우 기존 구조에서는 약 10초가 필요했지만, 병렬 처리 구조에서는 약 1~2초 수준까지 단축될 수 있었습니다.
4. 적용 기술과 구현 방식
이번 성능 개선에서 핵심적으로 사용된 기술은 Spring의 @Async와 Java CompletableFuture였습니다.
먼저 @Async는 Spring Framework에서 제공하는 비동기 처리 기능입니다.
특정 메서드에 @Async를 선언하면 해당 메서드는 별도의 스레드에서 비동기적으로 실행됩니다.
프로젝트에서는 문서별 Markdown 추출 메서드에 @Async를 적용했습니다.
이를 통해 각 문서 처리 작업이 서로 독립적으로 병렬 수행될 수 있도록 구성했습니다.
예를 들어 다음과 같은 형태로 구현할 수 있었습니다.
- extractMarkdownAsync(document)를 호출합니다.
- CompletableFuture
- 병렬 실행 구조로 처리합니다.
하지만 단순 @Async만으로는 전체 흐름 제어가 어려웠습니다. 각 작업의 완료 시점을 정확하게 관리해야 했기 때문입니다.
이를 해결하기 위해 CompletableFuture를 적극 활용했습니다.
CompletableFuture는 비동기 작업 결과를 표현하는 객체이며, 여러 작업의 완료를 조합하거나 동기화할 수 있는 매우 강력한 기능을 제공합니다.
프로젝트에서는 다음과 같은 방식으로 사용했습니다.
- 문서별 비동기 작업을 생성합니다.
- CompletableFuture 리스트를 수집합니다.
- CompletableFuture.allOf()로 전체 완료를 대기합니다.
- 결과 데이터를 취합합니다.
특히 CompletableFuture는 단순 Future보다 훨씬 유연한 흐름 제어가 가능했습니다.
예를 들어 다음과 같은 기능을 쉽게 구현할 수 있었습니다.
- 완료 시 후속 작업을 연결할 수 있습니다.
- 예외 처리 체인을 구성할 수 있습니다.
- 병렬 작업을 조합할 수 있습니다.
- 비동기 흐름을 관리할 수 있습니다.
또한 병렬 처리 도입 시 스레드 풀(Thread Pool) 관리 역시 중요했습니다.
무제한 병렬 실행은 오히려 시스템 부하를 증가시킬 수 있기 때문입니다.
따라서 프로젝트에서는 적절한 ThreadPoolTaskExecutor를 구성하여 최대 동시 실행 수를 제한했습니다.
이를 통해 과도한 병렬 처리로 인한 시스템 불안정을 방지할 수 있었습니다.
5. 고려 사항과 운영 안정성
비동기 처리와 병렬 처리 구조는 성능 측면에서 매우 강력한 장점을 제공하지만, 동시에 운영 복잡성도 증가시킵니다.
따라서 프로젝트에서는 단순 속도 개선뿐만 아니라 안정적인 운영 구조를 함께 고려했습니다.
가장 먼저 고려한 부분은 스레드 풀 관리였습니다.
병렬 처리 수를 무작정 늘리는 것은 오히려 성능 저하를 유발할 수 있습니다.
예를 들어 다음 문제가 발생할 수 있습니다.
- 스레드가 과다 생성될 수 있습니다.
- CPU 컨텍스트 스위칭이 증가할 수 있습니다.
- 메모리 사용량이 증가할 수 있습니다.
- 서버 부하가 증가할 수 있습니다.
따라서 프로젝트에서는 서버 스펙과 평균 요청량을 고려하여 적절한 스레드 풀 크기를 설정했습니다.
두 번째 고려 사항은 예외 처리였습니다.
병렬 처리 환경에서는 일부 작업만 실패하는 상황이 발생할 수 있습니다.
예를 들어 문서 10개 중 1개 처리만 실패할 수도 있습니다.
이 경우 전체 요청을 실패로 처리할 것인지, 일부 성공 결과만 사용할 것인지에 대한 정책이 필요했습니다.
프로젝트에서는 예외 발생 시 전체 흐름에 미치는 영향을 최소화할 수 있도록 구조를 설계했습니다.
또한 CompletableFuture의 exception handling 기능을 활용하여 비동기 작업 예외를 안정적으로 처리했습니다.
세 번째는 완료 시점 동기화였습니다.
병렬 처리에서는 “모든 작업이 정확하게 완료된 이후 다음 단계로 진행하는 것”이 매우 중요합니다.
만약 일부 작업 완료 이전에 AI 서버 요청이 발생하면 데이터 정합성 문제가 발생할 수 있습니다.
따라서 프로젝트에서는 CompletableFuture.allOf() 기반 구조를 사용하여 전체 완료 시점을 정확하게 제어했습니다.
네 번째는 모니터링과 추적성이었습니다.
비동기 구조에서는 로그 흐름이 분산되기 때문에 장애 원인 분석이 어려워질 수 있습니다.
이를 해결하기 위해 작업별 로그와 처리 시간을 추적할 수 있도록 구성했습니다.
이를 통해 특정 문서 처리 지연이나 예외 상황을 빠르게 확인할 수 있었습니다.
6. 개선 결과와 결론
비동기 처리 기반 구조 도입 이후 프로젝트에서는 여러 측면에서 의미 있는 성능 개선 효과를 확인할 수 있었습니다.
가장 먼저 체감된 부분은 응답 시간 감소였습니다.
기존 구조에서는 문서 개수 증가에 따라 처리 시간이 선형적으로 증가했지만, 개선 이후에는 병렬 처리 구조 덕분에 전체 응답 시간이 크게 단축되었습니다.
특히 문서 수가 많을수록 개선 효과가 더욱 크게 나타났습니다.
예를 들어 기존에는 문서 10개 처리 시 수 초 이상 걸리던 작업이 병렬 처리 이후 훨씬 짧은 시간 안에 완료될 수 있었습니다.
두 번째는 서버 자원 활용 효율 증가였습니다.
기존 구조에서는 순차 처리로 인해 CPU와 스레드 자원을 충분히 활용하지 못했습니다.
반면 개선 이후에는 여러 작업을 동시에 처리하면서 시스템 자원을 훨씬 효율적으로 사용할 수 있게 되었습니다.
세 번째는 사용자 경험 개선이었습니다.
사용자는 더 빠른 응답 속도를 체감할 수 있었고, AI 기능 사용 과정에서의 대기 스트레스 역시 크게 감소했습니다.
네 번째는 구조적 확장성이었습니다.
이번 경험을 통해 비동기 처리와 병렬 처리 구조가 단순 성능 개선뿐만 아니라 향후 확장성 측면에서도 매우 중요한 설계 방식이라는 점을 체감할 수 있었습니다.
특히 다음과 같은 환경에서 매우 효과적이라는 점을 확인했습니다.
- 독립 작업이 많은 구조입니다.
- I/O 처리 비중이 높은 구조입니다.
- 다수 요청을 병렬 처리하는 환경입니다.
- AI 연동 전처리 구조입니다.
결론적으로 이번 개선은 단순 기능 구현 수준을 넘어 “처리 구조 자체를 개선한 성능 최적화 사례”라고 볼 수 있었습니다.
또한 비동기 처리와 병렬 처리 구조는 현대 백엔드 시스템에서 매우 중요한 핵심 설계 방식이며, 향후 유사한 구조 설계에서도 적극적으로 활용할 수 있는 매우 유의미한 경험이었습니다.
messi