-
Notifications
You must be signed in to change notification settings - Fork 4
이미지 업로드 분리 및 비동기화
채팅 데이터에는 이미지를 첨부할 수 있으며, 이 이미지는 AWS S3에 저장됩니다. 하지만 기존 방식에서는 채팅 저장과 이미지 업로드가 동시에 진행되어 다음과 같은 문제가 발생했습니다.
S3에 이미지가 업로드되는 동안 DB 커넥션이 유지되면서 불필요하게 자원이 점유됩니다. DB 커넥션 수는 제한적이므로, 다수의 요청이 발생하면 성능 저하나 커넥션 부족 문제가 발생할 수 있습니다.
여러 개의 이미지 업로드가 순차적으로 진행될 경우, 전체 요청 시간이 길어질 수 있습니다. 대용량 이미지가 포함되면 이 문제는 더욱 심각해집니다.
트랜잭션이 불필요하게 길어지면서 DB 잠금(lock)이 길어지고 성능 저하를 초래할 수 있습니다.
1️⃣ 이미지 업로드 API 분리 이미지 업로드를 채팅 저장과 독립적으로 처리하기 위해 별도의 이미지 업로드 API를 구축하였습니다.
- 클라이언트는 이미지 파일을 업로드 API로 전송
- 서버는 이미지를 AWS S3에 업로드 후, 해당 이미지의 S3 경로를 반환
- 클라이언트는 받은 이미지 URL을 채팅 저장 요청과 함께 전송
@PostMapping
public ResponseEntity<ImageResponse> uploadImages(@RequestPart List<MultipartFile> images) {
return ResponseEntity.ok(imageService.save(images));
}
2️⃣ 채팅 저장 시 이미지 리스트 전달 클라이언트는 S3에 저장된 이미지 URL 리스트를 받아 이를 채팅 저장 요청에 포함하여 전송합니다.
public record ChatMessageRequest(
...
@Size(max = 5, message = "이미지는 최대 5개까지 첨부할 수 있습니다.")
List<String> imageUrls
)
3️⃣ DB 저장: 이미지 업로드 분리로 커넥션 사용 최적화 서버는 클라이언트로부터 받은 메시지 정보와 이미지 URL 리스트만 저장하며, 실제 이미지 업로드 과정은 DB와 무관하게 처리됩니다. 이를 통해 DB 커넥션 점유 시간을 줄이고, 트랜잭션 유지 시간을 단축할 수 있습니다.
성능 최적화를 위한 비동기 이미지 업로드 적용 이미지 업로드 성능을 개선하기 위해 비동기 처리(Async) 를 적용하여 AWS S3 업로드를 최적화하였습니다. 이를 통해 여러 이미지를 병렬로 업로드하여 시스템의 처리 속도를 높이고, 서버 리소스를 효율적으로 활용할 수 있습니다.
public List<String> uploadImages(List<ImageFile> images) {
List<CompletableFuture<String>> imageUploadFutures = images.stream()
.map(image -> CompletableFuture.supplyAsync(() -> uploadImage(image)))
.toList();
return getUploadedImageNamesFromFutures(imageUploadFutures);
}
private List<String> getUploadedImageNamesFromFutures(List<CompletableFuture<String>> futures) {
List<String> imageNames = new ArrayList<>();
futures.forEach(future -> {
try {
imageNames.add(future.join());
} catch (final CompletionException ignored) {
}
});
return imageNames;
}
동기 업로드 vs. 비동기 업로드 비교
기존 방식(동기 업로드) → 10개 이미지 업로드: 759ms 개선 방식(비동기 업로드) → 10개 이미지 업로드: 228ms ➡ 처리 속도 약 3배 향상되었습니다.
개선 전
개선 후
- 탭바 슬라이딩 애니메이션으로 전환하기
- Presigned url 을 통한 이미지 업로드
- 입력 크기에 맞춰 늘어나는 textarea만들기
- flex:1를 사용할 때 부모 컴포넌트의 사이즈를 넘어가는 현상
- 위아래로 채팅 무한 스크롤 구현하기
- 공통 컴포넌트 문서화를 통해 UI 재사용성 향상
- 전역적 소켓 관리
- 쿼리 캐싱을 이용한 서버 상태 관리
- 전역 에러 처리
- 모바일 호환성을 위한 노력
- 기술 선정 이유
- 아키텍처
- 답해요 채팅 저장 방식 고민(데이터베이스 고민)
- 말해요 채팅 저장 방식 고민
- MongoDB에서 답해요와 말해요 채팅 데이터 관리 및 샤드 설계
- 웹소켓 연결 방식 고민
- MongoDB 인덱스 유무에 따른 쓰기, 조회 성능 테스트
- 중복 웹소켓 세션 처리 전략 및 구현 결정
- @Async를 활용한 이메일 전송 비동기 처리 및응답 시간 개선
- Docker 환경에서 ClassPathResource.getFile()이 실패하는 문제 해결
- Redis sync vs Async
- MongoClient vs Spring Data MongoDb
- 채팅방 웹소켓 이벤트 정리
- 무한 스크롤 정리
- 중복 로그인 방지
- 이미지 업로드 분리 및 비동기화
- 채팅 전송 도중 채팅방이 삭제된다면?
- 금칙어 필터링
- 답해요 성능테스트
- 새로 개설된 말해요 채팅방 실시간으로 알리기