Loki's Blog

OCR과 Open AI의 응답 속도 개선

  • 설계
  • 최적화
  • OCR
  • 멋쟁이사자처럼
2025-08-30

들어가며

계약서 이미지를 업로드하면 OCR로 문자를 추출하고 OpenAI에게 전달해 번역하는 기능이 있는데, 이 페이지 하나를 여는 데 2분이 걸렸다.

프론트엔드가 OCR과 OpenAI 처리를 모두 담당하는 구조라, 네트워크 응답 속도가 곧 서비스 체감 속도였다. 직접 측정한 결과 OCR 처리는 평균 6초로 괜찮았지만, OpenAI 응답은 번역 요청 평균 44.15초, 하이라이팅 요청 평균 46.71초로 오래 걸렸다. 측정 지표는 번역본, 하이라이팅, 전체 요약 각각의 요청/응답 시간이었다.

'계약서 분석 결과' 페이지 하나에서 이 세 요청이 모두 처리되다 보니 전체 로딩 시간은 2분에 달했다. 40초 넘는 응답 하나도 버거운데, 그게 페이지 하나에서 세 번 겹치는 구조였다.

화면 구역별로 나눠 요청해 2분을 58초로 줄이다

'계약서 분석 결과' 페이지는 번역, 하이라이팅, 요약 세 구역으로 나뉘어 있었는데, 세 구역의 요청을 페이지가 열리자마자 한꺼번에 보내고 있었다. 사용자가 스크롤할 때 대기 시간 없이 바로 다음 구역을 볼 수 있게 하려던 설계였다.

이 설계를 바꿔, 페이지 로드 시 모든 요청을 한 번에 보내는 대신 화면에 보이는 구역에 해당하는 요청만 보내도록 했다. 사용자가 '번역' 구역을 보고 있을 때는 하이라이팅이나 요약 요청을 보내지 않는 식이다.

세 구역의 요청이 한 페이지 로드에 몰려 있던 것을, 사용자가 실제로 보는 구역 단위로 흩어놓은 셈이다. 그 결과 전체 로딩 시간은 2분에서 58초로 줄었다.

페이지별로 나눠 병렬 처리해 28초로 줄이다

계약서는 3페이지로 구성되는데, 그때까지는 OCR로 뽑은 세 페이지의 텍스트를 하나로 묶어 OpenAI 요청 한 번에 실어 보내고 있었다. 페이지 수만큼 프롬프트에 담기는 텍스트가 늘어난 만큼 응답을 생성하는 데도 시간이 걸릴 거라고 봤다.

한 요청에 세 페이지를 다 욱여넣는 대신, 페이지별로 요청을 쪼개 병렬로 비동기 처리하도록 바꿨다. 페이지마다 따로 요청하면 요청 하나가 담는 텍스트 양이 3분의 1로 줄고, 세 요청을 동시에 실행하면 전체 대기 시간도 가장 오래 걸리는 페이지 하나의 시간으로 수렴할 거라고 판단했다.

적용한 결과 가장 오래 걸리던 페이지의 번역본 요청/응답 시간이 24초로 줄었고, OCR 처리 시간을 더해도 총 28초로 개선됐다.

첫 페이지부터 바로 렌더링해 18초로 줄이다

28초로 줄었어도 30초에 가까운 로딩은 여전히 부담스러운 수준이었다. 더 뜯어보니, 세 페이지의 응답이 모두 도착해야만 화면을 렌더링하고 있었다.

이 부분을 바꿔, 첫 번째 페이지의 응답이 도착하는 즉시 화면에 렌더링하고 나머지 페이지는 백그라운드에서 비동기로 처리하도록 했다. 사용자는 첫 페이지가 뜨는 대로 바로 내용을 확인할 수 있고, 두 번째 페이지부터는 뒤늦게 로딩되더라도 이미 첫 페이지를 읽는 중이라 크게 거슬리지 않는다.

사용자가 첫 화면을 보기까지 걸리는 시간은 18초로 줄었다. OCR부터 화면 렌더링까지, 세 번의 개선을 거친 최종 흐름은 다음과 같다.

더 나아가서, 개선할 수 있는 부분

여기서 더 줄이려면 계약서 형식을 고정된 템플릿으로 다뤄야 한다고 본다. 고정된 문장은 이미 번역된 내용을 미리 저장해두고, 사용자가 입력하거나 변경한 부분만 OpenAI에게 요청해 처리하면 되기 때문이다. 아직 구현하지 않아 확인한 수치는 아니지만, 전달할 데이터 양 자체가 줄어드는 구조라 전체 응답 시간도 절반 이상 단축될 가능성이 있다고 본다. OCR과 OpenAI 처리를 합쳐 10초 내외까지는 갈 수 있을 것으로 짐작한다.

정리하며

세 번의 개선은 모두 같은 방향이었다. 화면에 보이는 구역만 요청하고, 페이지 단위로 요청을 쪼개 병렬로 실행하고, 먼저 도착한 페이지부터 렌더링했다. 세 경우 다 '한 번에 다 받아서 한 번에 보여준다'는 전제를 깨고, 처리 단위를 잘게 쪼개 먼저 끝난 것부터 내보내는 방향이었다.

비동기 처리를 설계할 때는 요청을 얼마나 줄이느냐보다, 처리 단위를 어디까지 쪼갤 수 있는지와 그중 무엇을 먼저 사용자에게 보여줄 수 있는지를 함께 따져야 한다는 걸 이번에 확인했다. 다음에 비슷한 로딩 문제를 만나면, 요청 자체를 줄이려 하기 전에 지금 하나로 묶여 있는 처리 단위를 쪼갤 수 있는지부터 볼 생각이다.

관련 글