Chefriend
외부 분석 API 호출과 이미지 전송의 대기 비용을 줄여 처리량과 응답성을 개선했습니다.
- Spring Boot
- WebClient
- AWS
- Terraform
- Docker
Chefriend는 고객이 리워드를 받고 비공개로 음식과 메뉴를 평가하면, 매장이 그 결과를 메뉴 개선에 활용할 수 있도록 만든 팀 프로젝트입니다. 저는 백엔드와 인프라를 맡아 외부 분석 API 연동, 이미지 전달 경로와 배포 자동화를 구현했습니다.
서비스의 주요 대기 시간은 데이터베이스 쿼리보다 외부 분석 응답과 이미지 전달에서 발생했습니다. 두 문제는 각각 애플리케이션 스레드와 사용자 화면을 붙잡고 있었기 때문에, 단순히 평균 응답을 줄이는 것이 아니라 같은 자원에서 요청이 몰려도 서비스가 멈추지 않는 구조와 반복 이미지 변환을 줄이는 구조가 필요했습니다.
문제의 시작
사용자가 메뉴 이미지를 등록하면 애플리케이션은 외부 분석 API를 호출해 메뉴 정보를 추출했습니다. 이 호출은 평균 약 3초가 걸렸고, 기존 동기 방식에서는 응답을 기다리는 동안 요청을 처리하던 서버 스레드가 그대로 점유됐습니다. 외부 시스템의 계산 시간은 우리 코드가 줄일 수 없는 구간이었지만, 그 시간을 기다리는 방식은 바꿀 수 있었습니다.
부하를 늘리자 동시 요청 50건 수준부터 목표 응답 시간 3.5초를 넘기며 요청 처리가 사실상 불가능한 상태가 재현됐습니다. 제한된 스레드가 외부 응답을 기다리는 동안 새 요청이 들어와도 실행할 스레드가 없었기 때문입니다. 스레드 수를 늘리는 방법은 잠시 한계를 늦출 수 있지만, 느린 외부 호출 수만큼 대기 자원과 메모리가 함께 늘어나는 구조는 그대로 남습니다.
이미지 경로에도 별도의 반복 비용이 있었습니다. 객체 저장소의 원본 이미지를 직접 제공할 때 응답에 200ms 이상이 걸렸고, 목록에서 필요한 썸네일 크기로 매번 클라이언트가 다시 줄였습니다. 같은 이미지와 같은 크기를 여러 사용자가 요청해도 변환 결과를 재사용하지 못해 네트워크와 클라이언트 연산을 반복했습니다.
사용자 관점에서는 메뉴 분석 요청이 몰릴수록 다음 요청까지 막히고, 이미지가 많은 화면에서는 원본 전달과 변환을 기다려야 했습니다. 운영 관점에서는 외부 API 지연 하나가 애플리케이션 스레드 풀 고갈로 확대됐고, 이미지 최적화를 클라이언트마다 반복해 서버가 전달 형식과 캐시를 통제할 수 없었습니다. 그래서 외부 I/O 대기를 요청 스레드에서 분리하고, 이미지는 엣지에서 크기별로 변환한 결과를 캐시하는 두 경로로 문제를 나눴습니다.
검증 기준은 기술을 도입했다는 사실이 아니라 같은 조건에서 병목이 사라졌는지였습니다. 외부 API 경로는 동일한 32개 스레드와 3.5초 목표 아래에서 처리량과 실패 요청을 비교했고, 이미지 경로는 원본 직접 제공과 CDN 변환·캐시 적용 뒤 응답 시간을 반복 측정했습니다. 또한 크기·품질·형식이 다른 요청이 같은 캐시 객체로 섞이지 않는지, 엣지 응답 크기 제한을 넘는 이미지는 품질을 단계적으로 낮춰 제한 안에 들어오는지도 확인했습니다.
설계 선택
먼저 외부 분석 호출은 응답을 기다리는 스레드를 없애는 방향으로 바꾸고, 이미지 전달은 한 번 만든 변환 결과를 가장 가까운 전달 지점에서 재사용하는 방향으로 분리해 개선했습니다.
외부 호출은 WebClient 기반 논블로킹 흐름으로 전환해 요청 스레드가 응답을 동기적으로 기다리지 않게 했습니다. 이미지는 CDN에서 요청한 크기·품질·형식으로 변환하고, 이 조건을 캐시 키에 포함해 서로 다른 결과가 섞이지 않도록 했습니다.
외부 응답 대기는 논블로킹으로 분리하고 이미지 변환 결과는 엣지에서 재사용합니다.
애플리케이션은 외부 분석을 논블로킹으로 호출하고 원본 이미지는 CDN에서 변환해 크기와 품질과 형식별로 캐시한 뒤 사용자 응답에 함께 제공합니다.
실패를 막는 장치
외부 분석 호출에는 타임아웃과 제한된 재시도를 두어 느린 외부 응답이 무기한 자원을 차지하지 않게 했습니다. 실패는 해당 요청의 경계 안에서 처리하고 새 요청이 계속 실행될 수 있도록 했습니다.
이미지 변환 결과는 요청 조건별로 분리했고, 엣지 응답 크기 제한을 넘는 경우 품질을 단계적으로 낮춰 제한 안에 들어오게 했습니다. 원본은 보존해 변환 실패가 데이터 손실로 이어지지 않게 했습니다.
검증
외부 API 경로는 32개 스레드와 3.5초 목표를 동일하게 두고 동기 방식과 논블로킹 방식의 처리량과 실패 요청을 비교했습니다. 검증 기준은 외부 계산 시간 자체가 아니라 같은 서버 자원에서 새 요청을 계속 받을 수 있는지였습니다.
이미지 경로는 원본 직접 응답과 CDN 변환·캐시 적용 뒤를 반복 측정했습니다. 크기·품질·형식 조합마다 올바른 캐시 객체가 반환되는지와 큰 이미지가 응답 제한 안에 들어오는지도 확인했습니다.
결과와 배운 점
원본 검증 기준에서 처리량은 18 RPS에서 866 RPS로 늘었고, 이미지 응답 시간은 200ms에서 30ms로 줄었습니다. 수치는 같은 검증 조건에서 얻은 결과이며 외부 분석 계산 시간이 사라졌다는 뜻은 아닙니다.
느린 외부 I/O를 다룰 때는 호출 자체보다 기다리는 동안 어떤 자원을 점유하는지를 봐야 했습니다. 이미지 최적화도 파일 크기만 줄이는 일이 아니라 변환 조건과 캐시 경계를 함께 설계해야 반복 비용을 없앨 수 있었습니다.