이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Spring, MySQL, Redis를 Compose로 묶을 때 어떤 설정 계약이 필요한가?
  • 로컬 MySQL/Redis 환경이 운영 장애 학습에 어떤 도움을 주는가?
  • 로컬 의존성 장애를 재현할 때 무엇을 조심해야 하는가?

개요

Spring 백엔드 서비스는 혼자 동작하지 않는다.

대부분 MySQL 같은 영속 저장소와 Redis 같은 cache/session/message 의존성을 가진다.

Docker Compose는 이 의존성을 로컬에서 반복적으로 띄워 개발, 테스트, 운영 장애 학습의 공통 기반을 만든다.

중요한 것은 container를 띄우는 것이 아니라 Spring 설정과 service name, port, 초기 데이터, migration 순서를 맞추는 것이다.

Compose 예시

services:
  api:
    build: .
    env_file:
      - .env.local
    ports:
      - "8080:8080"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
 
  mysql:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: app-password
      MYSQL_ROOT_PASSWORD: root-password
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 20
 
  redis:
    image: redis:7.2
    ports:
      - "6379:6379"
 
volumes:
  mysql-data:

이 예시는 local 환경 기준이다.

운영 Secret이나 운영 DB endpoint를 넣으면 안 된다.

Spring 설정

Spring container 안에서는 MySQL과 Redis를 service name으로 바라본다.

spring.datasource.url=jdbc:mysql://mysql:3306/app
spring.datasource.username=app
spring.datasource.password=app-password
spring.data.redis.host=redis
spring.data.redis.port=6379

host에서 실행하는 Spring app이 Compose MySQL에 붙는 경우는 다르다.

그때는 host port인 localhost:3306을 쓸 수 있다.

Spring을 container로 실행하는지, host JVM으로 실행하는지에 따라 설정 파일을 분리해야 한다.

Migration과 초기 데이터

로컬 DB는 애플리케이션 migration을 검증하는 좋은 장소다.

Flyway나 Liquibase가 있으면 Compose MySQL을 띄운 뒤 Spring startup에서 migration을 실행할 수 있다.

주의할 점은 이전 로컬 volume에 과거 schema가 남아 있을 수 있다는 것이다.

테스트가 이상하게 실패하면 DB 데이터가 깨끗한지 먼저 확인한다.

docker compose down -v
docker compose up -d mysql redis
docker compose up api

단, down -v는 로컬 DB 데이터를 삭제한다.

팀 문서에는 언제 reset해야 하는지와 어떤 seed data가 다시 들어가는지 적어야 한다.

Redis 사용 기준

Redis는 cache, session, rate limit, idempotency key 같은 기능에 쓰일 수 있다.

로컬 Redis가 있으면 TTL, key prefix, serialization, cache eviction을 개발 중 확인할 수 있다.

docker compose exec redis redis-cli keys '*'
docker compose exec redis redis-cli ttl some:key

운영 Redis와 달리 local Redis는 persistence, memory policy, auth, 분산 구성이 단순할 수 있다.

따라서 로컬에서 통과했다고 운영 Redis 동작을 완전히 보장하지는 않는다.

운영 체크리스트

  • Spring을 container로 띄우는지 host JVM으로 띄우는지 설정을 분리했는가?
  • MySQL URL이 실행 위치 기준으로 맞는가?
  • Redis host가 redis인지 localhost인지 분명한가?
  • migration이 로컬 DB에서 자동으로 재현되는가?
  • seed data와 reset 절차가 문서화되어 있는가?
  • MySQL/Redis image version이 고정되어 있는가?
  • local password가 운영 Secret과 다르다는 점이 명확한가?

장애 신호

  • Spring container가 localhost:3306으로 붙으려다 실패한다.
  • MySQL container는 Up인데 migration이 lock이나 권한 오류로 실패한다.
  • Redis key가 남아 테스트가 이전 상태에 영향을 받는다.
  • 팀원마다 MySQL image version이 달라 collation이나 SQL mode 차이가 난다.
  • 로컬 DB volume에 오래된 schema가 남아 신규 migration 검증이 왜곡된다.
  • 운영 DB dump를 local에 복원했는데 개인정보 처리 기준이 없다.

안전한 완화 조치

DB 연결 실패가 있으면 실행 위치를 먼저 확인한다.

Spring이 container 안이면 service name을 보고, host JVM이면 published port를 본다.

migration 실패는 schema 상태와 migration history table을 확인한다.

Redis 관련 문제는 key prefix와 TTL을 확인하고, 무작정 Redis 전체 flush를 실행하기 전에 대상 DB와 환경을 확인한다.

로컬 reset은 위험도가 낮지만, 운영 명령과 혼동되지 않도록 명령 이름과 prompt를 구분한다.

인프라 담당자와 공유할 자료

로컬 재현 목표: 주문 API DB timeout 재현
구성: Spring container + MySQL 8.4 + Redis 7.2
DB URL: jdbc:mysql://mysql:3306/app
Redis host: redis
초기화: docker compose down -v 후 Flyway migration
차이: 운영은 RDS MySQL 8.0, Redis 분산 구성 사용

로컬 재현 환경은 운영과 같은 점과 다른 점을 같이 적어야 협업 자료로 쓸 수 있다.

실전 팁

  • local DB password는 운영과 절대 같게 만들지 않는다.
  • image version을 명시해 팀원 환경 차이를 줄인다.
  • migration 실패를 로컬에서 먼저 경험하게 만든다.
  • Redis key prefix를 서비스와 환경별로 둔다.
  • 운영 dump를 local에 쓸 때는 마스킹과 접근 권한을 확인한다.

위험 신호!

  • Spring 실행 위치가 바뀌었는데 DB URL을 그대로 둔다.
  • Redis flush 명령을 환경 확인 없이 실행한다.
  • local volume에 남은 오래된 데이터로 테스트한다.
  • 운영 Secret을 .env.local에 복사한다.
  • local Compose가 운영과 같다고 가정한다.

확인 질문

확인 질문

  • Spring container에서 MySQL 주소를 mysql:3306으로 쓰는 이유는 무엇인가?
    • 같은 Compose network 안에서 mysql service name이 MySQL container 주소로 해석되기 때문이다.
  • 로컬 DB volume을 reset해야 하는 경우는 언제인가?
    • migration 검증이나 seed data 재현을 깨끗하게 해야 하거나 오래된 schema가 테스트 결과를 왜곡할 때다.
  • local Redis가 운영 Redis를 완전히 대체하지 못하는 이유는 무엇인가?
    • 운영은 auth, persistence, memory policy, 분산 구성, monitoring 조건이 다를 수 있기 때문이다.

참고 문서