이호영

백엔드 엔지니어

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 · InvestUP

문제 정의

기존 구조

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

문제 상황

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

01 · InvestUP

해결 과정Flyway 도입

Flyway가 해결할 수 있는 것

문제Flyway 도입 후
init 스크립트는 빈 DB에서만 실행 → 재생성 필요기동 시 적용되지 않은 마이그레이션만 실행 → 데이터 유지
수동 DDL 휴먼 에러로 인한 스키마 드리프트모든 환경에 같은 마이그레이션 파일 적용
인프라 담당에게 DDL 전달 후 대기PR에 마이그레이션 파일을 넣으면 배포 시 자동 반영
서버에서 직접 실행해 사전 검증·실행 기록 없음로컬 기동으로 먼저 실행, PR 리뷰를 거쳐 반영, 실행 내역은 마이그레이션 파일(git)과 flyway_schema_history로 추적

01 · InvestUP

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

  1. 1단계개발 서버 스키마·데이터를 최신으로 수동 마이그레이션
  2. 2단계Flyway가 만든 DB에 데이터만 이관
  3. 3단계지속 가능한 백업·복원 구조

01 · InvestUP

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

  1. 1단계
  2. 2단계
  3. 3단계

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

  1. 마지막 수동 마이그레이션 진행
  2. 개발 서버 DB와 V1로 만든 DB의 스키마를 추출해 비교, 실제 스키마 드리프트 발견(유니크 제약 누락) 및 수정
  3. 컬럼 순서 외 동일한 상태 확보

왜

데이터를 보존하려면 기존 데이터가 최신 스키마(V1)에 맞아야 함.

01 · InvestUP

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

  1. 1단계
  2. 2단계
  3. 3단계

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

  1. 스키마가 일치하므로 데이터만 백업(S3) pg data only
  2. 리허설(별도 인스턴스)
    1. 빈 DB 컨테이너 생성
    2. 앱 기동(Flyway가 V1으로 스키마 생성)
    3. 데이터 복원 및 확인
  • 수동 DDL로 맞춘 스키마를 그대로 쓰지 않고, Flyway가 처음부터 스키마를 관리하도록 V1으로 DB를 새로 생성
  • 데이터를 먼저 넣으면 Flyway가 비어 있지 않은 DB로 판단해 실패하므로 앱 기동 후 복원
  • 리허설 성공 후 개발 서버 DB 삭제 및 동일한 방식으로 복원

01 · InvestUP

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

  1. 1단계
  2. 2단계
  3. 3단계

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

  1. 인스턴스 교체(유형 변경 등) 전 스키마와 데이터를 함께 백업(S3) pg_dump
  2. 부트스트랩
    1. 빈 DB 컨테이너 생성
    2. 데이터 복원 및 확인
    3. 앱 기동
  • 부트스트랩으로 작성하여 인스턴스 교체 시 자동 복원
  • 덤프에 flyway_schema_history가 포함되어 복원 후에도 마이그레이션 이력이 이어짐

01 · InvestUP

성과 및 회고

성과

6.5만 건

종목·일봉 등 약 6.5만 건의 데이터를 손실 없이 Flyway 관리 DB로 이관

17건

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

서버 접근 없이

작업자가 인프라 담당에게 DDL·확인 스크립트를 전달하고 결과를 기다리던 과정 제거 → 서버 접근 없이 git으로 공유·리뷰

01 · InvestUP

성과 및 회고

추후 개선 방향

  • 정기 백업 자동화: 지금은 인스턴스 교체 직전 수동 백업이라 예기치 않은 장애에는 대비하지 못함 → cron 기반 백업 (RDS는 비용 증가로 제외)
  • 버전 충돌 사전 감지: 팀 내 공유로 충돌 없이 운영했지만 병렬 작업이 잦아질 경우 CI에서 중복 버전·순서 역전 감지, 타임스탬프 버전 도입 등을 고려

배운 점

마이그레이션 도구의 필요성

  • 스키마 변경도 코드처럼 기록·리뷰·재현되어야 함 → 수동 DDL은 무엇을 실행했는지 남지 않고, 한 번의 휴먼 에러가 드리프트로 이어짐.
  • 스키마 반영이 특정 사람에게 의존하지 않고, 변경을 가장 잘 아는 작업자가 직접 반영할 수 있음.

가능한 한 빨리 도입해야 하는 이유

  • 도입이 늦어질수록 기존 DB를 최신화하고 데이터를 옮기는 이관 비용이 따름.
  • 데이터가 없을 때는 V1 파일 하나로 끝나는 작업 → 초기 도입 비용이 거의 없으므로, 다음 프로젝트에서는 처음부터 도입

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
메인 페이지
도서 상세 페이지

02 · READTHEM.md

문제 정의

기존 구조

  • 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 · READTHEM.md

해결 과정문제 상황 재현

02 · READTHEM.md

해결 과정원인 추적

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

스택 트레이스의 코드를 직접 따라가보니 save는 commitSession에서 현재 세션(getCurrentSession())이 설정된 경우만 호출

근데 임시 세션도 아니고 병렬 요청이 세션을 설정한다는 게 이상했음. 브라우저를 확인해보니 병렬 요청 헤더에 SESSION 쿠키가 담겨 있었고, 6번에서 내려주더라.

SESSION 쿠키가 세션 역할을 한다.

코드 · commitSession

02 · READTHEM.md

해결 과정임시 세션 만료

6번에서 만료시켰더니 해결되었다.

OAuthLoginSuccessHandler에 한 줄 추가

코드 · OAuthLoginSuccessHandler

02 · READTHEM.md

해결 과정해결 검증

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

  • STATELESS는 Spring Security가 세션에 인증 정보를 저장하지 않게 할 뿐, 다른 곳의 세션 생성은 막지 않음
  • 인증 성공 시 기본 전략(세션 고정 보호)이 changeSessionId()로 세션 회전 → 응답에 새 SESSION 쿠키 발급
  • 세션 고정 보호를 끄는 대신 로그인 완료 시점(6번)에 임시 세션 만료를 선택
    • 문제의 원인은 보호 전략이 아니라 역할이 끝난 임시 세션이 남아 있던 것
    • 이후 세션을 쓰게 되더라도 세션 고정 보호를 유지할 수 있음

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

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

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

코드 · save
  • 에러 종류는 세션 회전 시점에 따라 갈림
    • (회전이 1번 전에 일어난 경우) → Session was invalidated
    • (회전이 Redis 요청 도중에 일어난 경우) → ERR no such key
  • → 재현된 증상 3가지(1건만 성공, 성공 요청이 매번 다름, 에러 2종)가 모두 설명됨

02 · READTHEM.md

성과 및 회고

성과

6 / 6

병렬 요청 6건 모두 성공

다중 인스턴스

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

02 · READTHEM.md

성과 및 회고

추후 개선 방향

  • 인프라 변경 전 영향 범위 점검: 세션 저장소를 바꾸기 전에 세션을 쓰는 곳(OAuth2 state 임시 세션)을 미리 정리했다면 배포 전에 발견할 수 있었음
  • 담당 영역 간 공유: 일정상 각자 맡은 기능을 공유할 시간이 없었고, 그 결과 인증 흐름을 모르는 상태에서 장애를 맞음 → 소셜 로그인 구조를 공부하는데 시간을 썼음.
    → 각자 작업한 영역에 대해 공유하는 세션을 갖기는 해야 겠음.

배운 점

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

로그가 길게 나오면 피로부터 느꼈는데 중요한 단서라는 것을 체감하게됨. 아무리 난해한 오류도 단서를 잘 수집하면 설명 가능한 원인을 찾을 수 있음.

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

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