강형준

구름톤 UNIV 3기 · 2024.09 – 2025.01 · 백엔드

Gravit!

게임처럼 CS 지식을 학습하는 서비스 — 약 90명의 유저, 인천대 창업동아리 선정

JavaSpring BootJPAPostgreSQLFlywayDocker

Overview

CS 지식을 리그·랭킹 구조로 학습하는 서비스입니다. 백엔드를 담당하며 대규모 데이터를 가정한 조회 성능 문제와 마이그레이션 인프라 문제를 해결했습니다.

담당 업무

  • 리그·랭킹 등 핵심 도메인 API 설계와 구현
  • 쿼리 성능 분석과 인덱스·실행계획 최적화
  • Flyway 기반 스키마 마이그레이션 체계 구축

성과

  • 유저 약 90명 확보
  • 인천대학교 창업동아리 선정

Trouble Shooting

100만 유저 가정 리그 랭킹 조회 병목 해결

1. 상황 및 문제

유저 100만 명 규모를 가정한 리그 랭킹 조회에서 응답 속도가 크게 저하되었습니다. 랭킹은 서비스의 첫 화면에 노출되는 핵심 조회 경로였습니다.

2. 원인 분석

실행 계획을 확인한 결과 전체 테이블 스캔과 대규모 조인이 발생하고 있었고, 정렬이 메모리를 넘어 디스크 정렬(external sort)로 떨어지고 있었습니다.

3. 해결 단계

  1. Step 1. 복합·커버링 인덱스로 Index Scan 유도

    랭킹 조회 조건과 정렬 키에 맞춘 복합 인덱스를 설계하고, 조회 컬럼을 인덱스에 포함시켜(커버링) 테이블 접근 자체를 줄였습니다. 실행 계획이 Seq Scan에서 Index Scan으로 바뀐 것을 확인했습니다.

  2. Step 2. CTE MATERIALIZED로 상위 10건만 추출 후 조인

    랭킹 페이지에 실제로 필요한 것은 상위 10건뿐이라는 점에 착안해, CTE에 MATERIALIZED 힌트를 주어 10건을 먼저 확정한 뒤 나머지 정보를 조인했습니다. 조인 대상이 10행으로 줄면서 옵티마이저가 Nested Loop Join을 선택하게 되었습니다.

    WITH top_ranks AS MATERIALIZED (
      SELECT user_id, score
      FROM league_ranking
      WHERE league_id = :leagueId
      ORDER BY score DESC
      LIMIT 10
    )
    SELECT r.user_id, u.nickname, r.score
    FROM top_ranks r
    JOIN users u ON u.id = r.user_id;
    상위 10건을 먼저 확정한 뒤 조인 — Nested Loop Join 유도

4. 검증 및 결과

전체 테이블 스캔과 디스크 정렬이 사라지고, 소수 행에 대한 Nested Loop Join으로 실행 계획이 안정화되어 랭킹 조회 응답이 크게 개선되었습니다.

Flyway와 datasource-proxy 충돌 — 마이그레이션 전용 DataSource 격리

1. 상황 및 문제

쿼리 로깅을 위해 datasource-proxy를 @Primary DataSource로 등록하자, 애플리케이션 기동 시 Flyway 마이그레이션에서 UnsupportedDataSourcePropertyException이 발생했습니다.

2. 원인 분석

Flyway가 프록시로 감싼 DataSource를 원본 HikariDataSource로 언랩하려다 실패하는 것이 원인이었습니다. 로깅용 프록시와 마이그레이션이 같은 DataSource를 공유하는 구조 자체가 문제였습니다.

3. 해결 단계

  1. Step 1. @FlywayDataSource로 마이그레이션 전용 DataSource 물리 격리

    마이그레이션 전용 HikariDataSource를 별도로 만들고 @FlywayDataSource로 지정해, 프록시를 거치지 않는 경로를 확보했습니다.

  2. Step 2. 마이그레이션 풀 최적화

    마이그레이션은 기동 시 잠깐만 쓰이므로 minimum-idle=0, idle-timeout=60s, maximum-pool-size=2로 설정해, 마이그레이션이 끝나면 커넥션이 반납되어 유휴 자원을 점유하지 않게 했습니다.

    spring:
      flyway-datasource:
        hikari:
          minimum-idle: 0
          idle-timeout: 60000
          maximum-pool-size: 2
    마이그레이션 전용 풀 — 기동 후 커넥션 자동 반납

4. 검증 및 결과

기동 시 마이그레이션이 정상 수행되고, 쿼리 로깅도 유지되었습니다. 마이그레이션 완료 후 전용 풀의 커넥션이 반납되는 것을 로그로 검증했습니다.