박재홍

Backend Engineer · Java / Spring

박재홍

업무 규칙을 데이터 구조와 API로 옮기고, 그 규칙이 동시 요청·재시도·배포 실패 속에서도 지켜지는지 테스트와 운영 지표로 확인합니다.

34Java 오픈소스 머지 PR
(31개 프로젝트)
179팀 프로젝트 머지 PR
(위굴 102 · Beach 77)
201×1 409×9동시 예약 10건에서
DB row 1건 보장
44→4N+1 제거 후
목록 조회 쿼리 수
00

기술

Backend

  • Java 21
  • Spring Boot
  • Spring MVC
  • Spring Security
  • JPA
  • JWT · OAuth

Data

  • PostgreSQL
  • PostGIS
  • Redis
  • Flyway
  • DB 제약조건 · Trigger

Test

  • JUnit 5
  • MockMvc
  • Testcontainers
  • JaCoCo
  • 부하 테스트

Delivery

  • GitHub Actions
  • Docker
  • GHCR
  • AWS EC2
  • GCP VM
  • Caddy · systemd

Observability

  • Micrometer Tracing
  • OpenTelemetry
  • Tempo
  • Loki · Alloy
  • Grafana
01Project

위굴 Backend Google Play 출시

충동구매 직전에 예산과 셀프체크로 구매를 한 번 더 점검하게 하는 소비 의사결정 서비스입니다. 인증부터 위시 저장, 구매 결정, 리마인더와 회고까지 이어지는 백엔드와 배포·운영을 담당했습니다.

기간
2026.03 – 2026.09
역할
백엔드 · 배포/운영
머지 PR
102건
저장소
SaGongSa-404/404_BE
  1. 인증OAuth · JWT
  2. 위시 저장직접 입력 · 쇼핑 링크
  3. 구매 숙려예산 · 셀프체크
  4. GO / STOP결정과 상태 전이
  5. 리마인더7일 후 FCM 알림
  6. 회고구매 결과 기록

A구매 결정 트랜잭션과 DB 불변조건

문제
구매 결정 한 번에 위시 상태, 월 예산, 리마인더, 결정 이벤트가 함께 바뀝니다. 일부만 반영되면 예산과 결정 결과가 어긋납니다.
해결
  • 결정 관련 변경을 하나의 트랜잭션으로 처리하고, 결과 조회 시 최신 상태가 아니라 결정 당시 이벤트를 반환
  • 서비스 검증에 더해 CHECK·UNIQUE·partial index·trigger로 음수 금액, 셀프체크 범위, 활성 중복 데이터를 DB 레벨에서 차단
검증
H2 대신 Testcontainers PostgreSQL로 Flyway 마이그레이션, JSONB, partial index, trigger를 실제 DB에서 검증

B재시도로 생기는 중복 저장 방지

문제
같은 상품을 다시 고민하는 건 정상 요청이지만, 더블탭이나 timeout 재시도로 같은 요청이 두 번 저장되면 안 됩니다. 내용 기반 중복 제거로는 둘을 구분할 수 없었습니다.
해결
Idempotency-Key 계약과 사용자별 partial unique index로 "같은 요청의 재전송"만 걸러내고, 같은 내용의 새 요청은 허용
create unique index uk_saved_items_user_idempotency_key
    on saved_items(user_id, idempotency_key)
 where idempotency_key is not null;
같은 key → 기존 결과 반환key 없음 → 새 항목URL 상품 → 기존 중복 규칙 유지

C쇼핑 링크 가져오기를 비동기 작업 큐로 전환

문제
상품 링크 분석은 브라우저(Playwright) 크롤링이라 느리고 무겁습니다. 동기 API로 두면 요청이 몰릴 때 서버 스레드와 브라우저 자원이 함께 묶입니다.
해결
  • PostgreSQL 작업 테이블(PENDING → RUNNING → SUCCEEDED/FAILED)과 FOR UPDATE SKIP LOCKED 기반 worker로 비동기 처리, 202 + Location 응답
  • 전체 큐 100개·사용자별 활성 작업 상한, 포화 시 429. 중단된 RUNNING 작업 재시도와 최대 시도 후 실패 처리
  • 여러 사용자의 같은 URL 요청은 대표 작업 1건만 크롤링하고 결과를 전파, 성공 5분·실패 30초 단기 캐시
  • 외부 브로커 대신 DB 큐를 고른 이유와 재검토 조건을 ADR로 기록
검증
작업 큐 통합 테스트와 전체 554개 테스트 통과, 전후 비교용 부하 테스트 도구(p50/p95/p99·오류율) 작성

DN+1 쿼리 제거

문제
마이페이지 목록 20건을 조회할 때 게시글마다 투표·작성자·상품을 따로 불러와 쿼리가 수십 번 실행됐습니다.
해결
게시글별 투표 조회를 단일 배치 쿼리로 바꾸고, 투표 목록은 게시글·작성자·상품을 fetch join으로 함께 로딩
내 게시글 24 → 5 쿼리투표 목록 44 → 4 쿼리

EHealth Check 통과 후에만 트래픽을 넘기는 배포

문제
단일 GCP VM에서 앱을 교체하면 새 버전이 뜨지 않을 때 서비스가 그대로 중단됩니다.
해결
  • 새 JAR를 8081에 먼저 띄워 /health 통과 시에만 Caddy를 전환하고, 8080 교체 후 복귀. 어느 단계에서 실패해도 기존 프로세스가 트래픽을 계속 처리
  • 배포 전 prod profile·secret·redirect 설정을 검사해 fail-fast, FCM 자격증명은 systemd drop-in으로 런타임 주입
  • 운영 중 API 폭주와 로그인 공격에 대비해 경로별 요청 제한(429)과 차단 지표 추가
  1. Build · Test
  2. →
  3. 8081 후보 기동
  4. →
  5. /health 통과
  6. →
  7. Caddy 전환
  8. →
  9. 8080 교체 · 복귀
02Project

Beach Complex

해수욕장 혼잡도·날씨 정보를 수집해 위치 기반으로 보여주고, 예약과 알림을 제공하는 서비스입니다. 백엔드와 AWS 배포, 관측 환경을 담당했습니다.

기간
2025.10 – 현재
역할
백엔드 · 인프라
머지 PR
77건
저장소
Beach-complex/Beach_complex

A동시 예약 요청에서 1건만 저장

문제
같은 사용자·해수욕장·시간의 예약이 동시에 들어오면, 서비스의 사전 조회만으로는 경쟁 조건을 막을 수 없습니다.
해결
DB UNIQUE 제약을 마지막 방어선으로 두고, 충돌을 500이 아닌 409 ProblemDetail(RESERVATION_DUPLICATE)로 변환
검증
10개 스레드 동시 요청을 MockMvc·실제 repository·고정 Clock으로 재현해 HTTP 응답과 최종 저장 상태를 함께 확인. 시간 경계(400), 인증(401), 타인 예약 취소(404), 취소 후 재예약도 검증
201 × 1409 × 9DB row × 1

B외부 데이터 주기 수집과 상태 모델

해결
  • 30분 주기 스케줄러로 해수욕장별 혼잡도·날씨를 수집하고, 시간별 관측 이력(BeachCondition)과 조회용 현재 상태(Beach.status)를 분리
  • 응답이 없거나 코드가 누락된 대상만 건너뛰어 한 곳의 실패가 전체 수집을 멈추지 않도록 처리
검증
Clock을 주입해 스케줄러 시각을 고정하고 경계 조건을 재현

CAWS EC2 컨테이너 배포 자동화

해결
  • 이미지를 latest와 commit SHA로 태깅해 배포 버전을 추적하고, secret은 이미지 밖 EC2 env 파일로 주입
  • 배포 후 Actuator Health를 재시도로 확인하고, 실패하면 컨테이너 상태와 최근 로그를 바로 출력
  • Firebase가 비활성화된 환경에서 앱이 부팅에 실패하던 문제를 조건부 빈 등록으로 해결
  1. Docker Buildx
  2. →
  3. GHCR · SHA 태그
  4. →
  5. EC2 Compose pull/up
  6. →
  7. Actuator Health

D분산 추적과 로그 연결

문제
HTTP 요청, 비동기 이메일, 스케줄러가 각각 로그만 남겨 하나의 요청이 어디서 실패했는지 따라가기 어려웠습니다.
해결
  • Micrometer Tracing(OpenTelemetry)으로 HTTP·@Async 이메일·SMTP 시도·스케줄러 구간에 span을 만들고 traceId를 로그 MDC와 연결
  • 스케줄러는 실행별 root span과 대상별 span으로 나누고, 결과를 success/skipped/error로 제한해 개인정보·고카디널리티 값 수집을 차단
  • Alloy → Loki 로그 파이프라인을 구성하고 Grafana에서 로그 ↔ Trace를 오가도록 연결
검증
in-memory exporter 기반 통합 테스트로 span 부모 관계와 예외 전파, MDC 정리 확인
03

오픈소스 기여

Java 라이브러리의 엣지 케이스 버그를 찾아 고쳤습니다. 코드를 고친 PR에는 모두 버그를 재현하는 회귀 테스트를 함께 냈고, 각 프로젝트의 전체 테스트와 포맷 규칙을 통과시켰습니다.

34머지된 PR
31프로젝트
6.1k최대 ★
javaparser★ 6.1k

단항 연산자 출력이 식의 의미를 바꾸는 버그

- -x가 --x(감소 연산자)로 출력되는 문제를 두 Pretty Printer와 LexicalPreservingPrinter에서 모두 수정했습니다.

PR #5151 · +217 / −1
jgrapht★ 2.7k

Steiner 트리가 더 비싼 평행 간선을 고르는 버그

경로를 정점 순서로 재구성하면서 가중치 1 대신 10인 간선을 선택하고, 입력 그래프에 없는 간선을 반환하던 문제를 수정했습니다.

PR #1391 · 10개 모듈 7,520 tests 통과

삭제 후 직렬화 결과가 깨지는 버그

Roaring64Bitmap.remove가 bitmap → array 컨테이너 전환을 무시해, 이후 portable 직렬화 왕복이 손상되던 문제를 수정했습니다.

PR #853 · 35,297 tests 통과
streamex★ 2.2k

큰 조합 스트림 분할 시 오버플로

이항계수 계산 중간값이 넘쳐 ofCombinations(64, 32) 같은 병렬 스트림 분할이 멈추던 문제를, 임계값 이상에서만 약분 후 곱하도록 고쳤습니다.

PR #288 · 3개 변형 1,488 executions 통과
머지된 PR 전체 보기 (34)