맨 위로 ↑

백엔드 엔지니어

김선우

사용자의 문제에서 출발하는 개발자입니다.

기능을 만들기 전에 사용자가 어디서 불편을 겪는지 먼저 확인합니다. 원인은 기록과 수치로 찾고, 해결은 배포와 운영까지 책임지며, 결과는 다시 사용자 반응으로 검증해 왔습니다.

우아한테크코스 테코톡에서 발표하는 김선우
우아한테크코스, JVM 컴파일러 테코톡

문제 해결 기록

Selected cases
  1. 01LLM 판단과 코드 검증을 분리한 3계층 성분 데이터 파이프라인 구축Poudy
  2. 02자동 롤백 뒤에 숨은 배포 실패 감지를 위한 인프라 모니터링 체계 도입Poudy
  3. 03성분 데이터 관리 개선: 정적 파일 방식에서 승인 및 검증 기반 관리 시스템으로 전환Poudy
  4. 04비관적 락과 상태 지정 API로 좋아요 동시성과 재시도 오류 해결Layer Archive
  5. 05EC2 Docker DB의 데이터 손실 한도를 24시간에서 5분으로 줄이고 복구 리허설로 검증Layer Archive
01Poudy데이터 파이프라인 / 2026.08 – 현재

LLM 판단과 코드 검증을 분리한 3계층 성분 데이터 파이프라인 구축

성분 데이터를 처리 단계별 3계층(L0 원본 보관, L1 정리와 검토, L2 승인 데이터)으로 나누고, 답이 정해진 검증은 코드가, 판단이 필요한 작업만 LLM이 맡도록 역할을 나눴습니다.

데이터 3계층 구조와 LangGraph 검증 흐름

크게 보기 ↗
데이터는 L0 원본 보관, L1 정리와 검토, L2 승인 데이터 세 계층으로 나뉘고, L2에는 승인된 필드만 올라가 서비스 DB로 반영된다. LangGraph 흐름은 근거 찾기, 근거 고르기, 설명과 태그 쓰기, 다른 역할 검토를 거쳐 코드 기반 정책 게이트로 검증한다. 거절되면 추가 조사로 가고, 통과하면 삭제나 낮은 확신도 제안인지 보고 관리자 검수가 필요한지 판단한다. 필요하면 체크포인트를 저장하고 interrupt로 멈춘 뒤 검수 승인 후 재개하고, 필요 없으면 바로 L1 검토 후보가 되며, 승인되면 L2로 올라간다. LLM 호출 오류는 일시 실패면 RETRY_WAIT 후 재시도하고, 반복 실패면 PARKED로 두고 다음 원료로 넘어간다

문제 원인

  • 원료 한 건의 수집, 가공, 검증과 작업 조율까지 Codex 모델 하나에 맡겨, 3일 동안 Pro 사용 한도를 초기화 3번까지 모두 쓰고도(기본 한도의 4배) 서비스에 쓸 만한 데이터를 얻지 못했습니다.
  • 처리와 검증 역할이 나뉘지 않아, 사람이 개입하지 못하는 동안 실패하는 데이터 하나에 걸리면 LLM이 같은 시도를 반복하며 다음 데이터로 넘어가지 못한 채 시간과 토큰만 썼습니다.
  • 원본과 가공 결과가 섞여 값의 출처와 승인 근거를 추적하기 어려웠습니다.

해결 과정

  • 한 건씩이 아니라 전체 데이터를 배치로 처리하도록 수집 파이프라인을 다시 설계하고, 원본, 정리와 검토 후보, 승인 데이터로 나눈 메달리온 구조(L0~L2)를 적용했습니다.
  • 파일 처리, 형식 정리, 중복 검사처럼 규칙이 명확한 일은 Python 코드가, 근거 찾기, 설명 쓰기, 검토처럼 판단이 필요한 일만 LLM이 맡도록 나눴습니다.
  • LLM 단계는 LangGraph로 찾기, 고르기, 쓰기, 검토로 나누고, 결과는 코드로 된 정책 게이트를 통과해야 반영되게 했습니다. 근거가 없거나 확신도가 낮은 제안은 자동 반영하지 않고 관리자 검수로 처리합니다.
  • 작업 상태와 호출 기록을 저장해 성공한 호출은 재사용하도록 설계했습니다. 시간 초과처럼 일시적인 실패는 재시도하고, 다시 해도 같은 결과가 나오는 실패는 사유를 기록해 DLQ처럼 별도 경로로 빼 두어, 에이전트가 멈추지 않고 다음 원료를 이어서 처리하도록 설계했습니다.

결과

  • 원료 22,013건을 같은 규칙으로 처리하는 배치 파이프라인 구축
  • 출처와 승인 이력을 남겨 값이 어디서 왔는지 추적 가능
  • 성공한 호출은 재사용하고 막히는 데이터는 따로 빼 두어, 한 건에 걸려 토큰만 쓰며 멈추던 문제 해소
  • 원료 100건 시험 실행까지 마쳤고, 나머지 원료는 같은 구조로 처리할 예정입니다
02Poudy배포 / 모니터링 / 2026.09

자동 롤백 뒤에 숨은 배포 실패 감지를 위한 인프라 모니터링 체계 도입

자동 롤백으로 배포 실패가 묻히던 문제를 계기로 Prometheus, Grafana, Loki 중심의 모니터링 파이프라인을 도입했습니다. 백엔드 프로세스까지 관측하고 Discord 알림을 연동해, 장애를 팀 전체가 바로 알 수 있는 구조를 마련했습니다.

모니터링과 알림 변경 전후

크게 보기 ↗
변경 전: 팀이 새 버전을 배포하면 CodePipeline과 CodeDeploy가 백엔드 EC2에 설치하고 시작하지만, 파일 누락으로 기동에 실패해 이전 버전으로 롤백되고 정상 동작한다. CloudWatch는 EC2 상태와 자원만 확인해 정상으로 보고, 팀은 배포 실패 알림을 받지 못해 48시간 뒤에야 수동으로 확인했다. 변경 후: 서버별 Alloy가 지표와 로그를 Cloudflare Tunnel로 보내 로그는 Loki, 지표는 Prometheus에 쌓이고, Blackbox가 공개 주소를 확인한 결과도 Prometheus로 간다. Grafana 알림 규칙은 조건이 2분 이어지면 팀 Discord로 알리고, 스테이징에서 2~3분 안에 수신을 확인했다

문제 원인

  • 검색어 데이터 파일을 S3에 올려 두지 않아 스테이징부터 백엔드가 기동하지 못했지만, 배포 파이프라인이 자동으로 롤백해 화면은 정상으로 보였습니다.
  • 팀 누구도 새 코드가 반영되지 않은 것을 몰라, 48시간 동안 배포 실패와 롤백이 반복됐습니다.
  • 기존 모니터링(CloudWatch, SNS 이메일)은 운영 EC2가 살아 있는지만 봐서, 서버는 살아 있고 백엔드 프로세스만 멈춘 장애를 감지하지 못했습니다.

해결 과정

  • 예외 처리 개선은 팀에 제안하고, 이와 별개로 프로세스 수준 장애를 바로 감지하도록 모니터링을 다시 설계했습니다.
  • Prometheus, Loki, Grafana와 Alloy로 프로세스, systemd 상태, 서버 자원, 로그를 한곳에서 관측하도록 구성했습니다.
  • 운영만 보던 범위를 스테이징까지 넓히고, Blackbox로 공개 주소에 실제로 접속되는지 외부에서 확인했습니다.
  • 장애 기준별 Grafana 알림 규칙 9개를 두고, 담당자 이메일 대신 팀 공용 Discord 채널로 알림을 모았습니다.

결과

  • 스테이징에서 백엔드를 멈췄을 때 2~3분 안에 팀 Discord로 알림이 오는 것을 확인 (이전 장애는 발견까지 48시간)
  • 관측 범위를 EC2 생존 여부에서 프로세스, systemd, 자원, 외부 접근 상태까지 확대
  • 각 서버에 접속하지 않고 Grafana 한 화면에서 로그와 주요 지표 확인
03Poudy데이터 관리 / 2026.08 – 현재

성분 데이터 관리 개선: 정적 파일 방식에서 승인 및 검증 기반 관리 시스템으로 전환

정적 파일 수동 관리로 생기던 데이터 불일치와 검증 없는 반영을 막기 위해 승인 기반 관리자 시스템을 구축했습니다. API 기반 검증과 승인 절차를 도입해, 검증된 데이터만 서비스 DB에 반영되는 구조를 마련했습니다.

관리 방식 변경 전후

크게 보기 ↗
변경 전에는 사람마다 각자 관리한 엑셀 파일과 AI가 검수한 엑셀, JSON 파일을 사람이 수동으로 통합하고, 통합한 엑셀을 변환 스크립트로 서버용 JSON으로 바꿔 사람이 S3에 올리면, 운영 EC2가 시작할 때 systemd가 복사하고 Spring이 읽었다. 변경 후에는 관리자 화면에서 변경 요청 API로 요청하면 요청 검증이 버전과 멱등 키를 확인해, 버전이 다르면 거절하고 같은 멱등 키면 기존 결과를 돌려준다. 통과한 요청은 승인 대기로 가서 반려되거나, 추가 조사 뒤 다시 대기하거나, 승인된다. 승인되면 반영 전 재검사를 거치고, 실패하면 반영을 막고 통과하면 확정 데이터(DuckDB, 이력)에 반영한 뒤 CSV와 manifest 릴리스 파일을 만들어 검증용 PostgreSQL 서비스 DB에 넣는다

문제 원인

  • 성분, 제품 데이터와 AI 검수 결과가 엑셀과 파일 단위로 흩어져, 최신 상태와 변경 이력을 추적하기 어려웠습니다.
  • 파일을 직접 고치는 방식이라 누가 왜 바꿨는지 남지 않았습니다.
  • 이전 태그 DB와 AI 검수 결과가 달라도 자동으로 구분되지 않아, 어떤 값을 검토해야 하는지 사람이 직접 찾아야 했습니다.
  • 파일 변환, S3 업로드까지 서비스 반영 과정이 수작업이었고, 반영 전 검증 단계도 없었습니다.
  • LLM 검수에 드는 토큰과 처리 시간을 기록하지 않아 병목 지점과 비효율을 파악하기 어려웠습니다.

해결 과정

  • 원료 22,013건의 상태, 변경 내용, 근거, 수정 이력을 확인하고 승인할 수 있는 관리자 화면을 구축했습니다.
  • 모든 변경을 API 요청으로만 처리하고 요청자와 사유를 남겨, 같은 요청이 반복돼도 한 번만 반영되도록 설계했습니다.
  • 이전 태그 DB와 AI 검수 결과를 비교해 불일치를 자동으로 찾아내고, 값은 자동으로 고르지 않고 검토 대상으로 분리했습니다.
  • 승인된 데이터만 확정 데이터로 반영하고, CSV와 manifest로 된 릴리스 파일을 만들어 별도 DB에서 검증하는 절차를 만들었습니다.
  • LLM 호출별 입력, 출력 토큰과 처리 시간을 기록하고 시각화해, 비용이 큰 작업과 병목 구간을 찾을 수 있게 했습니다.

결과

  • 원료 22,013건을 관리자 화면에서 조회, 승인, 릴리스까지 처리하는 운영 흐름 구축
  • 모든 변경의 요청자와 사유가 기록되고, 같은 요청을 다시 보내도 두 번 반영되지 않음
  • 이전 태그 DB와 AI 검수 결과가 다른 1,086건을 자동으로 찾아 검토 대상으로 분리
  • 릴리스 생성, 별도 DB 적재, 조회, 되돌리기까지 검증 (운영 DB 반영은 다음 단계)
  • 호출 기록으로 병목이 코드가 아니라 LLM 호출이라는 것을 수치로 확인
04Layer Archive동시성 / 멱등성 / 2026.07

비관적 락과 상태 지정 API로 좋아요 동시성과 재시도 오류 해결

같은 사용자의 동시 요청에서 나던 중복 오류를 사용자 행 비관적 락(SELECT … FOR UPDATE)으로 순서대로 처리해 없애고, 토글 API를 원하는 상태를 지정하는 방식으로 바꿔 재시도해도 결과가 같도록(멱등성) 했습니다.

동시 요청 처리와 재시도 결과 비교

크게 보기 ↗
변경 전에는 요청 A와 B가 동시에 좋아요가 있는지 DB에 물어 둘 다 없음을 받고, 둘 다 INSERT해 A는 성공하고 B는 중복 키 오류로 실패한다. 변경 후에는 A와 B가 SELECT user FOR UPDATE로 사용자 행을 잠그려 하고, B는 잠금을 기다린다. A가 저장 후 커밋하면 B가 잠금을 얻고 liked=true를 보내지만 이미 좋아요라 변경이 없어 좋아요 1개, 오류 없음으로 끝난다. 같은 요청을 재시도하면 토글 API는 켜짐에서 꺼짐으로 뒤집히지만 상태 지정 API는 켜짐 그대로다

문제 원인

  • 좋아요를 "조회 후 추가 또는 삭제"로 처리해, 같은 사용자의 요청이 동시에 들어오면 둘 다 "없음"으로 읽고 추가하려다 중복 제약 오류가 났습니다.
  • 토글 방식이라 응답이 늦어 사용자가 다시 누르면, 같은 요청이 방금 누른 좋아요를 오히려 지웠습니다.
  • 추가만 별도 트랜잭션으로 떼어 오류를 흡수하던 방식은, 실패 시 API 응답과 실제 데이터가 달라질 수 있었습니다.

해결 과정

  • SELECT … FOR UPDATE로 사용자 행을 잠가 같은 사용자의 동시 요청을 순서대로 처리하도록 했습니다.
  • 토글 API를 liked: true/false처럼 최종 상태를 지정하는 멱등 API로 바꿨습니다.
  • 좋아요 처리 전체를 하나의 트랜잭션으로 묶어, 실패하면 함께 롤백되도록 했습니다.
  • 변경 단계별로 동시 요청과 재시도 테스트를 다시 실행해, 잠금과 API 변경의 효과를 각각 검증했습니다.

결과

  • 같은 사용자의 요청 5개를 동시에 50번 보냈을 때 실패 200건 → 0건 (250건 중)
  • 같은 요청을 다시 보냈을 때 좋아요가 유지된 비율 0% → 100% (50번)
  • 재시도가 안전해져 화면의 재조회와 지연 재확인 로직을 제거

측정 환경: PostgreSQL 16, 요청마다 별도 트랜잭션으로 서비스 호출

05Layer Archive백업 / 복구 / 2026.09

EC2 Docker DB의 데이터 손실 한도를 24시간에서 5분으로 줄이고 복구 리허설로 검증

수강생 15~20명 규모에 맞춰 RDS 대신 EC2의 Docker에 DB를 두는 대신, 백업을 pgBackRest WAL 아카이빙으로 바꾸고 복원 리허설로 복구 시간을 직접 측정했습니다.

장애 가정 복구 흐름

크게 보기 ↗
① 기존에는 EC2 Docker의 Postgres를 하루 1회 pg_dump로 S3에 백업해 최대 24시간 데이터 손실이 가능했고 복원 여부도 검증하지 않았다. ② pgBackRest로 전체 주 1회, 증분 매일, WAL 5분 이내 아카이빙을 영구 삭제 불가능한 S3에 구성했다. ③ 기존 EC2가 없다고 가정하고 새 EC2 기동 17초, 부트스트랩 60초, 설정과 이미지 18초, DB 복원 18초, 백엔드 배포 29초를 거쳐 health를 확인했다. ④ 데이터 손실 한도 24시간에서 5분(실측 132초), DB 복원 약 10초, 전체 복구 약 2분 20초. 첫 리허설 실제 소요는 7분 13초로, 저장소 인증 실패와 복원본 WAL의 운영 백업 오염을 발견해 복구 절차서에 반영했다

문제 원인

  • 비용을 줄이기 위해 RDS 대신 EC2의 Docker에 Postgres를 운영했고, 하루 1회 pg_dump 백업으로는 최대 24시간의 데이터를 잃을 수 있었습니다.
  • 백업 파일이 실제로 복원되는지 검증하지 않았고, 절반이 잘린 덤프도 pg_restore --list 검사를 통과해 백업 성공만으로는 복구 가능성을 알 수 없었습니다.
  • 관리형 DB가 아니므로 EC2가 통째로 사라졌을 때 서비스를 다시 세우는 복구 절차도 직접 준비해야 했습니다.

해결 과정

  • EC2 운영은 유지하되, 핵심 목표를 데이터 손실 한도 축소와 실제 복구 가능성 검증으로 잡았습니다.
  • pgBackRest로 전체, 증분 백업과 WAL 아카이빙을 S3에 구성해 데이터 손실 한도를 5분으로 줄였습니다.
  • 특정 시점 복원(PITR)과 새 EC2 전체 복구를 직접 리허설하고, 막힌 지점과 복구 시간을 측정했습니다.
  • 리허설에서 발견한 저장소 인증 실패와 WAL 오염 문제를 고쳐 복구 절차서에 반영했습니다.

결과

  • 데이터 손실 한도 24시간 → 5분 (실측 132초)
  • DB 복원 약 10초, 새 EC2 전체 복구 약 2분 20초 (막힌 지점을 포함한 첫 리허설 실제 소요는 7분 13초)
  • 추가 고정 비용은 S3 저장료만 발생하며, 리허설 비용은 $0.013 수준으로 분기마다 반복 검증 가능

측정: 2026-09-01~02, DB 30.6MB, DNS와 TLS 전환 제외

프로젝트

Projects

Poudy

2026.08 – 현재
우아한테크코스 팀 프로젝트
기능 기획, 성분 데이터 설계, 마케팅, 인프라 담당

화장품 전성분 기반 성분 분석과 맞춤형 뷰티 정보를 제공하는 서비스. 웹과 WebView 기반 Android 앱으로 운영하며 릴리즈를 이어가는 중

Poudy 전체 구조

크게 보기 ↗
사용자 요청은 Frontend EC2의 Nginx와 Next.js를 거쳐 Backend EC2의 Spring Boot로 가고, Spring Boot는 DB EC2의 PostgreSQL과 피드백 이미지용 S3를 사용하며, DB는 매일 S3에 백업된다. Frontend와 Backend EC2의 Alloy가 지표와 로그를 Cloudflare Tunnel을 거쳐 Monitoring EC2의 Prometheus와 Loki로 보내고, Blackbox 확인 결과와 함께 Grafana에 모인다. 배포는 GitHub, CodeBuild, CodeDeploy 순으로 FE, BE EC2에 이루어지고, 실패하면 이전 버전을 다시 배포한다. 데이터 파이프라인은 공공데이터와 엑셀을 LLM과 함께 L0~L2로 정제하고, 관리자 화면의 승인을 거친 데이터를 배포용 묶음으로 만들어 PostgreSQL에 반영한다

Spring Boot, Next.js, PostgreSQL, Nginx, systemd, CodePipeline, CodeBuild, CodeDeploy, S3, Prometheus, Loki, Grafana, k6
운영: EC2 5대, 제품 약 1,000건, 원료 약 2만 건
설계 기준: 목표 DAU 500명의 예상 피크 약 0.4 req/s(가정) 대비, 백엔드 t4g.small 1대로 16 req/s(약 40배)를 10분간 오류 없이 처리(p95 약 61ms, 스테이징)

Layer Archive

2026.05 – 현재
개인 개발

일러스트 강사의 의뢰로 만든 그림 수업 관리 시스템(LMS) 겸 게임화 커뮤니티

배포 환경에서만 나던 목록 조회 오류를 서버 로그로 추적해 DB 종류에 따른 타입 추론 차이를 찾고, DB에 민감한 테스트를 실제 PostgreSQL(Testcontainers)로 옮겨 재발을 막았습니다. 목록 조회의 N+1은 목록 전용 DTO로 없애 게시글 50건 기준 쿼리를 53회에서 4회로 줄였습니다.

Java 21, Spring Boot 3.4, PostgreSQL, Next.js 15 / 배포: Vercel(프론트), EC2 Docker(백엔드, DB), GitHub Actions
대상: 수강생 15~20명 (오픈 준비 중)

LinkZip

2024.07 – 2024.08
백엔드 리드 (BE 4명)

링크를 저장하면 LLM이 핵심 내용을 요약해 함께 보여주는 URL 아카이빙 서비스

Express.js, MySQL, Redis, OpenAI API, AWS

IT 뉴스 RAG 챗봇

2025.03 – 2025.06
개인 개발

LangChain 기반으로 벡터 DB, LLM, 웹 UI를 구성한 뉴스 질의응답 챗봇과 졸업논문

Python, LangChain, ChromaDB, Solar LLM, Streamlit

사용하는 기술

Skills

BACKEND

  • Java
  • Spring Boot
  • Spring Security
  • JPA
  • NestJS
  • Express.js

FRONTEND

  • TypeScript
  • Next.js
  • React

DATA

  • PostgreSQL
  • MySQL
  • Redis
  • ChromaDB

INFRA / TEST

  • AWS
  • Docker
  • GitHub Actions
  • nginx
  • PM2
  • JUnit
  • Testcontainers

AI

  • RAG
  • LangChain
  • Claude Code
  • Codex

경력/교육

Experience

경력

  • CEM Ware | STEAMuP 개발팀 인턴 2025.09 – 2025.12

    수학, 과학, 코딩 교육 플랫폼 STEAMuP의 신규 기능 QA와 GitLab 이슈 트래킹, 코딩 교육 콘텐츠 개발

교육

  • 우아한테크코스 8기 Backend 2026.02 – 2026.12 (수료 예정)
    • 4개월간 10개 미션을 통해 객체 간 책임 분배와 계층별 역할 분리 설계 역량 강화
    • 5번의 페어 프로그래밍과 현업자 코드 리뷰를 통해 행위 중심 객체 설계와 예외 처리 습관 형성
    • JVM의 바이트코드 실행과 JIT 컴파일 최적화 원리를 주제로 테코톡 발표 진행
    • 소프트 스킬 워크숍 리드를 맡아 건강한 협업을 위한 피드백과 소통 역량 강화
  • 인하대학교 정보통신공학 학사 2019.03 – 2026.02

수상/자격

Credentials

수상

  • UMC 6기 데모데이 대상 (1위)NEODINARY | 2024.08 | LinkZip

자격

  • 정보처리기사한국산업인력공단 | 2026.09
  • SQLD (SQL개발자)한국데이터산업진흥원 | 2022.09
  • 리눅스마스터 2급한국정보통신진흥협회 | 2022.04

Contact

이메일로 편하게 연락 주세요.

sunwoo005@naver.com ↗

다이어그램

PNG 저장 ↓

확대한 이미지는 좌우로 스크롤해 볼 수 있습니다. Esc 키로 닫기