I am currently modifying a Spring application to implement an API for uploading multiple images to S3 at once using @Async for asynchronous processing with multi-threading to achieve faster responses. However, after implementing this, the response time is not as fast as expected.
The logs indicate that multi-threading is functioning correctly, but the actual S3 upload times are longer, and the overall response is not faster during testing. I understand that S3 can handle concurrent requests, so I am unsure where the issue might be. Could you help identify the problem? Thank you!
Here is the code I wrote and the log output:
@Operation(summary = "다중 이미지 업로드 - 비동기", description = "새로운 이미지를 다중으로 업로드합니다.")
@PostMapping(value = "/images/async", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse<List<String>> uploadMultipleImagesAsync(
@Parameter(description = "업로드할 이미지 파일들", required = true, schema = @Schema(type = "array", format = "binary"))
@RequestPart("files") List<MultipartFile> files,
@Parameter(description = "이미지들이 업로드될 도메인 이름(notice or kindergarten)", required = true)
@RequestParam("domainName") String domainName) {
List<String> urls = imageService.uploadImagesAsync(files, domainName);
return ApiResponse.onSuccess(urls);
}
@Service
@RequiredArgsConstructor
public class ImageService {
private final S3Service s3Service;
// 여러 개의 이미지 업로드 - 비동기
public List<String> uploadImagesAsync(List<MultipartFile> multipartFiles, String domainName) {
List<CompletableFuture<String>> futures = new ArrayList<>();
for (MultipartFile file : multipartFiles) {
futures.add(s3Service.uploadImageAsync(file, domainName));
}
List<String> urls = new ArrayList<>();
futures.forEach(future ->
urls.add(future.join())
);
return urls;
}
}
// 이미지 업로드 - 비동기
@Async("imageUploadExecutor")
public CompletableFuture<String> uploadImageAsync(MultipartFile file, String domainName) {
CompletableFuture<String> future = new CompletableFuture<>();
String fileName = createFileName(file.getOriginalFilename());
String folder = IMAGES_FOLDER + domainName + "/";
long startTime = System.currentTimeMillis();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());
future.complete(uploadToS3(file, folder, fileName));
long endTime = System.currentTimeMillis();
log.info("{} - 실행 시간: {}", Thread.currentThread().getName(), endTime - startTime);
return future;
}
// S3에 업로드 공통 로직
private String uploadToS3(MultipartFile file, String folder, String fileName) {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getSize());
objectMetadata.setContentType(file.getContentType());
try (InputStream inputStream = file.getInputStream()) {
amazonS3Client.putObject(new PutObjectRequest(bucket, folder + fileName, inputStream,
objectMetadata).withCannedAcl(CannedAccessControlList.PublicRead));
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
"파일 업로드에 실패했습니다.");
}
return amazonS3Client.getUrl(bucket, folder + fileName).toString();
}
@Bean(name = "imageUploadExecutor")
public Executor imageUploadExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadGroupName("imageUploadExecutor");
executor.setCorePoolSize(10);
executor.setMaxPoolSize(15);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
async log
synch log
In practice, I expected to see a noticeable improvement in response time with asynchronous processing, but there was no significant difference compared to synchronous processing.
I also tried adjusting the thread pool settings and tested it on a VM rather than locally, but I didn’t observe any substantial effect. I really want to understand the cause.
user24348330 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.