Overview

최근 회사 업무를 진행하며 다건의 ChatCompletion을 처리해야 하는 업무를 수행하였습니다. 타이트한 일정 속에서 개발 구조를 고민하고, 프롬프트 수정 및 개발을 병행하는 한편, 데이터 전처리 파트와의 조율 등 여러 업무를 동시에 처리해야 했습니다. 충분한 시간을 가지고 꼼꼼하게 계획하지 못했던 점은 아쉬웠지만, 이 과정에서 성능 개선 옵션과 트러블슈팅을 통해 알게 된 내용들을 정리하여 공유하고자 합니다. LLM을 활용한 처리가 워낙 흔해진 요즘, 이 글이 의미가 있을까 싶기도 하지만, 모든 것을 기억할 수 없으니 기록을 남기게 되었습니다.

상황 설정: 대량의 ChatCompletion 호출 🚨

처리해야 했던 상황은 다음과 같습니다. 1건의 데이터당 총 19회의 ChatCompletion을 호출해야 하며, 처리해야 할 건수는 1,500건 이상이었습니다. 결과적으로 총 28,500건 이상의 ChatCompletion 호출이 예상? 호출이 될 예정이었습니다.

이 작업은 실시간성이 필수적이지 않고 빈번하게 발생하지 않을 상황이었기에, 애플리케이션 개발 전에 Batch API를 옵션으로 고려했습니다. Batch API는 비용 절감 및 대량 처리에 이점이 있었으나, 촉박한 일정과 새로운 처리 방식 도입에 대한 부담으로 인해 일단 19회 순차 호출 처리 방식으로 개발을 시작했습니다.

상황 1: 순차 처리와 병렬 처리 도입 🚀

초기 개발은 1건의 데이터에 대해 19회의 ChatCompletion을 순차적으로 호출하고, 각 응답값 후처리 후 Pydantic 클래스에 할당하여 최종 응답으로 반환하는 방식이었습니다.

🐌 순차 처리의 한계 순차 처리 방식은 60초 이상의 시간이 소요되어 서비스 응답 시간에 큰 부담이 되었습니다.1500~2000건이상 처리 예정이기에 시간이 많이 소요되는 작업이었습니다.

⚡️ ThreadPool을 이용한 병렬 처리 이를 개선하기 위해 ThreadPool을 이용한 병렬 처리를 도입했습니다. 19회의 호출 중 선후행 관계가 필요한 호출(B, C)을 제외하고, A, (B->C), D 그룹을 병렬로 호출하도록 구조를 변경했습니다.

이 개선으로 응답 시간은 약 20초가량 단축되었지만, 새로운 문제가 발생했습니다. 이 API를 호출하는 외부 시스템 또한 병렬로 API를 호출하고 있었기에 (예: 5개 동시 호출), 결과적으로 1회 API 호출 시 최대 15회(5 × 3)의 ChatCompletion이 동시 호출되는 상황이 발생했습니다. 이는 곧바로 429 Too Many Request 에러로 이어졌습니다.

상황 2: 429 에러 트러블슈팅 및 Quota 관리 🛠️

병렬 호출의 중첩으로 발생한 429 에러를 해결하기 위해 두 가지 방향으로 접근했습니다.

  • AOAI (Azure OpenAI) Quota 확인 및 증설 요청

  • API 병렬 호출 횟수 조정 (Down)

🎯 즉각적인 병렬 콜 조정
당장 컨트롤 가능한 API 병렬 호출 횟수 조정을 우선 진행했습니다. 동시에 클라우드팀에 Quota 현황을 문의했습니다.

💡 Quota 확인 결과
확인 결과, 배포된 모델의 총 Quota가 정해져 있었고, 이 Quota가 개발 환경과 운영 환경에 각기 다르게 분배되어 있음을 알게 되었습니다. 추후 개발 환경에서 충분히 테스트를 마친 병렬 콜 조정 값에서도 갑자기 429 에러가 발생했는데, 이는 개발과 운영 환경 간의 Quota 차이 때문에 발생한 오류가 많았습니다. 결국 클라우드팀에 Quota 증설을 요청하며 이슈를 해결했습니다. 배포 환경별 Quota를 미리 확인하고 확보하는 것이 중요하다는 교훈을 얻었습니다.

상황 3: Batch API 재고려 (도입 실패) 🔄

이러한 실시간 처리의 복잡성과 Quota 이슈 속에서, 초기 옵션이었던 Batch API의 이점이 다시 부각되었습니다. Batch API의 주요 특징은 다음과 같습니다.

  • 요청 데이터 준비: 요청 JSON들을 모아 JSONL 파일 형태로 생성 후 업로드합니다.

  • Job 생성: 업로드 후 받은 file_id를 사용하여 Batch Job을 생성합니다.

  • 상태 확인: 생성된 batch_id로 Job의 처리 상태를 확인합니다.

  • 결과 다운로드: 결과 파일 역시 JSONL 형태이며, 요청 시 사용했던 custom_id로 개별 응답을 식별할 수 있습니다.

📉 Batch API의 장점
Batch API는 토큰 비용이 매우 저렴하며, Quota 제약 없이 대량 처리에 매우 유리합니다. (비실시간 처리)

🚧 도입 실패 요인
하지만 다음과 같은 이유로 결국 Batch API를 적용하지 못했습니다.

  • 아키텍처 변경: 현재 API 호출 트랜잭션과 분리된 완전한 재개발이 필요하여 일정을 초과할 수 있는 위험이 있었습니다.

  • 파일 처리 이슈: 업로드할 JSONL 파일이 사이즈 제한에 걸려 파일을 샤딩(Sharding) 해야 하는 추가적인 개발 이슈가 발생했습니다.

마치며

개발 전 충분한 검토와 함께 빠른 프로토타입 개발을 진행할 시간이 있었다면, 당면했던 Quota 문제나 Batch API 적용 가능성 등을 사전에 잘 챙겨서 더욱 효율적인 구조로 개발할 수 있었을 텐데 하는 아쉬움이 남습니다. 특히 LLM 관련 서비스에서는 비용, 지연 시간, Quota 세 가지 요소를 초기 기획 단계에서 충분히 고려하는 것이 중요함을 다시 한번 깨달았습니다. 하지만 잘 되지 않네요,,, 이러한 경험이 반복되면 개선될지도?