BACKEND ENGINEER / SELECTED WORK

이현태 포트폴리오

커머스의 회수 배치와 구독 결제, 영상 전송, 부하 테스트를 개발하며 내린 선택과 그 결과를 정리했습니다.

wjdgusxo99@gmail.com / github.com/iflov / LinkedIn

CASE 01에이치투비즈 / 2026.02 / 거래소 미거래 상품 회수 배치

로그는 성공인데 데이터는 절반만 바뀐 회수 배치 바로잡기

담당회수 배치 설계와 개발, 세 서버의 조회 영향 조사, 배포 전 검증 쿼리 작성. 첫 운영 실행 뒤 검증 쿼리 실행은 운영팀이 맡았습니다.

Node.js / MyBatis / MariaDB / PM2 cron

문제
한 건의 회수가 두 테이블 쓰기로 나뉘고 커밋 단위가 달라, 절반만 반영돼도 로그에는 성공으로 남았습니다.
선택
새 상태 값이 닿는 세 서버의 조회를 배포 전에 조사하고, 로그 대신 두 테이블을 대조하는 쿼리를 미리 준비했습니다.
결과
첫 운영 실행에서 로그에 드러나지 않은 복원 누락 4건을 찾아 보정하고, 두 쓰기를 한 트랜잭션으로 묶었습니다.

회수 처리 (수정 후)

  1. 배치판매중 3개월 초과 조회
    UPDATE →
  2. 거래소 상태판매중을 만료회수로
    같은 트랜잭션 →
  3. 보관함 상태등록 전 상태로 복원

배포 후 대조

  1. 검증 쿼리두 테이블 조인
    건별 비교 →
  2. 불일치 추출복원 누락 4건
위는 수정 후 한 건을 회수하는 경로입니다. 첫 운영 실행 때는 두 UPDATE가 서로 다른 연결에서 실행됐습니다. 아래는 배포 전에 준비한 검증 쿼리가 누락을 찾은 경로입니다.

문제

거래소에 올린 상품은 팔리거나 판매자가 취소하지 않으면 판매중으로 계속 남았습니다. 2026-02-03 기준 판매중 4,070건 중 77%가 3개월 이상 거래가 없었습니다. 회수하려면 거래소 상태를 만료회수로 바꾸고 보관함 상태를 등록 전 값으로 되돌려야 했습니다. 첫 구현은 이 두 쓰기를 서로 다른 연결에서 실행해, 한쪽만 반영돼도 로그에는 성공으로 남는 구조였습니다.

선택 01 / 회수 상태는 새 코드로, 영향 범위는 배포 전에 조사

거래소 상태 코드는 Admin, Backend, Watchdog 세 서버가 함께 씁니다. 새 값을 넣으면 그 값으로 분기하는 기존 조회 중 어디는 새 값을 포함하고 어디는 뺍니다. 판매완료 통계에 회수 건이 섞이면 집계가 틀어지고, 회원의 거래 이력에는 회수 건도 보여야 했습니다.

다른 방안 기존 판매취소 코드를 다시 쓰면 조회 필터를 건드리지 않아도 됩니다. 하지만 판매자가 취소한 건과 시스템이 회수한 건을 운영과 CS에서 구분할 수 없게 됩니다.

선택 이유 그래서 신규 코드를 넣고, 그 대가로 세 서버의 매퍼를 파일과 조건 단위로 읽고 영향 없음, 검토 필요, 변경 필요로 나눴습니다. 값을 나열한 in 필터는 새 값을 자동으로 빼고, not in 필터는 자동으로 포함합니다. 회원 거래 이력처럼 새 값이 포함되는 화면은 그게 의도에 맞는지도 이때 판정했습니다.

제약 점검 비용을 배포 전에 한 번 치르는 쪽을 골랐고, 조사 대상은 Admin 4개, Backend 3개, Watchdog 2개 파일이었습니다. 관리자 거래소 목록에 회수 건을 보이게 하는 필터 변경은 수정안을 문서로 남겼고, 실제 반영 여부는 기록으로 확인되지 않습니다.

선택 02 / 배포 후 확인은 배치 로그 대신 데이터 대조로

UPDATE는 조건에 맞는 행이 없어도 예외 없이 끝납니다. 호출이 끝났다는 것과 행이 실제로 바뀌었다는 것은 다른 사실이라, 로그만으로는 두 테이블이 함께 바뀌었는지 알 수 없었습니다.

비교 기준: 실행됐다는 기록과 반영됐다는 사실을 구분할 수 있는가

배포 후 확인은 배치 로그 대신 데이터 대조로: 대안 비교
방안장점부담
배치 로그 신뢰추가 작업이 없음실행과 반영을 구분하지 못함
검증 쿼리로 건별 대조 (채택)로그가 아니라 데이터가 근거가 됨쿼리를 미리 설계하고 배포 때 사람이 실행해야 함

선택 이유 배포 전에 검증 쿼리 5종을 만들고 판정 기준을 정했습니다. 불일치 건 추출과 잔여 만료 대상 조회가 둘 다 0건이면 정상입니다. 그중 하나는 두 테이블을 조인해 등록 시 저장해 둔 복원값과 현재 보관함 상태를 건별로 비교합니다. 첫 운영 실행 뒤 이 쿼리에서 4건이 나왔고, 이 쿼리를 준비해 두지 않았다면 로그만 보고 지나쳤을 건입니다.

제약 검증이 사람의 쿼리 실행에 기대므로 일상 운영에서는 결과를 바로 알 수 없습니다. 다음 단계는 처리 건수와 실패 건수를 알림으로 보내는 것입니다.

첫 운영 실행에서 4건을 찾은 과정

  1. 2026-02-10 첫 운영 실행. 거래소 상태는 대상 전부 만료회수로 바뀌었고, 배치 로그도 모두 처리 완료였습니다.
  2. 운영팀이 미리 준비한 대조 쿼리를 실행했고, 거래소에서는 회수됐지만 보관함 상태가 거래중에 남은 4건이 나왔습니다.
  3. 4건은 같은 날 등록 전 상태로 수동 보정하고, 보정 후 기대값과 일치하는 것을 다시 조회했습니다.
  4. 두 UPDATE를 한 트랜잭션에 넣고, 어느 쪽이든 affectedRows가 0이면 전체를 롤백하도록 고쳤습니다.

롤백된 건은 거래소 상태가 판매중으로 남고, 판매중인 건만 고르는 대상 조회에 다음 실행 때 다시 걸립니다. 별도 실패 큐를 두지 않고 대상 조회 조건이 재처리 조건이 되도록 한 것입니다.

결과

첫 운영 실행에서 사전 스냅샷 3,119건과 실행 시점까지 추가된 8건을 회수했습니다. 로그에 드러나지 않은 복원 누락 4건(약 0.13%)을 데이터 대조로 찾아 같은 날 보정했고, 두 쓰기를 한 트랜잭션으로 묶었습니다. 이제 실패한 건은 판매중 상태로 남아 다음 실행 대상에 다시 잡힙니다.

검증 한계 4건은 수동으로 보정했는데, 그 보정이 원인 판별에 필요한 보관함의 수정 시각을 덮어써 원인은 특정하지 못했습니다. 수정 후 롤백된 건이 다음 실행에서 자동으로 재처리된 것을 관측한 기록은 없습니다.

이현태 / 미거래 상품 회수 배치CASE 01 / 04

CASE 02인휴먼즈 / 2025.04 ~ 2025.11 / 조직 단위 구독 백엔드

결제 결과와 이용 기간이 어긋나는 구독 상태 다루기

담당구독, 결제, 요금제 모듈 설계 및 개발, 개발용 ECS 배포 경로 구성.

NestJS / TypeORM / MySQL / PortOne / AWS ECS

문제
해지 신청과 기간 종료, 외부 결제 결과가 서로 다른 시점에 발생합니다.
선택
해지와 종료 상태를 나누고, PG 예약, 웹훅 반영, 만료 배치의 역할을 구분했습니다.
결과
상태 저장과 예약 실패 시 첫 결제 취소 요청까지 구현했습니다. 중복 통지 차단과 PG 대사는 다음 단계입니다.

결제 반영

  1. PG 예약갱신 결제 실행
    결과 통지 →
  2. 구독 서버웹훅 수신 후 PG 재조회
    조회 결과 반영 →
  3. MySQL구독과 결제 상태 저장

정상 이용 중 해지 신청 → 종료 상태 저장

  1. 해지 요청다음 PG 예약 취소
    취소 성공 →
  2. 해지 예정CANCEL_REQUEST 저장
    종료일 이후 배치 →
  3. 기간 종료UNSUBSCRIBED 저장
갱신 결제는 PG 예약이 실행하고 서버는 거래를 재조회해 반영합니다. 아래는 정상 ACTIVE 구독의 해지 경로입니다. 상태 저장과 전체 서비스의 사용권 차단은 별도의 검증 범위입니다.

문제

해지 신청은 다음 결제를 원하지 않는다는 뜻이지만, 이미 결제한 기간의 종료와는 구분해야 했습니다. 또한 PG의 결제 승인과 서버의 구독 저장은 별도로 일어납니다. 한 단계의 성공 뒤 다른 단계가 실패할 수 있어, 구독 상태와 외부 거래를 연결하는 처리가 필요했습니다.

선택 01 / 해지 의사와 기간 종료를 서로 다른 상태로 저장

6월 1일부터 7월 1일까지인 이용 주기에 6월 20일 해지를 신청한 상황을 예로 들면, 다음 결제를 중지하는 것과 현재 주기를 끝내는 것은 다른 처리입니다. 날짜는 동작 설명을 위한 예시이며 실제 거래 기록은 아닙니다.

표현해야 하는 것: 해지 신청 시점과 주기 종료 시점

해지 의사와 기간 종료를 서로 다른 상태로 저장: 구현 구조 비교
방안장점부담
활성 값 + 날짜 조건활성 여부 조회가 단순하며 날짜 필드로 해지 예정을 표현 가능정상 이용과 해지 예정의 구분을 여러 조건으로 해석해야 함
명시적 구독 상태 (구현)해지 예정과 기간 종료를 서로 다른 값으로 읽을 수 있음상태별 조회 조건과 전이 규칙을 일관되게 관리해야 함

구현과 동작 구현에서는 다음 PG 예약을 취소한 뒤 기존 구독 행을 CANCEL_REQUEST로 바꾸고 다음 결제일을 비웁니다. 예시의 6월 20일에 곧바로 종료 상태를 저장하지 않고, 7월 1일 종료일에 도달한 뒤 배치가 UNSUBSCRIBED로 바꿉니다. 갱신 시에는 새 주기 행을 만들고 이전 행을 RENEWED로 표시합니다. RENEWED는 이전 주기를 구분하는 표식입니다.

관리 비용 상태가 늘어난 만큼 각 API와 조회가 같은 기준으로 현재 구독을 판단해야 합니다. PG 예약 취소가 성공해도 DB 갱신이 실패하면 두 시스템의 상태가 어긋날 수 있습니다. 이전 행도 수정하므로 모든 상태 변화가 독립된 이력으로 보존되는 구조는 아닙니다.

선택 02 / 갱신 결제 실행과 결과 반영, 기간 종료를 분리

갱신 결제는 PG에서 일어나고 구독 주기는 서버 DB에 저장됩니다. 서버가 갱신 시각마다 직접 결제를 요청하는 방식도 가능하지만, 현재 구현은 PG 예약이 실행하고 서버가 결과를 반영하는 구조입니다.

거래 결과를 확인하는 것과 중복 처리를 막는 것은 별도 구현이 필요합니다.

갱신 결제 실행과 결과 반영, 기간 종료를 분리: 구현 구조 비교
방안장점부담
서버가 갱신 결제 요청결제 요청 시각과 재시도를 애플리케이션에서 관리실행 스케줄과 중복 요청 방지를 서버에서 관리해야 함
PG 예약이 갱신 실행 (구현)갱신 실행을 PG에 맡기고 서버는 결과 반영을 담당중복되거나 유실된 통지에 대응하고 로컬 상태에 반영해야 함

구현과 동작 웹훅이 오면 결제 식별자로 PG 거래를 다시 조회합니다. 갱신 성공 경로는 기존 구독을 RENEWED로 저장하고, 다음 결제를 예약한 뒤 새 ACTIVE 구독과 결제 이력을 차례로 저장합니다. 만료 배치는 결제를 요청하지 않고 해지와 연체의 종료 조건을 처리합니다. 첫 신청과 업그레이드의 즉시 결제는 이 갱신 경로와 별개입니다.

다음 단계 PG 재조회는 거래 결과를 확인할 뿐, 같은 통지를 한 번만 처리하게 만들지는 않습니다. 결제 이력의 중복 제약보다 구독 변경과 다음 예약이 먼저 실행되므로, 마지막 저장이 실패해도 앞선 처리는 되돌아가지 않습니다. 당시 범위는 재조회로 결과를 확인하는 데까지였습니다. 중복 통지는 처리 시작 시 결제 식별자를 먼저 확인하거나 선점한 뒤 구독을 바꾸는 순서로, 유실된 통지는 예약 ID로 PG 결제 결과를 조회해 로컬 이력과 맞추는 주기 대사 배치로 다루는 편이 맞습니다.

첫 승인은 성공했지만 다음 예약이 실패한다면

  1. 첫 결제 승인 후 다음 주기의 PG 예약을 요청합니다.
  2. 예약이 실패하면 이미 승인된 결제 ID로 PG에 취소를 요청합니다.
  3. 취소 호출이 성공하면 FAILED 결제 이력을 저장하고 신청 실패를 응답합니다.

취소 호출 자체가 예외로 끝나면 실패 이력 저장까지 가지 않아, 새 결제와 구독 행이 DB에 남지 않을 수 있습니다. 당시 범위는 다음 예약이 실패했을 때 앞선 결제를 취소하는 보상 호출까지였습니다. 이 경우를 추적하려면 실패한 취소를 별도 재처리 상태로 저장해 다시 시도해야 합니다. 로컬의 결제 이력과 구독 이력은 한 트랜잭션으로 묶고, 그 바깥에 PG 취소 호출을 두는 순서가 맞다고 봅니다.

결과

해지 신청과 기간 종료를 나누는 저장 경로, PG 예약, 웹훅 반영, 만료 배치, 첫 신청의 예약 실패 시 취소 요청을 구현했습니다. 개발용 ECS 배포 경로도 구성했습니다.

검증 한계 상용 운영 전에 중단돼 운영 성과 수치는 없습니다. 후속 코드와 Git 조사로 처리 순서를 확인했습니다. 전체 서비스의 이용 차단과 모든 상태 전이, 실패 복구까지 실행해 확인하지는 않았습니다.

이현태 / B2B 구독 결제CASE 02 / 04

CASE 03인휴먼즈 / 2025.04 ~ 2025.11 / 실시간 얼굴 변환 기술 검증

브라우저에서 서버 워커까지 영상 왕복 경로 연결하기

담당영상 전송과 시그널링, MQ와 워커 연결, 컨테이너를 설계하고 개발했습니다. AI 모델은 동료 담당.

Python / FastAPI / aiortc / ZeroMQ / msgpack / Docker

문제
설치형 얼굴 변환을 브라우저로 옮기고, 서버 처리 결과를 원래 연결에 반환해야 했습니다.
선택
영상은 WebRTC, 서버 내부 전달은 ZeroMQ, 프레임과 메타데이터는 MessagePack으로 구성했습니다.
결과
초기 영상 왕복 경로를 구현하고 흑백 변환과 모델 연동 동작을 확인했습니다.

영상 왕복

  1. 브라우저영상 입력과 결과 표시
    WebRTC ↔
  2. 시그널링aiortc / 연결 관리
    ZeroMQ ↔
  3. MQ프레임과 결과 전달
    요청과 결과 ↔
  4. 워커프레임 처리

설정 전달

  1. 브라우저실행 중 설정 변경
    DataChannel →
  2. 시그널링설정 메시지 수신
    msgpack →
  3. MQ사용자별 설정 전달
본인이 구현한 초기 분리 구조를 요약했습니다. 처리 결과는 MQ와 시그널링을 거쳐 브라우저로 돌아옵니다.

문제

기존 얼굴 변환은 설치형 앱에서 실행돼 사용자 장비가 추론 연산을 부담했습니다. 브라우저에서도 이용할 수 있도록 처리를 서버로 옮겨야 했습니다. 이때 영상 프레임과 실행 중 설정을 서버로 보내고, 처리 결과를 원래 연결로 돌려주는 경로가 필요했습니다.

선택 01 / 영상 전송은 WebSocket 대신 WebRTC Media Track으로

서버에서 처리한 영상을 브라우저에 계속 재생해야 했습니다. 현재 움직임에 대응하는 영상이 중요했고, 통신 상대는 다른 사용자가 아니라 추론 서버였습니다.

비교 기준: 미디어 처리 기능을 어디까지 직접 구현해야 하는가

영상 전송은 WebSocket 대신 WebRTC Media Track으로: 대안 비교
방안장점부담
WebSocket양방향 연결 구성이 비교적 단순영상 인코딩과 디코딩과 재생 조절을 별도로 구성
WebRTC Media Track (채택)브라우저의 코덱 협상과 혼잡 제어 활용SDP, ICE, TURN 연결 구성 필요

선택 이유 브라우저의 캡처, 코덱 협상, 혼잡 제어를 활용하려고 Media Track을 선택했습니다. 영상은 Track으로, 실행 중 설정은 DataChannel로 분리했습니다.

제약 미디어 전송 기능을 활용하는 대신 연결 수립과 TURN 구성을 직접 다뤘습니다. WebRTC를 사용해도 서버 추론 시간이나 큐 대기는 별도로 관리해야 합니다.

선택 02 / 서버 내부 메시징은 Kafka나 RabbitMQ 대신 ZeroMQ로

연결 관리와 모델 처리를 한 프로세스에 두면 추론 런타임의 재시작과 WebRTC 연결 관리가 같은 실행 단위에 묶입니다. 초기 구조를 시그널링, MQ, 워커로 나눈 뒤, 그 사이에서 프레임과 결과를 전달할 방법이 필요했습니다.

비교 기준: 과거 프레임 보존보다 워커의 요청에 따라 프레임을 전달하는 경로

서버 내부 메시징은 Kafka나 RabbitMQ 대신 ZeroMQ로: 대안 비교
방안장점부담
Kafka로그 보존과 다시 읽기, 소비자별 처리 위치 관리별도 브로커 운영이 필요하며 과거 영상 재처리는 이번 요구의 중심이 아님
RabbitMQ큐 라우팅과 확인 응답 기반 전달 관리브로커와 전달 정책을 운영해야 하며 필요한 보장 수준을 따로 정해야 함
ZeroMQ (채택)소켓 패턴으로 프로세스를 연결하고 워커 요청 경로를 구성영속 큐, 재전송, 장애 복구를 자동으로 제공하지 않음

선택 이유 사용자의 현재 영상에 대응하는 처리가 중요했으므로 과거 프레임을 보존하고 재생하는 기능보다 프로세스 사이의 전달 경로를 우선했습니다. ZeroMQ로 워커가 다음 프레임을 요청하고 결과를 반환하는 구조를 구현했습니다. 사용자 식별자와 프레임 식별자를 함께 전달해 시그널링이 원래 입력과 결과를 연결하도록 했습니다.

제약 별도 범용 브로커 제품을 운영하지 않는 대신 라우팅과 응답 매칭, 통신 실패 대응을 직접 맡았습니다. 직접 만든 MQ 프로세스는 여전히 존재하며, 이 프로세스가 멈추면 영상 왕복도 끊깁니다. 프로세스를 나눴지만 MQ 장애의 영향까지 막거나 자동으로 복구하지는 못합니다.

선택 03 / 프레임 직렬화는 JSON + Base64 대신 MessagePack으로

시그널링, MQ, 워커 사이에서는 프레임 bytes와 함께 사용자 식별자, 프레임 식별자, 이미지 형태와 자료형 등의 메타데이터를 전달해야 했습니다. 바이너리를 담는 비용뿐 아니라 여러 프로세스가 같은 메시지를 해석할 수 있는지도 중요했습니다.

비교 기준: 원시 bytes를 담으면서 직접 관리할 메시지 형식의 복잡도를 줄이기

프레임 직렬화는 JSON + Base64 대신 MessagePack으로: 대안 비교
방안장점부담
JSON + Base64메타데이터를 텍스트로 확인하기 쉬움프레임 bytes를 문자열로 바꾸면서 데이터 크기와 변환 작업이 늘어남
Raw Binary필요한 필드만 담는 형식을 직접 구성필드 위치와 길이와 파싱 규칙을 직접 설계하고 변경해야 함
Protocol Buffers스키마와 생성 코드로 메시지 구조를 명시스키마 정의와 코드 생성 절차를 함께 관리해야 함
MessagePack (채택)bytes와 메타데이터를 함께 직렬화하며 별도 코드 생성 불필요메시지를 눈으로 읽기 어렵고 필드의 의미와 순서 검증은 별도로 필요

선택 이유 Python 프로세스 사이에서 프레임 bytes와 작은 메타데이터 묶음을 주고받는 용도라 MessagePack을 선택했습니다. Base64 문자열로 바꾸지 않고 bytes를 담되, Raw Binary처럼 모든 필드의 바이트 위치를 직접 파싱하는 방식은 피했습니다. Protocol Buffers의 스키마 정의와 코드 생성 절차 대신 송수신 코드가 메시지 형식에 합의하도록 구성했습니다.

제약 MessagePack이 직렬화할 수 있다는 것과 양쪽이 같은 의미로 해석한다는 것은 다릅니다. 튜플의 필드 순서나 자료형이 어긋나지 않도록 직접 관리해야 하고 디버깅에도 디코딩이 필요합니다. 처음부터 MessagePack을 사용했으며 Base64와 성능을 비교 측정하지는 않았습니다.

한 프레임이 돌아오는 과정

  1. 시그널링이 브라우저 프레임에 식별자를 붙여 MQ로 보냅니다.
  2. 다음 작업을 요청한 워커가 프레임을 받아 처리합니다.
  3. 워커가 같은 식별자를 결과에 실어 반환합니다.
  4. 시그널링이 사용자 식별자와 프레임 식별자로 원래 입력을 찾아 브라우저로 돌려줍니다.

결과

브라우저 입력을 시그널링, MQ, 워커로 보내고 결과를 반환하는 초기 파이프라인을 구현했습니다. 흑백 변환으로 전송 경로를 확인했고, 모델 연동 동작도 협업 과정에서 확인했습니다.

검증 한계 기술 검증 후 출시 전에 중단됐습니다. 지연 시간과 FPS 개선, 워커 확장 효과와 장애 복구 성능을 입증하는 측정 자료는 없습니다.

이현태 / WebRTC 영상 처리CASE 03 / 04

CASE 04인휴먼즈 / 2025.08 ~ 2025.11 / 사내 부하 테스트 플랫폼

부하 생성과 측정 대상을 분리한 테스트 실행 환경 만들기

담당FE, BE, CTO로 구성된 3인 팀에서 백엔드와 실행기, DB 저장, 배포 구성을 담당했습니다.

Next.js / Express / NestJS / k6 / PostgreSQL / InfluxDB / Helm / ArgoCD

문제
문서로 비교하던 부하 테스트를 웹에서 반복 실행해야 했고, 부하를 만드는 자원이 화면이나 측정 대상과 섞이지 않아야 했습니다.
선택
화면 서버와 k6 실행기, 부하 대상 서버를 나누고 실행 이력과 측정값을 서로 다른 DB에 저장했습니다.
결과
시나리오 작성부터 실행과 결과 조회까지 웹에서 처리하도록 구현하고, Helm과 ArgoCD 배포 설정을 작성했습니다.

실행 경로

  1. Control Panel시나리오와 실행 요청
    HTTP →
  2. Runnerk6 자식 프로세스 실행
    부하 요청 →
  3. Mock Server부하 요청 처리와 오류 주입

이력 저장

  1. Control Panel실행 조건과 이력
    저장 →
  2. PostgreSQL실행 이력

측정값 저장

  1. k6측정값 전송
    저장 →
  2. InfluxDB시계열 측정값
맨 위는 실행 요청 경로이고, 아래 두 줄은 서로 독립된 저장 경로입니다. 실행기의 부하와 대상 서버의 처리를 구분해 읽을 수 있도록 구성했습니다.

문제

테스트 조건과 결과를 문서로 비교하던 작업을 웹에서 반복 실행할 수 있도록 플랫폼을 만들었습니다. k6는 별도 바이너리여서 실행 프로세스를 관리해야 했고, 부하 생성 자원이 화면이나 측정 대상과 섞이지 않도록 서비스를 나눌 필요가 있었습니다.

선택 01 / 화면 서버에서 직접 실행하는 대신 Runner를 분리

k6는 별도 바이너리로 실행되므로 웹 요청을 받는 것 외에 스크립트 생성과 자식 프로세스 관리가 필요했습니다. 부하를 만드는 쪽의 자원 소비가 화면이나 부하 대상의 처리와 섞이지 않도록 서버를 따로 배치해야 했습니다.

다른 방안 Control Panel에서 k6를 바로 실행하면 별도 실행 API와 서비스 배포가 줄어듭니다. 반면 테스트가 소비하는 자원과 화면 서버의 자원을 함께 관리해야 합니다. Runner를 별도로 두면 실행 환경을 따로 배치할 수 있지만 네트워크 요청과 서비스 간 장애 처리가 늘어납니다.

선택 이유 Control Panel은 조건 입력과 실행 요청, 이력 조회, Runner는 스크립트 생성과 k6 프로세스 실행, Mock Server는 부하 대상 API와 오류 주입을 맡도록 나눴습니다. Helm과 ArgoCD와 CI 이미지 태그 갱신 경로를 구성하고, 부하 대상 서버를 별도 노드에 배치하는 설정도 작성했습니다.

제약 실행 요청은 실패할 수 있는 HTTP 호출이 됐고 배포하고 모니터링할 서비스도 늘었습니다. 서비스를 나눠도 같은 노드에 배치하면 자원을 공유하므로 노드 배치 설정이 별도로 필요합니다.

선택 02 / Runner는 Express와 수동 DI, Mock Server는 NestJS

Runner는 스크립트를 만들고 별도 k6 프로세스를 관리합니다. Mock Server는 요청에 적용할 오류 주입 설정과 미들웨어 생명주기를 다룹니다. 둘 다 HTTP 서버지만 사용하는 프레임워크 기능의 범위는 달랐습니다.

다른 방안 두 서비스 모두 NestJS를 쓰면 모듈과 의존성 주입 방식을 통일할 수 있습니다. 반대로 모두 Express로 구성하면 스택은 단순해지지만 Mock Server의 상태와 미들웨어 정리도 직접 관리해야 합니다. 통일성뿐 아니라 각 서비스에서 실제로 필요한 기능을 기준으로 비교했습니다.

선택 이유 작은 실행 계층인 Runner는 Express와 수동 DI로 구성해 객체 생성과 의존성 연결을 코드에 드러냈습니다. 상태와 생명주기를 다루는 Mock Server에는 NestJS를 남겼습니다. 측정값은 k6가 InfluxDB로 직접 보내고, 실행 조건과 이력은 Control Panel이 PostgreSQL에 저장하도록 쓰기 경로도 나눴습니다.

제약 Runner에서는 객체 간 의존성 연결과 테스트 격리를 직접 관리하고 두 서비스의 구성 방식도 각각 유지해야 합니다. 두 저장소를 함께 커밋하지 않으므로 실행 성공과 이력 누락이 함께 발생할 수 있습니다. 이력이 없어도 테스트는 실행되므로, 실행 여부만으로 결과 조회를 보장할 수 없습니다.

이력 저장에 실패해도 실행은 계속

실행 요청에서 PostgreSQL 이력 저장이 실패하더라도 Runner 호출은 계속됩니다. 응답의 historyTracked 값으로 이력 기록 여부를 구분하고, 실행된 k6의 측정값은 별도 경로로 InfluxDB에 보냅니다. 따라서 “실행 요청을 받았다”, “이력을 남겼다”, “측정값을 저장했다”는 서로 다른 상태입니다. 이 흐름은 이력 저장소 장애가 테스트 시작을 막지 않도록 한 동작이며, 모든 결과가 조회된다는 보장은 아닙니다.

결과

시나리오 작성부터 실행과 결과 조회까지 웹에서 처리하도록 구현하고 배포 설정을 작성했습니다. 구현은 Runner 한 대에서 동작하며 진행 상태를 Runner 메모리에 둡니다. 재시작 뒤에도 실행을 이어받거나 Runner를 여러 대로 늘리려면, 진행 상태와 실행 슬롯을 공유 저장소로 옮기고 재시작 때 실행 중이던 이력을 별도 상태로 정리해야 합니다.

검증 한계 노드 분리 전후로 자원 간섭을 비교한 측정 기록은 없습니다. 이력 저장 실패 뒤에도 실행이 이어지는 동작은 코드 기준 설명이며, 이번에 회사 시스템을 다시 실행해 확인한 것은 아닙니다.

이현태 / k6 부하 테스트CASE 04 / 04