이호영

백엔드 엔지니어

JavaKotlinSpring BootSpring Data JPA PostgreSQLMySQLAWSTerraform

이메일
eric250@naver.com
전화
010-3421-6615
GitHub
github.com/puppywimy

주요 트러블 슈팅

InvestUP주식 초보자를 위한 교육형 모의 주식 서비스

데이터 보존이 필요해진 시점에 Flyway 도입, 8일간 마이그레이션 17건 자동 반영

p.2

READTHEM.md개발자의 도서 리뷰 플랫폼

소셜 로그인 직후 병렬 API 요청 6건 중 5건이 500으로 실패하던 세션 경쟁 조건 해결

p.11

InvestUP주식 초보자를 위한 교육형 모의 주식 서비스

데이터 보존이 필요해진 시점에 Flyway 도입, 8일간 마이그레이션 17건 자동 반영

  1. 문제 정의
  2. 해결 과정
  3. 성과 및 회고

프로젝트 소개

InvestUP주식 초보자를 위한 교육형 모의 주식 서비스

프로그래머스 데브코스 백엔드 10기 최종 프로젝트 최우수상(1위)

진행 기간
2026.8.21 ~ 2026.9.15 (약 4주)
역할
5인 팀 · 인프라 전담, 백엔드(외부 API 연동 · 종목 검색)
기술 스택
Java 21 · Spring Boot 3.5 · PostgreSQL + TimescaleDB · Flyway · AWS EC2 · Terraform
링크
서비스 · GitHub
InvestUP 랜딩 페이지 InvestUP 종목 상세 페이지

01문제 정의

컨테이너 생성 시 1회 schema.sql timescale.sql AWS EC2 · 개발 서버 앱 컨테이너 Spring Boot DB 컨테이너 PostgreSQL TimescaleDB 확장 인프라 담당 수동 DDL

기존 구조

  • PR 병합 시 앱은 개발 서버에 자동 배포, 스키마는 schema.sql, timescale.sql로 관리
  • 스키마 파일을 DB 컨테이너 init 스크립트로 삽입하기 때문에 변경 시 컨테이너 재생성 → 데이터 손실
  • 환율 및 시세 데이터가 쌓이며 재생성 불가 → DDL로 수동 마이그레이션

문제 상황

  • 수동 DDL은 휴먼 에러로 개발 서버와 최신 브랜치의 스키마가 어긋날 위험이 있음.
  • 변경 내용을 가장 잘 아는 작업자가 인프라 담당에게 DDL을 전달하고 결과를 기다려야 함.
  • 서버에서 DDL을 직접 실행해 사전 검증 없이 바로 반영되고, 무엇을 실행했는지 기록이 남지 않음.
  • 스키마 변경 3건이 추가로 예정 → 수동 반영이 반복될 상황

02해결 과정Flyway 도입

Flyway의 문제 해결 방식

데이터를 유지한 채 스키마 변경

컨테이너 재생성 불가 → DDL로 수동 마이그레이션

기동 시 적용되지 않은 마이그레이션만 실행 → 데이터 유지

스키마 드리프트 차단

수동 DDL 휴먼 에러로 인한 스키마 드리프트

모든 환경에 같은 마이그레이션 파일 적용

작업자가 직접 반영

인프라 담당에게 DDL 전달 후 대기

PR에 마이그레이션 파일을 넣으면 배포 시 자동 반영

사전 검증 및 기록

서버에서 직접 실행해 사전 검증·실행 기록 없음

로컬 기동으로 먼저 실행, PR 리뷰를 거쳐 반영, 실행 내역은 마이그레이션 파일(git)과 flyway_schema_history로 추적

02해결 과정도입을 위한 3단계 진행

  1. 1단계

    개발 서버 스키마·데이터를 최신으로 수동 마이그레이션

  2. 2단계

    Flyway가 만든 DB에 데이터만 이관

  3. 3단계

    지속 가능한 백업·복원 구조

02해결 과정도입을 위한 3단계 진행

  1. 1단계

    개발 서버 스키마·데이터를 최신으로 수동 마이그레이션

데이터를 보존하기 위해 기존 데이터를 최신 스키마(V1)로 맞춤.

마지막 수동 마이그레이션

수동 DDL

스키마 추출·비교

개발 서버 DB↔V1로 만든 DB

실제 스키마 드리프트 발견

유니크 제약 누락 → 추가 수동 DDL

동일한 상태 확보

컬럼 순서 외 차이 없음

02해결 과정도입을 위한 3단계 진행

  1. 2단계

    Flyway가 만든 DB에 데이터만 이관

수동 DDL로 맞춘 스키마 대신, Flyway가 처음부터 스키마를 관리하도록 V1으로 DB 생성

데이터만 백업

S3 · 일반 테이블 pg_dump --data-only
캔들 하이퍼테이블 \copy CSV

별도 인스턴스 리허설 → 성공 후 개발 서버에 동일 적용

빈 DB 컨테이너 생성

개발 서버는 기존 DB 삭제 후

앱 기동

Flyway가 V1으로 스키마 생성

데이터 복원 및 확인

* 데이터를 먼저 넣으면 Flyway가 비어 있지 않은 DB로 판단해 실패하므로 앱 기동 후 복원

02해결 과정도입을 위한 3단계 진행

  1. 3단계

    지속 가능한 백업·복원 구조

부트스트랩으로 작성해 인스턴스 교체 시 자동 복원함.

스키마·데이터 백업

인스턴스 교체 전
S3 · pg_dump

부트스트랩 · 인스턴스 교체 시

빈 DB 컨테이너 생성

데이터 복원 및 확인

앱 기동

* 스키마와 함께 백업한 덤프에 flyway_schema_history가 포함되어, 데이터를 먼저 복원해도
Flyway가 이력을 이어받아 이후 마이그레이션만 실행

03성과 및 회고

성과

데이터 손실 없이 이관

종목·일봉 등 약 6.5만 건의 데이터를 Flyway가 관리하는 DB로 옮기고, 이후 인스턴스 교체 시에도 자동 복원

PR만으로 스키마 반영

도입 후 8일간(9/8~9/15) 마이그레이션 17건이 수동 DDL 없이 PR 병합만으로 개발 서버에 반영

작업자가 직접 반영

팀원 5명 모두 마이그레이션을 직접 작성, 인프라 담당에게 DDL을 전달하지 않고 git으로 공유·리뷰

03성과 및 회고

추후 개선 방향

정기 백업 자동화

지금은 인스턴스 교체 직전에만 백업해 예기치 않은 장애에 대비하지 못함.
→ cron 기반 정기 백업 (RDS는 비용 증가로 제외)

CI에서 버전 충돌 감지

병렬 작업이 늘면 같은 버전 번호나 순서 역전이 생길 수 있음
→ PR 단계에서 중복 버전·순서 역전을 검사

배운 점

도입이 늦을수록 이관 비용이 커짐

데이터가 쌓인 뒤 도입해 스키마 최신화부터 데이터 이관까지 3단계가 필요했지만, 데이터가 없을 때는 V1 파일 하나로 끝나는 작업
→ 다음 프로젝트는 첫 스키마부터 Flyway로 관리

수동 DDL은 기록이 남지 않음

무엇을 실행했는지 남지 않아 유니크 제약 누락 같은 드리프트를 비교해 보기 전까지 알 수 없었음.
→ 스키마 변경도 코드처럼 파일로 남기고 리뷰해야 함.

READTHEM.md개발자의 도서 리뷰 플랫폼

소셜 로그인 직후 병렬 API 요청 6건 중 5건이 500으로 실패하던 세션 경쟁 조건 해결

  1. 문제 정의
  2. 해결 과정
  3. 성과 및 회고

프로젝트 소개

READTHEM.md개발자의 도서 리뷰 플랫폼

진행 기간
2026.6.25 ~ 2026.7.10, 2026.7.23 ~ 2026.8.3 (약 3주)
역할
3인 팀 · 팀장, 백엔드(도서 API 연동, 아키텍처 설계)
기술 스택
Kotlin 2.3 · Spring Boot 4 · Spring Security · Spring Session Data Redis · MySQL ·
AWS EC2 · Terraform
링크
서비스 · GitHub
READTHEM.md 메인 페이지 READTHEM.md 내 서재 페이지

01문제 정의

클라이언트 GitHub 서버 1 소셜 로그인 API 요청 2 리다이렉트 & 세션 쿠키 설정 3 소셜 로그인 4 동일 세션 확인 5 GitHub API 요청 6 로그인 성공 인기 도서 조회 API 500 최근 리뷰 조회 API 500 인기 리뷰 조회 API 200 평점 좋은 도서 조회 API 500 리뷰 많은 도서 조회 API 500 내 정보 조회 API 500

기존 구조

  • Spring Security + JJWT: JWT 인증을 사용해 SessionCreationPolicy.STATELESS 설정
  • Spring Security OAuth2 Client: GitHub 소셜 로그인만 지원
    • 로그인 과정에서만 CSRF 방지용 state 값을 세션에 임시 저장

문제 상황

  • 무중단 배포를 위해 다중 인스턴스로 구성하면서 운영 세션 저장소를 톰캣 메모리에서 Redis로 변경
  • 이후 소셜 로그인 직후 메인 화면의 병렬 API 요청 6건 중 매번 1건만 성공, 나머지 5건은 500
  • 실패하는 요청은 매번 달라지고, 로그에는 Session was invalidated와 가끔 ERR no such key가 섞여 나옴.

02해결 과정문제 상황 재현

재현 조건

쿠키를 모두 지운 상태에서 소셜 로그인 → 메인 화면 리다이렉트 직후 병렬 요청

두 에러(Session was invalidated, ERR no such key)의 스택 트레이스를 확인하니 모두 RedisSessionRepository.save에서 발생

02해결 과정원인 추적

스택 트레이스와 프레임워크 코드를 따라가며 원인 파악

JWT 인증(STATELESS)을 쓰는 일반 API 요청이 세션을 저장하고 있었음.

스택 트레이스 확인

두 에러 모두
RedisSessionRepository.save

프레임워크 코드 추적

commitSession에서
현재 세션이 있을 때만 save

병렬 요청이 세션을 가짐

임시 세션도 아닌데
현재 세션이 설정됨

브라우저 요청 확인

요청 헤더에 SESSION 쿠키
→ 6번 응답에서 발급

코드 · commitSession

02해결 과정임시 세션 만료

로그인 완료 시점(6번)에 임시 세션 만료

역할이 끝난 임시 세션 정리

로그인 후에도 임시 세션이 남아 병렬 요청마다 세션 저장

OAuthLoginSuccessHandler에서 임시 세션을 만료하는 한 줄 추가

세션 고정 보호를 끄지 않은 이유

  • 문제의 원인은 보호 전략이 아니라 역할이 끝난 임시 세션이 남아 있던 것
  • 이후 세션을 쓰게 되더라도 세션 고정 보호를 유지할 수 있음
코드 · OAuthLoginSuccessHandler

02해결 과정해결 검증

재현된 증상이 모두 설명되는지 확인

STATELESS인데 6번에서
SESSION 쿠키가 발급된 이유

STATELESS는 Spring Security가 세션에 인증 정보를 저장하지 않게 할 뿐, 다른 곳의 세션 생성은 막지 않음
→ 인증 성공 시 세션 고정 보호가 changeSessionId()로 세션 회전, 응답에 새 SESSION 쿠키 발급

매번 다른 요청이,
두 종류의 에러로 실패한 이유

코드 · save

에러 종류는 세션 회전 시점에 따라 갈림
1번 전 회전 → Session was invalidated
Redis 요청 도중 회전 → ERR no such key

톰캣 메모리에서는
발생하지 않은 이유

톰캣 메모리를 사용했기 때문

→ 재현된 증상 3가지(1건만 성공, 성공 요청이 매번 다름, 에러 2종)가 모두 설명됨

03성과 및 회고

성과

병렬 요청 6건 모두 성공

소셜 로그인 직후 6건 중 5건이 500으로 실패하던 메인 화면 병렬 요청이 모두 성공

다중 인스턴스에서 임시 세션 사용

다중 인스턴스 환경에서도 안정적으로 소셜 로그인의 임시 세션을 사용할 수 있음.

03성과 및 회고

추후 개선 방향

인프라 변경 전 영향 범위 점검

세션 저장소를 바꾸기 전에 세션을 쓰는 곳(OAuth2 state 임시 세션)을 미리 정리했다면 배포 전에 발견할 수 있었음

담당 영역 간 공유

일정상 각자 맡은 기능을 공유할 시간이 없었고, 인증 흐름을 모르는 상태에서 장애를 맞아 구조를 공부하는 데 시간을 씀
→ 각자 작업한 영역을 공유하는 자리 마련

배운 점

스택 트레이스를 잘 확인하자

긴 로그는 피로부터 느꼈는데 중요한 단서였음.
→ 난해한 오류도 단서를 잘 수집하면 설명 가능한 원인을 찾을 수 있음.

저장소를 바꾸면 동시성 조건도 바뀜

톰캣 메모리에서는 문제없던 코드가 Redis에서 경쟁 조건이 됨
→ 외부 저장소로 옮길 때는 병렬 요청 시나리오를 함께 검증