소개

모바일·웹·백엔드·인프라 전반에서 B2B 서비스를 설계·개발·운영해 온 4년차 풀스택 개발자입니다. 결제·정산·ERP 연동 등 비즈니스 핵심 영역을 담당하며, 현재 2개 서비스·9개 저장소를 단독 운영하고 있습니다.

약국을 고객으로 하는 관리 플랫폼과 의약품 도매 커머스를 만듭니다. 돈이 오가는 도메인과 외부 시스템 연동처럼 틀리면 바로 드러나는 영역을 주로 다뤘고, 전제가 맞는지 로그와 데이터로 먼저 확인하는 방식으로 일합니다.

성과

측정 가능한 개선
항목 이전 이후
사업자등록증 OCR 처리시간 524초 0.6~5.8초
배포 러너 점유 시간 2.5~3.5분 30~40초
외부 ERP 원장 대조 매칭률 부분 집계 100%
담당 범위 앱 1개 서비스 2개 · 저장소 9개

각 항목의 배경과 판단 근거는 아래 경력에 정리했습니다.

경력

제이엔제이테크 · 개발팀 대리 · 2022.11 – 현재

YMBP — 의약품 B2B 도매 커머스

2026.07 –

Java 21 · Spring Boot 3.5 · JPA/QueryDSL · MySQL · MSSQL · Redis · Meilisearch · Next.js 15 (App Router) · React 19 · TypeScript · Tailwind · Turborepo · Python (PaddleOCR · FastAPI) · Docker · GitHub Actions

백엔드 1 + 프론트엔드 4 + 자동화 3, 단독 운영

  • 결제·수금 체계 설계·구현 — 외상 거래를 카드 기반으로 전환. 카드 미등록 시 주문을 막는 게이트와 월말 수금 구조를 확정했습니다. PG가 빌링키도 0원 승인도 지원하지 않아, 1만원을 승인하고 즉시 전액 취소하는 방식으로 카드 유효성을 검증했습니다. 주문 차단은 잘못 켜지면 전 회원이 주문을 못 하므로, 게이트를 DB 설정값으로 두어 재배포 없이 전환하고 설정 조회 실패 시 열리도록 했습니다.
  • 사업자등록증 OCR 자동입력 — 가입 시 수기 입력하던 등록증을 자동으로 채웁니다. 524초 → 0.6~5.8초 CPU 추론이 건당 524초로 사용이 불가능해, GPU 추론과 상주 워커로 요청마다 모델을 다시 올리던 구조를 없앴습니다. OCR 서버는 사내망, 서비스는 클라우드에 있어 네트워크가 닿지 않았는데, 등록증은 개인정보라 TLS가 필수인 반면 고정 IP·인증서 발급에 제약이 있었습니다. 공인망을 여는 대신 SSH 역터널을 택해 인바운드 개방·TLS·유동 IP를 한 번에 피했고, 프로세스를 강제 종료해 15초 내 자동 복구를 확인했습니다. OCR이 실패하거나 꺼져 있어도 직접 입력으로 넘어가 가입 자체는 막히지 않습니다.
  • 외부 유통 ERP 원장 기준 마진 집계 배치 — 외부 ERP의 매출 원장과 자사 상품을 대조해 마진을 산출합니다. 전체 기간 매칭률 100% 초기 전제였던 품목코드 조인이 실제로는 무의미하다는 것을 데이터로 확인하고(코드 보유 건이 소수) 품목명과 규격의 복합 키로 바꿨습니다. 마진율 대신 분자와 분모를 저장해 재집계 때 평균의 평균으로 값이 왜곡되는 문제를 없앴고, 정답 대조군을 만들어 채점하는 방식으로 오매칭을 걸러냈습니다. 이 과정에서 @Transactional self-invocation으로 재집계분이 통째로 유실되던 결함을 찾아 고쳤습니다 — 최초 실행은 INSERT라 정상으로 보이고 재실행에서만 드러나는 종류였습니다.
  • 운영 보안 문제 해결 — 로그인할 때 사용자 ID와 이메일이 외부로 나가고 있던 것을 찾아 차단했습니다. 에러 트래킹 도구를 점검하다 퇴사자 개인 계정으로 4개월간 운영 에러가 수집되고 있었고, 그와 별개로 레이아웃에 남아 있던 서드파티 로깅 스크립트가 개인정보를 전송하고 있었습니다. 자사 계정으로 옮기면서 전송 전 스크럽·헤더/쿠키/본문 미수집·세션 리플레이 마스킹을 적용하고, 스크립트를 제거한 뒤 운영 페이지에서 전송 0건을 확인했습니다. 그 밖에 무인증으로 열려 있던 관리 API를 차단하고(외부 200 → 403 실측), 타인 게시글이 조회되던 취약점을 고쳤습니다.
  • 배포 파이프라인 개선 — 프론트엔드 3종의 빌드를 별도 러너로 분리하고 산출물만 전달하도록 바꿨습니다. 2.5~3.5분 → 30~40초 self-hosted runner 한 대를 여러 저장소가 공유해 배포가 직렬로 밀리던 문제였습니다.

그 외 — 의약외품 이커머스(셀러 정산·배송비·프로모션·기획전), 회원 유형·권한 체계(JWT 클레임 기반 경로 차단), 서비스 간 계정 연동(SSO-lite, 서버 간 HMAC 검증), 반품 한도 제도(정책값 5종을 DB 설정으로 분리), 운영자 도구 구축

약매니저 — 약국 관리 플랫폼

2023.01 –

Java 17 · Spring Boot 3.2 · JPA/QueryDSL · MySQL · MongoDB · Redis · Meilisearch · WebSocket · React Native 0.78 (bare) · React 18/19 · TypeScript · Redux Toolkit · Zustand · Vision Camera · CodePush · Firebase(FCM) · Amplitude · NCP · Docker · GitHub Actions

앱·웹·백엔드 3개 파트 담당, 현재 단독 운영

  • 모바일 앱 (React Native) — 카메라로 알약을 세어 재고에 등록하는 약 계수기가 핵심 기능입니다. 약국에서 실제로 쓰이는 업무 도구라 카메라·프린터·바코드가 엮인 기능이 많았습니다. 온디바이스 모델 로드가 실패하는 단말을 위해 대체 경로를 두고, 촬영 화면을 오가며 카메라 리소스가 새던 문제를 수명 관리로 정리했습니다. 라벨 프린터(Nemonic SDK), 바코드·QR 재고 등록, 마약류 점검부도 함께 담당했습니다.
  • 약사·약국 인증 플로우 — 인증 수단 선택, 약국명 검색, 직접 입력, 면허증 촬영 인증. 화면마다 제각각이던 진입점을 인증 상태 기준으로 통일하고, 인증 대상이 아닌 병·의원 근무자는 문의 채널로 분기시켰습니다.
  • Amplitude 이벤트 트래킹 체계 전면 구축 — 페이지·버튼 단위 이벤트를 설계하고 사용자 속성을 연동했습니다. 지표 없이 추측으로 하던 기능 판단을 데이터 기반으로 옮기는 것이 목적이었습니다.
  • 운영 백오피스 (React) — 운영 화면 전반을 구축했습니다. 운영 지표 대시보드를 만들고, 앱 무선배포(CodePush)를 어드민에서 직접 제어하도록 연동했습니다. 배포할 때마다 개발자가 붙지 않아도 되게 하는 것이 목표였습니다. self-hosted runner 전환, pm2 상태 체크 등 배포 자동화도 함께 정리했습니다.

그 외 — 주문·발주, 커뮤니티, 앱 인프라(딥링크·FCM·Firebase Remote Config·Sentry·양 플랫폼 빌드), 디자인 토큰 체계 도입, 도매몰 반품 채널 연동(앱 수정 없이 서버 측만으로 완료)

CountMediQ — 글로벌(미국) 서비스

Next.js 16 (App Router) · React 19 · TypeScript · Supabase · TanStack Query · Firebase · React Native 0.78 · Drizzle ORM (op-sqlite) · react-native-iap · i18n

웹 대시보드 신규 구축 · 모바일 앱

  • 웹 대시보드를 0→1로 구축 (Next.js App Router · TypeScript) — 앱에서 모은 계수 기록을 데스크톱에서 조회하고 보고서로 뽑습니다. 구독 과금 모델이라 요금제와 접근 권한이 제품 구조의 일부입니다. Supabase 인증 기반 보호 라우트에 구글 로그인과 전화번호 OTP를 병행하고, 구독 등급(Pro+) 미달 사용자는 업그레이드 경로로 보냈습니다. 기록 검색·페이지네이션, PDF 보고서·CSV 내보내기, 다국어를 구현하고 조회 인덱스와 초기 렌더를 개선했습니다.

개인 프로젝트

회사 업무와 별개로, 관심 있는 문제를 끝까지 만들어 본 것들입니다. 셋 다 공개 저장소입니다.

jario — 창업할 자리의 경쟁 밀도를 세는 지도

2026

Next.js 16 (App Router) · React 19 · TypeScript · PostgreSQL/PostGIS · Supabase · Zod · Tailwind 4 · Docker Compose · Vercel

자체 테스트 68개 (CI에서 PostGIS 컨테이너 기동)

  • 데이터를 먼저 의심합니다 — 업소의 85%가 다른 업소와 좌표가 완전히 같습니다(건물 단위 지오코딩, 한 지점 최대 1,040곳). 마커 하나가 업소 하나가 아니라, 누르면 그 자리에 뭐가 있는지부터 보여줍니다. 회전율은 업소번호로 세면 30% 부풀려집니다 — 사라진 14,823곳 중 3,469곳은 번호만 새로 발급됨 원본에 개업일도 폐업일도 없어 분기 스냅샷 두 장을 대조했습니다. 번호가 아니라 (상호명, 좌표)로 한 번 더 걸렀고, 같은 계산을 파이썬과 SQL로 각각 해 일치를 확인했습니다. 보정 후에도 이전·상호변경이 섞여 있어 화면에는 “폐업”이 아니라 “사라짐”으로 적습니다.
  • 좌표가 없는 데이터를 지도에 올렸습니다 — 공실률·임대료 조사는 상권을 이름으로만 주고, 구획도는 JPG 이미지라 경계를 쓸 수 없었습니다. 상권 68곳에 좌표를 붙이며 13곳이 동명의 랜드마크로 갔습니다 지도 API의 첫 결과를 쓰면 신촌/이대가 안산자락길, 종로가 경복궁이 됩니다 — 주변 업소가 0~7곳인 산과 궁궐입니다. 우리 데이터의 업소 밀도로 검증해 걸러냈습니다. 경계를 모르므로 “이 자리의 공실률”이라 쓰지 않고, 가장 가까운 상권과 거리를 함께 적어 3km를 넘으면 아무것도 보여주지 않습니다.
  • 느린 원인은 하나가 아니었습니다 — 서울을 더하자 동네 분석이 68초가 됐습니다. EXPLAIN ANALYZE로 네 몫을 갈라 각각 다르게 고쳤습니다. 68s → 0.18~0.22s · 화면이 뜨는 시간 1,752ms → 248ms 인덱스를 못 타는 접두어 조건, 남아 있던 힙 접근, 매 요청마다 69만 행을 훑던 집계, 그리고 DB 쿼리는 289ms인데 응답이 1.9초였던 것 — 서버 리전이 DB에서 멀었습니다. 마지막 몫은 쿼리를 고쳐도 줄지 않아, 재보지 않았다면 인덱스만 붙들고 있었을 겁니다.
  • 컬럼 하나를 채우려다 DB를 두 배로 만들 뻔했습니다 — 회전 정보를 기존 테이블에 붙이려다 먼저 재봤습니다. 691,087행 UPDATE → 350.5MB에서 691.3MB · 무료 티어 여유는 130MB뿐 PostgreSQL은 UPDATE 때 행을 새로 쓰고 옛 행을 남깁니다(MVCC). 이 문장 하나로 운영이 한도를 넘습니다. 기존 테이블은 읽기만 하고 별도 테이블에만 쓰도록 바꿔, 서울·인천 69만 곳에 회전·상권까지 얹고도 395MB / 500MB입니다.

MutantHunter — 증명된 테스트만 제안하는 PR 봇

2026

TypeScript · Node.js · GitHub Actions · ts-morph · Stryker Mutator · Vitest · esbuild

자체 테스트 348개

  • 문제를 바꿨습니다 — “이 diff의 테스트를 써줘”는 정답이 없어 검증할 수 없습니다. 대신 코드를 일부러 망가뜨려 살아남는 지점을 찾고, “이 변형을 잡아내는 테스트를 써줘”로 바꿨습니다. 커버리지는 그 줄이 실행됐는지만 봅니다. 틀렸을 때 누가 알아채는지는 묻지 않습니다. 변형을 주입하면 그 질문에 답할 수 있고, 답이 정해져 있으니 생성된 테스트를 실행으로 채점할 수 있습니다.
  • 증명하지 못한 것은 제안하지 않습니다 — 5개 게이트 중 핵심은 “망가뜨린 코드에서 실패하는가”입니다. 157건 폐기 / 그중 53건은 통과하지만 결함을 못 잡던 테스트 현재 코드에서 멀쩡히 통과하는 그럴듯한 테스트는 얼마든지 만들어집니다. 그런 테스트는 커버리지를 올리고 코드 리뷰도 통과하지만 아무것도 지켜주지 않습니다. 실제로 변형을 적용해 돌려봐야만 갈립니다.
  • 측정 장치를 먼저 만들었습니다 — 같은 조건을 반복해 잡음 폭을 재고, 그 기준으로 앞서 내린 A/B 판정 두 개를 스스로 철회했습니다. 채택률이 25% → 29%로 올랐을 때 개선이라고 적었지만, 아무것도 바꾸지 않은 반복 실행에서도 같은 폭으로 흔들린다는 것을 확인했습니다. 대시보드가 채택률과 잡음 폭을 함께 그리는 이유입니다. 프롬프트 튜닝은 세 번 시도해 전부 효과를 입증하지 못했고, 그 사실도 그대로 남겨뒀습니다.
  • 대상 선택이 3배 차이를 만듭니다 — 외부 오픈소스 3곳에 돌린 결과, 잘 테스트된 저장소에서는 제안이 0건이고 테스트가 없는 코드에서는 7건 중 6건이 채택됐습니다. 잘 관리된 코드에 남은 변형은 어려운 잔여물뿐이라 제안할 것이 없습니다. 그때 침묵하는 것이 올바른 동작입니다 — 증명 못 한 것을 내밀면 메인테이너의 시간을 뺏고 신뢰를 한 번에 잃습니다. 외부 저장소에 처음 돌리면서 자체 환경에 가려져 있던 버그 3건도 함께 드러났습니다.

agent-trace-viewer — 코딩 에이전트 트레이스 뷰어

2026

Next.js 16 (App Router) · React 19 · TypeScript · node:sqlite · Tailwind 4 · Docker

로컬 실행 도구 (네이티브 모듈 없음)

  • 이미 있는 기록을 읽습니다 — Claude Code와 Codex는 모든 세션을 디스크에 남기지만 읽을 방법이 없습니다. 토큰·비용·도구 호출과 그 결과로 만들어진 PR·파일까지 보여줍니다. 에이전트에 계측을 추가하지 않습니다. 남은 로그만 파싱하므로 대상 도구를 건드릴 필요가 없고, 로그가 외부로 나가지도 않습니다.
  • 숫자가 무엇인지 명시합니다 — 비용은 API 정가 환산이지 청구액이 아니라고 화면에 적습니다. 로그에는 토큰 수만 있고 과금 정보가 없습니다. 종량제 키였는지 정액 구독이었는지 로그는 모릅니다. “이 토큰이 공개 요율이었다면 얼마였을까”는 세션을 비교할 때 맞는 질문이지만, 청구서와는 다릅니다. 캐시 배수를 반영해 프롬프트 캐싱이 실제로 아낀 양도 숫자에 들어갑니다.
  • 임계값은 재서 정했습니다 — “활동 시간”은 벽시계 시간에서 유휴를 뺀 값이고, 기준은 5분입니다. 사흘 뒤 이어붙인 세션은 429시간에 걸쳐 있지만 실제 작업은 3시간쯤입니다. 5분과 15분 사이에서 총합이 거의 움직이지 않아, 짧은 공백과 진짜 유휴가 그 지점에서 깨끗하게 갈립니다.
  • 자격증명은 넘기지 않습니다 — Docker 실행 시 로그는 읽기 전용으로 마운트하고, ~/.codex는 통째로 붙이지 않습니다. 그 디렉터리에 auth.json이 있습니다. 트레이스를 읽자고 컨테이너에 자격증명을 줄 이유는 없어, 세션 디렉터리와 이름 색인만 따로 마운트합니다.

기술

Backend
Java 17/21, Spring Boot 3.2–3.5, JPA, QueryDSL, Python, FastAPI, PaddleOCR(GPU 추론), MySQL, MSSQL, PostgreSQL/PostGIS, MongoDB, Redis, Meilisearch
Frontend
TypeScript, React 18/19, Next.js 15/16 (App Router), TanStack Query, Zod, Tailwind, Turborepo
Mobile
React Native 0.78 (bare), Redux Toolkit, Zustand, Vision Camera, Drizzle ORM, Firebase, Amplitude, CodePush
Infra
NCP, Vercel, Supabase, Docker, GitHub Actions, nginx, 무중단 배포, 결제 PG 연동, Sentry
테스트
Vitest, Playwright, node:test, Stryker Mutator, PostGIS 컨테이너 기반 통합 테스트

일하는 방식

전제를 실측으로 확인합니다. 위 마진 집계 배치는 초기 설계 전제 세 가지가 모두 실측에서 틀린 것으로 드러나 방향을 바꾼 작업입니다. 된다고 알려진 것보다 로그와 데이터를 먼저 봅니다.

혼자라서 리뷰어가 없는 상황을 절차로 메웠습니다. 백엔드·프론트·어드민을 동시에 구현할 때는 인터페이스(enum·필드명·에러 코드)를 먼저 고정하고 구현 후 3자 대조로 검증합니다. 이 단계에서 하루에만 필드 불일치 세 건을 배포 전에 걸러낸 날도 있습니다.

위험한 도메인은 끝까지 돌려보고 배포합니다. 운영 DB·실 결제·실 알림 채널에 닿지 않는 테스트 환경 규약을 만들어, 결제와 주문도 로컬에서 전 구간을 검증한 뒤 내보냅니다.

학력

  • 한국방송통신대학교 · 컴퓨터과학과 2025.02 – 재학 (휴학 중)
  • 대구과학대학교 · 건축인테리어학과 (전문학사) 2017.02 – 2022.02

연락처