#Backend

[코드가 환경을 모르는 구조 6/7] 컨테이너는 왜 폭발하는가

[코드가 환경을 모르는 구조 6/7] 컨테이너는 왜 폭발하는가
01

Summary

느려터진 통합 테스트, '이것' 하나로 10분에서 2분으로 단축하기

Testcontainers 폭발을 막는 Gradle BuildService 기반의 스마트한 컨테이너 공유 전략

통합 테스트는 신뢰도가 높지만 느리다는 단점이 있습니다. 특히 Testcontainers를 쓸 때 컨테이너가 무한 증식하는 'dirty context' 문제를 해결하지 못하면 CI는 금방 마비됩니다. 본 아티클은 플렉스(flex) 팀이 Gradle BuildService를 이용해 단 하나의 컨테이너로 수많은 모듈의 통합 테스트를 격리하고 가속화한 실전 노하우를 담고 있습니다.

  • 01Spring TestContext 캐싱 메커니즘과 컨테이너 폭발의 상관관계 분석
  • 02@MockBean과 @DirtiesContext가 테스트 성능에 미치는 악영향 진단
  • 03Gradle BuildService를 활용한 '빌드 스코프 싱글턴' 컨테이너 관리 기법
  • 04물리적 컨테이너 공유와 논리적 스키마 격리를 결합한 하이브리드 전략
  • 05CI 성능 최적화를 통해 '테스트 부식'을 막고 개발 문화를 개선한 사례

RECOMMENDATION

멀티모듈 프로젝트에서 통합 테스트 속도 저하로 고통받는 백엔드 엔지니어들에게 적극 추천합니다. 특히 Spring Boot 환경에서 Testcontainers 도입을 고민 중이라면 성능 최적화를 위한 필수 설계 지침서가 될 것입니다.

The Problem

통합 테스트 시 Testcontainers를 사용할 때, Spring의 dirty context 현상으로 인해 불필요한 컨테이너가 중복 생성되어 CI 서버의 메모리 부하와 테스트 시간 지연이 발생한다. 특히 멀티모듈 프로젝트에서 모듈별로 서로 다른 컨텍스트 키가 생성되거나 @MockBean 사용 시 컨테이너가 무분별하게 복제되어 전체 빌드 속도가 급격히 저하되는 문제가 있다.

The Solution

Gradle 8에서 제공하는 BuildService를 활용하여 빌드 전체 범위에서 단 하나의 컨테이너만 유지하는 '빌드 수준 싱글턴' 구조를 도입했다. 하나의 공유 컨테이너 내에서 각 모듈의 경로를 기반으로 논리적인 데이터베이스 스키마를 분리하고, Liquibase를 통해 모듈별 독립적인 테스트 환경을 구축하여 컨테이너 복제 문제를 해결했다.

The Result

실제 인프라를 사용하는 통합 테스트의 신뢰성을 그대로 유지하면서도, 컨테이너 기동 시간을 획기적으로 줄여 10분 이상 소요되던 CI 시간을 2분대로 단축했다. 이를 통해 개발자들이 테스트를 건너뛰지 않고 자주 실행할 수 있는 환경을 조성하여 '테스트 부식'을 방지하고 코드 품질을 안정적으로 관리할 수 있게 되었다.

Trade-off

BuildService는 단일 빌드 내에서는 싱글턴을 보장하지만, 로컬 환경에서 여러 빌드가 동시에 실행되는 경우 여전히 여러 개의 컨테이너가 뜰 수 있다. 또한 CI 환경에서는 매 실행마다 새로운 JVM이 시작되므로 빌드 간 컨테이너 재사용 이득이 제한적이며, 이를 보완하기 위해 별도의 스냅샷 캐시 전략이 추가로 필요하다.

03

Key Concepts

Concept · 01

Dirty Context

Spring 테스트 프레임워크에서 특정 설정 변경으로 인해 기존 캐시된 ApplicationContext를 사용하지 못하고 새로 생성해야 하는 상태를 의미한다.

  • @MockBean 사용이나 컨텍스트 설정 변경 시 발생하여 의존된 컨테이너의 재기동을 유발한다.
Concept · 02

Gradle BuildService

Gradle 빌드 실행 기간 동안 단일 인스턴스로 유지되며 Task 간에 상태를 안전하게 공유할 수 있는 서비스이다.

  • 빌드 시작 시 컨테이너를 띄우고 종료 시점에 정리하는 수명 주기 관리자로 활용된다.
Concept · 03

Logical Schema Isolation

물리적인 데이터베이스 서버는 공유하되, 논리적인 데이터베이스(스키마)를 별도로 생성하여 데이터를 격리하는 방식이다.

  • 모듈 경로를 기반으로 고유한 DB 이름을 생성하여 멀티모듈 환경의 테스트 간섭을 방지한다.