Spring Boot 애플리케이션에서 AWS S3에 저장된 파일을 다운로드하기 위해 S3AsyncClient를 사용하고 있었고, 기존에는 잘 동작하던 API가 어느 시점 이후부터 다음과 같은 에러가 발생했습니다.
java.lang.ClassNotFoundException: software.amazon.awssdk.regions.internal.util.RegionScope
at software.amazon.awssdk.services.s3.internal.handlers.EndpointAddressInterceptor.lambda$modifyHttpRequest$1(EndpointAddressInterceptor.java:71)
왜 RegionScope 클래스를 못찾을까?
이번 오류는 AWS SDK 내부 클래스인 RegionScope를 찾지 못해 발생했습니다.
이 클래스는 SDK 버전에 따라 패키지 경로가 변경되었고, 다음과 같은 차이가 있습니다.
SDK 버전 | 클래스 위치 |
2.17.x | software.amazon.awssdk.regions.RegionScope |
2.30.x | software.amazon.awssdk.regions.util.RegionScope |
즉, 클래스명은 같지만 실제 패키지 경로가 달라지기 때문에 서로 다른 버전의 SDK 모듈이 혼용된 상태에서 클래스 참조가 꼬이면 런타임에 ClassNotFoundException이 발생하게 됩니다.
왜 다른 버전의 SDK가 혼용되어 사용되었을까?
AWS Personalize 기능을 추가하면서 해당 기능은 AWS SDK 2.30.x 버전을 필요로 했고, 그래서 서비스 애플리케이션인 fo-web에 personalizeruntime 의존성을 2.30.16으로 직접 추가했습니다. 배포 후 정상 작동할 것으로 예상했지만, 다른 모듈에서 런타임 에러가 발생했습니다.
fo-web
├── personalizeruntime:2.30.16 (신버전 직접 명시)
│ └── regions:2.30.16 (transitive로 자동 포함)
└── framework (의존성)
└── s3:2.17.36 (구버전)
framework
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.17.36</version>
<type>pom</type>
<scope>import</scope>
</dependency>
fo-web
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>personalizeruntime</artifactId>
<version>2.30.16</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>profiles</artifactId>
<version>2.30.16</version>
</dependency>
- 공통 라이브러리인 framework에는 AWS SDK BOM이 2.17.36으로 설정되어 있었고, 여기서 s3 모듈을 해당 버전으로 관리하고 있었습니다.
- 그런데 fo-web 애플리케이션에서 AWS Personalize Runtime 기능을 추가하기 위해 personalizeruntime 모듈을 최신 버전인 2.30.16으로 직접 추가했습니다.
- 이 과정에서 regions:2.30.16 모듈이 함께 올라갔고, 해당 버전에서는 RegionScope 클래스의 위치가 software.amazon. awssdk.regions.RegionScope 로 바뀌어 있었습니다.
- 반면, s3:2.17.36은 여전히 software.amazon.awssdk.regions.util.RegionScope를 참조하고 있었고, 이 클래스는 regions:2.30.16에는 존재하지 않기 때문에 런타임에서 ClassNotFoundException이 발생하게 된 것입니다.
BOM 이란? (Bill Of Materials 제품을 생산하는 데 필요한 부품과 원재료의 목록)
소프트웨어 개발, 특히 Maven, Gradle과 같은 빌드 도구를 사용하는 프로젝트에서 의존성들을 관리하기 위한 매커니즘. BOM의 역할은 의존성 관리 설명서와 같습니다.
AWS SDK
BOM 2.17.36
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-sdk-java-pom</artifactId>
<version>2.17.36</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bom</artifactId>
<packaging>pom</packaging>
<name>AWS Java SDK :: Bill of Materials</name>
<description>The AWS SDK for Java - BOM module holds the dependency managements for individual java clients.</description>
<url>https://aws.amazon.com/sdkforjava</url>
<properties>
<sonar.skip>true</sonar.skip>
</properties>
<dependencyManagement>
<dependencies>
<!-- Codegen -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>codegen-lite-maven-plugin</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>
....
해결방안
1. framework에서 AWS SDK 버전 통일 관리
공통 라이브러리인 framework에서 AWS SDK BOM의 버전을 2.30.x로 버전업
장점
- 모든 application에서 SDK 버전을 일관되게 통일 가능
- BOM을 한 곳에서만 관리
→ 유지보수 효율적
단점
- framework에 추가한 AWS SDK 모듈은 이 framework를 사용하는 모든 application에 자동으로 포함됨.
→ 해당 application에서 실제로 사용하지 않더라도 불필요한 라이브러리가 함께 올라가고, 충돌 가능성도 생길 수 있음.
- SDK 버전을 올리면, framework를 사용하는 모든 application에 영향을 줌.
→ 예상치 못한 에러나 호환성 이슈가 생길 수 있음.
→ 따라서 버전을 올리기 전에는 충분한 테스트와 검증 과정이 필요함.
2. 서비스에서만 사용하는 Personalize만 sharding (비권장)
3. 멀티 모듈 분리 (personalize 관련 기능을 별도 서비스로 분리)
이번 버전 이슈를 겪으면서 "앞으로는 AWS SDK 버전을 통일해야 하나?"라는 고민이 들었습니다. 하지만 실제로는 현재 어디에서 어떤 SDK를 사용하고 있는지조차 완전히 파악되지 않은 상황이었고, 당장 필요한 것은 제가 담당하고 있는 애플리케이션의 문제를 해결하는 일이었습니다. 이번에 문제가 된 의존성은 실제로 사용하고 있지 않기 때문에, 이를 제거함으로써 충돌 가능성 자체를 없애는 방향으로 해결할 예정입니다. 장기적으로 보면 버전 통일이 올바른 방향일 수도 있지만, 반대로 반드시 통일만이 정답은 아닐 수도 있다는 생각이 듭니다. 결국 의존성 관리는 조직이 감당할 수 있는 복잡성과 유연성 사이에서 어디에 균형을 둘 것인가의 문제인 것 같습니다.
'n년차 개발자' 카테고리의 다른 글
Spring Boot Logstash 연동 설정 및 작동 방식 (1) | 2025.04.16 |
---|---|
모니터링 시스템 구축 (0) | 2025.04.07 |
docker mysql 쿼리 결과 파일로 받기 (0) | 2022.10.14 |
Java -Xms -Xmx 옵션 (OutOfMemoryError: Java heap space...) (0) | 2022.09.19 |
Prometheus, Grafana 사용해보기 (0) | 2022.08.23 |