Projects/hub-eleven

[부하테스트] 재고 API - (2) k6 부하 테스트 환경과 개발 환경 분리

annovation 2026. 7. 29. 23:20

진행 환경

💡 진행 환경

  • Java 17
  • Spring Boot 3.x
  • MySQL 8
  • Redis
  • Docker Compose
  • k6
  • Redisson

문제 상황

💡 문제 상황

 

k6 재고 차감 부하 테스트를 구성하면서 테스트용 상품과 재고를 SQL로 직접 생성했다.

시드 SQL 코드 리뷰 과정에서 기존에 같은 product_id를 가진 재고가 다른 stock_id로 저장되어 있다면 테스트 재고가 추가 삽입되어 하나의 상품에 여러 재고가 존재할 수 있다는 문제가 발견됐다.

처음에는 시드 SQL만 수정하면 해결될 것이라 생각했지만 테스트 환경을 확인하면서 더 근본적인 문제가 있다는 것을 알게 되었다.

 

💡 기존 환경

용도MySQLRedis

개발 환경 Docker MySQL (hubeleven) DB 0
Spring Boot 테스트 Homebrew MySQL (hubeleven_test) DB 0
k6 부하 테스트 Docker MySQL (hubeleven) DB 0

 

💡 발견한 문제

  • k6 시드 데이터가 개발 데이터와 같은 스키마에 저장됐다.
  • 부하 테스트 중 개발 데이터가 변경되거나 삭제될 수 있었다.
  • Spring Boot 테스트와 개발 서비스가 Redis DB 0을 공유했다.
  • 동일한 재고 락 키가 환경 간 충돌할 수 있었다.
  • Spring Boot 테스트가 Homebrew MySQL과 Docker Redis를 혼합 사용했다.
  • 스키마명 대소문자 규칙이 일관되지 않았다.
  • 이전 테스트 데이터가 다음 테스트에 영향을 줄 수 있었다.

결국 시드 SQL의 중복 문제는 SQL 자체의 문제가 아니라 테스트 데이터와 개발 데이터가 동일한 저장 공간을 사용하는 환경 구성 문제와 연결되어 있었다.


해결 방법

💡 환경 분리 방식 검토

 

환경별 컨테이너를 추가하는 방법과 하나의 컨테이너 안에서 논리적으로 분리하는 방법을 비교했다.

현재 프로젝트는 한 명이 로컬에서 개발, Spring Boot 테스트, k6 부하 테스트를 순차적으로 실행하는 구조이므로 자원 격리보다 데이터 격리가 더 중요했다.

추가 컨테이너와 포트, 볼륨 관리 비용을 늘리지 않기 위해 MySQL은 스키마, Redis는 논리 DB를 사용하는 방식을 선택했다.

용도MySQLRedis

개발 hubeleven DB 0
Spring Boot 테스트 hubeleven_test DB 1
k6 부하 테스트 hubeleven_loadtest DB 2

 

MySQL 스키마명은 운영체제별 대소문자 차이를 방지하기 위해 모두 소문자로 통일했다.

또한 Redisson은 커스텀 설정 때문에 spring.data.redis.database 값을 자동 적용하지 않고 있었다. 따라서 설정값을 직접 주입하여 실제 Redis 연결에 반영하도록 수정했다.

config.useSingleServer()
    .setAddress("redis://" + host + ":" + port)
    .setDatabase(database);

분리 후 장점과 단점

💡 장점

  • 개발 데이터와 테스트 데이터가 완전히 분리된다.
  • Spring Boot 테스트가 개발용 Redis 락 키에 영향을 주지 않는다.
  • k6 시드 SQL을 반복 실행해도 개발 데이터가 변경되지 않는다.
  • Docker Compose 기준으로 테스트 환경을 통일할 수 있다.
  • 별도 컨테이너 없이 데이터 격리를 구현할 수 있다.
  • 테스트 결과의 재현성이 높아졌다.
  • 환경별 설정이 명확해졌다.

💡 단점

  • 데이터만 분리되고 서버 자원은 공유된다.
  • MySQL CPU, 메모리, 커넥션을 공유한다.
  • Redis CPU와 메모리를 공유한다.
  • 개발 서비스와 k6를 동시에 실행하면 성능 측정에 영향을 줄 수 있다.
  • Redis 논리 DB는 Cluster 환경에서는 사용할 수 없다.
  • DB 번호 설정 누락 여부를 별도로 검증해야 한다.
  • 기존 Docker 볼륨은 초기화 SQL이 다시 실행되지 않는다.

따라서 성능 측정 시에는 다른 개발 작업이나 Spring Boot 테스트를 동시에 실행하지 않기로 했다.


문제 해결 결과

💡 환경별 분리 결과

환경 MySQL Redis
개발 hubeleven DB 0
Spring Boot 테스트 hubeleven_test DB 1
k6 부하 테스트 hubeleven_loadtest DB 2

 

 

💡 환경별 분리 후 Gradle 전체 테스트 정상 통과

💡 부하 테스트 환경 적용

echo "=== Config Server가 제공한 loadtest 설정 ==="

curl -s http://localhost:8888/product-service/loadtest |
jq '{
  mysql: [.propertySources[].source["spring.datasource.url"] | select(.)][0],
  redisDatabase: [.propertySources[].source["spring.data.redis.database"] | select(.)][0]
}'

echo
echo "=== 실제 애플리케이션 연결 확인 ==="

docker exec mysql-hubeleven sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -N -e "
SELECT CONCAT(
  \"MySQL hubeleven_loadtest 연결 수: \",
  COUNT(*)
)
FROM information_schema.PROCESSLIST
WHERE DB = \"hubeleven_loadtest\";
"'

docker exec redis-hubeleven sh -c \
'printf "Redis DB 2 연결 수: "; redis-cli CLIENT LIST | grep -c "db=2"'
  • 해당 명령어를 통해 MySQL 연결 대상과 Redis 연결 대상을 확인

  • loadtest 프로필로 Product 서비스를 실행
DB_USERNAME="$(sed -n 's/^DB_USERNAME=//p' .env)" \
DB_PASSWORD="$(sed -n 's/^DB_PASSWORD=//p' .env)" \
SPRING_PROFILES_ACTIVE=prod,loadtest \
SERVER_PORT=18085 \
./gradlew :product:bootRun
  • Config Server 설정값과 실제 애플리케이션 연결 정보를 조회하여 MySQL은 hubeleven_loadtest 스키마, Redis는 DB 2에 정상적으로 연결된 것을 확인
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hubeleven_loadtest?...
  data:
    redis:
      database: 2
  • 이를 통해 부하 테스트 데이터와 Redis 키가 개발 환경과 분리되어 있음을 검증

배운 점

테스트 코드와 시드 SQL이 정상 동작한다고 해서 테스트 결과의 신뢰성이 보장되는 것은 아니었다.

성능 테스트에서는 다음 조건을 함께 관리해야 한다.

  • 테스트 데이터가 개발 데이터와 분리되어 있는가
  • 개선 전후 동일한 데이터와 인프라 조건을 사용하는가
  • 테스트 종료 후 상태가 다음 측정에 영향을 주지 않는가
  • 설정 파일의 값이 실제 클라이언트 연결에 반영되는가
  • 측정 결과가 코드 변경 때문인지 환경 차이 때문인지 구분 가능한가

이번 작업을 통해 성능 최적화 이전에 측정 환경의 재현성과 데이터 격리를 먼저 확보해야 한다는 점을 확인할 수 있었다.