← 글 목록

2026년 8월 10일

시간표 이미지 분석 타임아웃 해결을 위한 비동기 처리와 멀티프로세싱 적용

동숲 프로젝트에서 시간표 이미지 자동 분석 시 발생한 타임아웃 현상과 동시성 문제를 해결하기 위해 비동기 작업 큐와 멀티프로세싱을 적용한 과정을 정리했습니다.

Message QueueAsynchronous ProcessingMultiprocessingOCR이미지 분석TimeoutProject

이미지 분석 타임아웃 해결을 위한 비동기 처리 적용 및 멀티프로세싱 도입

왜 타임아웃 현상이 발생했나

동숲 프로젝트에는 사용자가 시간표 이미지를 업로드하면 자동으로 강의명, 교수명, 강의실, 요일, 교시 정보를 분석하는 기능이 있었습니다.

이 기능은 시간표 이미지에서 표 영역을 찾고, 각 셀을 분리한 뒤 OCR을 수행하는 방식으로 구성되었습니다. 시간표 특성상 하나의 이미지 안에 여러 개의 셀이 존재하기 때문에, 모든 셀에 대해 OCR을 반복 호출해야 했습니다.

문제는 이 과정이 생각보다 오래 걸린다는 점이었습니다. 이미지 전처리, 표 감지, 셀 분리, OCR 수행이 순차적으로 진행되면서 전체 분석 시간이 길어졌고, 요청이 완료되기 전에 서버나 클라이언트에서 타임아웃이 발생할 수 있었습니다.

또한 초기에는 동기 처리 방식으로 구성되어 있었기 때문에, 하나의 이미지 분석 요청이 오래 실행되는 동안 다른 요청에 대한 응답이 지연되는 문제가 발생했습니다.

즉 문제는 크게 두 가지였습니다.

  1. 시간표 이미지 분석 자체가 오래 걸림
  2. 긴 작업이 서버 요청 처리 흐름을 막음

이를 해결하기 위해 이미지 분석 작업을 요청-응답 흐름에서 분리하고, 내부 작업 큐와 멀티프로세싱을 적용하는 방향으로 개선했습니다.

기존 동기 처리 방식의 문제점

초기 구조에서는 사용자가 이미지를 업로드하면 서버가 요청을 받은 뒤, 이미지 분석이 끝날 때까지 응답을 반환하지 않는 방식이었습니다.

흐름은 다음과 같았습니다.

사용자 이미지 업로드

FastAPI 요청 수신

이미지 전처리

시간표 셀 검출

셀 단위 OCR 수행

결과 파싱

응답 반환

이 방식은 구현은 단순하지만, 이미지 분석 시간이 길어질수록 문제가 발생했습니다.

이미지 분석 작업이 완료될 때까지 HTTP 요청이 계속 유지되어야 하고, 분석 중 다른 요청이 들어오면 서버 응답성이 떨어질 수 있습니다. 특히 OCR은 CPU 연산과 외부 OCR 엔진 호출이 반복되는 작업이기 때문에, 단순 API 요청 처리보다 시간이 오래 걸릴 수밖에 없었습니다.

따라서 이미지 분석 작업을 일반적인 API 요청처럼 즉시 처리하는 방식은 적합하지 않다고 판단했습니다.

적용 방식

이를 개선하기 위해 다음과 같은 방식을 적용했습니다.

  1. FastAPI 내부 비동기 작업 큐 도입
  2. Job ID 기반 작업 상태 관리
  3. 멀티프로세싱을 활용한 OCR 병렬 처리
  4. 클라이언트의 폴링 방식 적용

전체 흐름은 다음과 같이 변경했습니다.

사용자 이미지 업로드

FastAPI 요청 수신

Job ID 생성

작업 큐에 이미지 분석 작업 등록

즉시 Job ID 응답

백그라운드에서 이미지 분석 수행

클라이언트가 Job ID로 상태 조회

분석 완료 시 결과 반환

이 구조를 적용하면서 사용자는 이미지 분석이 끝날 때까지 HTTP 요청을 계속 유지하지 않아도 되고, 서버는 긴 작업을 일반 요청 처리 흐름과 분리할 수 있게 되었습니다.

1. 멀티프로세싱의 도입

시간표 이미지 분석에서 가장 오래 걸리는 부분은 셀 단위 OCR 처리였습니다.

하나의 시간표 이미지에는 여러 개의 셀이 존재하고, 각 셀마다 OCR을 수행해야 했습니다. 초기에는 이 작업을 순차적으로 처리했기 때문에 셀 개수가 많아질수록 전체 처리 시간이 길어졌습니다.

이를 개선하기 위해 멀티프로세싱을 도입했습니다.

멀티프로세싱은 하나의 작업을 여러 프로세스로 나누어 병렬 처리하는 방식입니다. Python에서는 CPU 연산이 많은 작업에서 단일 프로세스만 사용할 경우 병렬 처리에 한계가 있을 수 있습니다. 따라서 OCR처럼 반복적이고 독립적인 작업은 여러 프로세스로 나누어 처리하는 것이 효과적이라고 판단했습니다.

시간표 이미지 분석에서는 각 셀의 OCR 작업이 서로 독립적이기 때문에 병렬 처리에 적합했습니다.

예를 들어 기존에는 다음과 같이 처리되었습니다.

셀 1 OCR → 셀 2 OCR → 셀 3 OCR → 셀 4 OCR → ...

멀티프로세싱 적용 후에는 다음과 같이 여러 셀을 동시에 처리할 수 있습니다.

프로세스 1 → 셀 1 OCR
프로세스 2 → 셀 2 OCR
프로세스 3 → 셀 3 OCR
프로세스 4 → 셀 4 OCR

물론 현재 프로젝트 서버는 제한된 CPU 기반 단일 인스턴스였기 때문에, 무작정 worker 수를 늘리는 방식은 적합하지 않았습니다. worker 수가 너무 많아지면 오히려 CPU 사용량이 급증하고, 컨텍스트 스위칭 비용이 증가해 전체 성능이 떨어질 수 있습니다.

따라서 서버 자원 안에서 감당 가능한 수준으로 worker 수를 조정하는 것이 중요했습니다.

2. FastAPI 내부 비동기 작업 큐 도입

이미지 분석 작업은 시간이 오래 걸리기 때문에 일반적인 동기 API 요청 방식으로 처리하기에 적합하지 않았습니다.

이를 해결하기 위해 FastAPI 내부에서 비동기 작업 큐 구조를 적용했습니다.

사용자가 이미지 분석을 요청하면 서버는 바로 분석을 수행하지 않고, 먼저 Job ID를 생성한 뒤 작업을 큐에 등록합니다. 그리고 클라이언트에게는 Job ID를 즉시 반환합니다.

이후 실제 이미지 분석 작업은 백그라운드에서 처리되고, 클라이언트는 Job ID를 이용해 작업 상태를 조회합니다.

작업 상태는 다음과 같이 관리할 수 있습니다.

상태 의미
QUEUED 작업이 큐에 등록되었지만 아직 실행되지 않은 상태
RUNNING 이미지 분석 작업이 실행 중인 상태
SUCCEEDED 이미지 분석이 성공적으로 완료된 상태
FAILED 이미지 분석 중 오류가 발생한 상태
CANCELLED 사용자가 작업을 취소한 상태

이 구조를 통해 긴 작업을 요청 처리 흐름에서 분리할 수 있었고, 이미지 분석 중에도 다른 API 요청에 응답할 수 있는 구조를 만들 수 있었습니다.

비동기 메시지 큐를 검토한 이유

비동기 처리를 도입하는 과정에서 Redis, Kafka, RabbitMQ와 같은 외부 메시지 브로커도 함께 고려할 수 있었습니다.

이러한 도구들은 작업 대기열, 재시도, 장애 복구, 작업 분산 처리 측면에서 장점이 있습니다.

대표적인 후보는 다음과 같습니다.

기술 특징 적합한 상황
Redis Queue / Redis Streams 인메모리 기반으로 가볍고 빠른 큐 구성 가능 단순 작업 큐, 빠른 처리
RabbitMQ 전통적인 메시지 브로커, ACK와 라우팅 기능 제공 안정적인 작업 큐, 재시도 처리
Kafka 대용량 이벤트 스트리밍 플랫폼 대규모 로그, 이벤트 처리
Celery + Redis/RabbitMQ Python 기반 비동기 작업 큐 프레임워크 백그라운드 작업, 분산 작업 처리
DB 기반 Queue 별도 인프라 없이 DB 테이블로 큐 구성 작은 규모, 단순 작업 관리

Redis나 RabbitMQ는 비동기 작업 큐를 구성하기에 적합하고, Kafka는 대용량 이벤트 스트리밍 환경에서 강점을 가집니다. Celery 역시 Python 환경에서 백그라운드 작업을 처리할 때 많이 사용되는 방식입니다.

하지만 동숲 프로젝트는 제한된 OCI 인스턴스 1개에서 Docker 컨테이너 기반으로 운영되고 있었고, 현재 가장 큰 문제는 대규모 트래픽 처리보다 시간표 이미지 분석 작업이 오래 걸린다는 점이었습니다.

이 상황에서 별도의 메시지 브로커를 추가하면 새로운 컨테이너 관리, 네트워크 설정, 장애 대응, 큐 상태 관리 등 운영 복잡도가 증가할 수 있다고 판단했습니다.

FastAPI 비동기 처리를 선택한 이유

초기 단계에서는 Redis, Kafka, RabbitMQ 같은 외부 메시지 큐를 도입하기보다, 기존 FastAPI 서버 안에서 비동기 작업 큐 구조를 먼저 적용하는 것이 더 적합하다고 판단했습니다.

FastAPI 내부 비동기 처리 방식은 별도의 인프라를 추가하지 않고도 요청 처리 흐름과 실제 작업 수행 흐름을 분리할 수 있습니다.

동숲 프로젝트의 현재 상황에서는 다음과 같은 이유로 FastAPI 내부 큐 방식이 가장 현실적인 선택이었습니다.

  • 별도 메시지 브로커 컨테이너가 필요 없음
  • Redis, Kafka, RabbitMQ 운영 부담이 없음
  • 기존 FastAPI 서버 구조 안에서 빠르게 적용 가능
  • 단일 인스턴스 환경에 적합함
  • 이미지 분석 작업을 요청 흐름에서 분리할 수 있음
  • 프로젝트 규모에 비해 구조가 과하게 복잡해지지 않음

물론 FastAPI 내부 비동기 처리가 외부 메시지 브로커를 완전히 대체하는 것은 아닙니다.

서버가 재시작되면 큐에 등록된 작업이 사라질 수 있고, 실패한 작업의 재시도나 여러 서버 인스턴스 간 작업 분산에는 한계가 있습니다. 또한 대규모 트래픽 상황에서는 Redis Queue, RabbitMQ, Celery와 같은 외부 작업 큐가 더 안정적인 선택이 될 수 있습니다.

하지만 현재 동숲 프로젝트의 인프라 제약과 서비스 규모를 고려했을 때, 우선은 FastAPI 기반 비동기 처리로 구조를 단순하게 유지하고, 이후 트래픽 증가나 작업 안정성 요구가 커질 경우 외부 메시지 큐 도입을 검토하는 방향이 더 현실적이라고 판단했습니다.

메시지 큐 개념

Queue는 선입선출(First In First Out, FIFO) 구조를 가진 자료구조입니다.

먼저 들어온 데이터가 먼저 처리되는 구조이기 때문에, 작업을 순서대로 처리해야 하는 상황에 적합합니다.

메시지 큐(Message Queue)는 이러한 Queue 구조를 활용해 서비스들이 서로 비동기적으로 메시지를 주고받을 수 있도록 돕는 구조입니다.

일반적인 동기 처리 방식에서는 요청을 보낸 쪽이 작업이 끝날 때까지 기다려야 합니다.

요청 → 작업 처리 완료까지 대기 → 응답

반면 메시지 큐 기반 비동기 처리에서는 요청을 받은 뒤 작업을 큐에 등록하고, 실제 작업은 별도의 처리 흐름에서 수행합니다.

요청 → 작업 큐 등록 → 즉시 응답

              백그라운드 작업 처리

이 방식을 사용하면 시간이 오래 걸리는 작업을 요청-응답 흐름에서 분리할 수 있고, 서버는 사용자의 요청에 더 빠르게 반응할 수 있습니다.

동숲 프로젝트에서는 외부 메시지 브로커를 사용하지는 않았지만, FastAPI 내부에서 작업을 큐에 등록하고 순차적으로 처리하는 방식으로 메시지 큐와 유사한 구조를 적용했습니다.

API 구조

비동기 작업 처리를 위해 API는 다음과 같은 방식으로 구성할 수 있습니다.

Method Endpoint 설명
POST /timetable/analyze 시간표 이미지 분석 작업 요청
GET /jobs/{job_id} 작업 상태 및 결과 조회
POST /jobs/{job_id}/cancel 작업 취소 요청

1. 이미지 분석 요청

사용자가 시간표 이미지를 업로드하면 서버는 이미지를 즉시 분석하지 않고 Job ID를 생성합니다.

{
  "job_id": "abc123",
  "status": "QUEUED"
}

2. 작업 상태 조회

클라이언트는 Job ID를 이용해 작업 상태를 주기적으로 조회합니다.

{
  "job_id": "abc123",
  "status": "RUNNING"
}

분석이 완료되면 다음과 같이 결과를 반환합니다.

{
  "job_id": "abc123",
  "status": "SUCCEEDED",
  "result": [
    {
      "day": "월",
      "period": "1",
      "course_name": "데이터베이스",
      "professor": "홍길동",
      "room": "3호관 201호"
    }
  ]
}

3. 작업 실패

분석 중 오류가 발생하면 실패 상태와 에러 메시지를 저장합니다.

{
  "job_id": "abc123",
  "status": "FAILED",
  "error": "OCR 분석 중 오류가 발생했습니다."
}

이러한 구조를 통해 클라이언트는 분석 요청과 결과 조회를 분리할 수 있고, 서버는 긴 작업을 백그라운드에서 안정적으로 처리할 수 있습니다.

개선된 부분

비동기 작업 큐와 멀티프로세싱을 적용하면서 다음과 같은 부분이 개선되었습니다.

1. 타임아웃 문제 완화

기존에는 이미지 분석이 완료될 때까지 HTTP 요청을 유지해야 했기 때문에, 분석 시간이 길어지면 타임아웃이 발생할 수 있었습니다.

비동기 처리 구조에서는 요청 즉시 Job ID를 반환하고, 분석 결과는 별도 조회 API를 통해 확인하도록 변경했습니다.

이를 통해 긴 OCR 작업이 HTTP 요청 타임아웃에 직접 영향을 주지 않도록 개선할 수 있었습니다.

2. 서버 응답성 개선

이미지 분석 작업이 진행되는 동안에도 서버는 다른 요청에 응답할 수 있어야 했습니다.

작업 큐를 통해 이미지 분석을 백그라운드 작업으로 분리하면서, 긴 작업이 일반 API 요청 흐름을 막는 문제를 줄일 수 있었습니다.

3. OCR 처리 시간 개선

멀티프로세싱을 적용하면서 셀 단위 OCR 작업을 병렬로 처리할 수 있게 되었습니다.

각 셀은 독립적으로 OCR을 수행할 수 있기 때문에, 순차 처리보다 전체 분석 시간을 줄일 수 있었습니다.

다만 worker 수를 과도하게 늘리면 CPU 사용량이 증가할 수 있기 때문에, 서버 자원에 맞는 적절한 worker 수 조정이 필요했습니다.

4. 작업 상태 관리 가능

기존 동기 처리 방식에서는 사용자가 요청을 보낸 뒤 분석이 끝날 때까지 기다려야 했습니다.

비동기 구조에서는 QUEUED, RUNNING, SUCCEEDED, FAILED, CANCELLED와 같은 상태를 관리할 수 있기 때문에, 클라이언트가 현재 작업 진행 상황을 확인할 수 있습니다.

적용하면서 고려한 점

1. 외부 메시지 브로커 도입 여부

Redis, RabbitMQ, Kafka 같은 외부 메시지 큐를 사용하면 더 안정적인 작업 큐를 구성할 수 있습니다.

하지만 현재 프로젝트는 단일 OCI 인스턴스에서 운영되고 있었고, 별도의 컨테이너와 운영 관리 부담을 늘리기 어려운 상황이었습니다.

따라서 초기 단계에서는 FastAPI 내부 비동기 작업 큐를 적용하고, 향후 트래픽 증가나 안정성 요구가 커질 때 외부 메시지 브로커를 도입하는 방식이 더 적합하다고 판단했습니다.

2. 작업 유실 가능성

FastAPI 내부 큐는 구조가 단순하다는 장점이 있지만, 서버가 재시작되면 큐에 등록된 작업이 유실될 수 있습니다.

따라서 현재 방식은 작업 유실이 큰 문제가 되지 않는 초기 단계나 단일 서버 환경에 적합합니다.

만약 사용자가 많은 환경에서 안정적인 작업 보장이 필요하다면 Redis Queue, RabbitMQ, Celery 같은 외부 큐 도입이 필요합니다.

3. Worker 수 조정

OCR 작업은 CPU 자원을 많이 사용할 수 있습니다.

멀티프로세싱 worker 수를 늘리면 병렬 처리가 가능하지만, 서버 자원보다 많은 worker를 실행하면 오히려 성능이 떨어질 수 있습니다.

따라서 현재 서버의 CPU 성능과 동시 요청 수를 고려해 적절한 worker 수를 설정하는 것이 중요했습니다.

4. 작업 취소 처리

이미지 분석은 시간이 오래 걸릴 수 있기 때문에 사용자가 작업을 취소할 수 있는 구조도 고려했습니다.

작업이 이미 완료된 경우에는 취소할 수 없도록 처리하고, 아직 대기 중이거나 실행 중인 작업에 대해서만 취소 요청을 반영하는 방식이 필요했습니다.

이를 통해 불필요한 작업이 계속 실행되는 것을 줄일 수 있습니다.

결과와 기대 효과

비동기 작업 큐와 멀티프로세싱을 적용하면서 시간표 이미지 분석 기능의 구조를 더 안정적으로 개선할 수 있었습니다.

가장 큰 변화는 이미지 분석 요청과 실제 분석 작업을 분리했다는 점입니다.

기존에는 분석이 완료될 때까지 사용자가 기다려야 했지만, 개선 후에는 서버가 Job ID를 먼저 반환하고, 클라이언트가 상태 조회 API를 통해 결과를 확인하는 구조로 변경되었습니다.

이를 통해 다음과 같은 효과를 기대할 수 있었습니다.

  • 긴 OCR 작업으로 인한 API 타임아웃 완화
  • 이미지 분석 중 다른 요청 응답 지연 문제 개선
  • 셀 단위 OCR 병렬 처리로 전체 분석 시간 단축
  • 작업 상태 조회 및 실패 처리 가능
  • 향후 외부 메시지 큐 도입으로 확장 가능한 구조 확보

이번 개선은 단순히 속도를 높이는 작업이 아니라, 오래 걸리는 작업을 서비스 구조 안에서 어떻게 안정적으로 처리할 것인지에 대한 구조 개선이었습니다.

배운 점

이번 경험을 통해 시간이 오래 걸리는 작업을 일반적인 동기 API 요청 안에서 모두 처리하는 방식에는 한계가 있다는 것을 배웠습니다.

특히 OCR 이미지 분석처럼 전처리와 반복 연산이 많은 작업은 사용자가 요청한 즉시 결과를 반환하기 어렵습니다. 이런 작업을 동기 방식으로 처리하면 타임아웃이 발생하거나, 다른 요청의 응답까지 지연될 수 있습니다.

처음에는 단순히 OCR 속도 자체를 개선하는 것만 생각했지만, 실제 문제는 작업 시간이 긴 것뿐만 아니라 긴 작업이 API 요청 흐름을 막는 구조에도 있었습니다.

비동기 작업 큐를 적용하면서 요청과 작업을 분리하는 것이 중요하다는 점을 배웠습니다. 사용자는 먼저 Job ID를 받고, 서버는 백그라운드에서 작업을 처리하며, 결과는 상태 조회 API를 통해 확인하는 방식이 더 안정적인 구조였습니다.

또한 멀티프로세싱을 적용하면서 CPU 기반 작업은 worker 수 조정이 중요하다는 점도 알게 되었습니다. 병렬 처리를 적용한다고 해서 무조건 빨라지는 것이 아니라, 서버 자원에 맞는 적절한 worker 수를 설정해야 안정적으로 동작할 수 있었습니다.

Redis, Kafka, RabbitMQ 같은 외부 메시지 큐를 바로 도입하지 않은 것도 좋은 경험이었습니다. 기능적으로는 외부 메시지 브로커가 더 강력하지만, 현재 프로젝트 규모와 인프라 제약을 고려하면 FastAPI 내부 비동기 작업 큐가 더 현실적인 선택이었습니다.

결국 이번 개선을 통해 기술 선택은 단순히 더 강력한 도구를 고르는 것이 아니라, 현재 문제의 크기와 운영 환경에 맞는 적절한 복잡도를 선택하는 과정이라는 것을 배웠습니다.