티캣

Backend

블록체인 티켓과 포토카드 거래를 제공하는 공연 예매 플랫폼

2026.08 ~ 2026.10Blockchain Developer · Backend 성능 개선 · Monitoring6인 팀

SSAFY 우수상(1등)

스마트컨트랙트와 블록체인 API를 구현하고, 예매 서버의 성능 개선·대기실·모니터링을 담당했습니다.

티캣 서비스 화면

핵심 성과

실사용자 확보진입 장벽 개선 · 3주 운영
105명
FDS 재현율합성 평가 · 기존 규칙 37.2%
93.1%
좌석 조회 서버 p953,600 VU · 서버 직접 호출
20ms

시스템 아키텍처

예매 API · 블록체인 API

Spring에서 예매를 처리하고, 내부 Node API에서 지갑과 체인 트랜잭션을 관리합니다.

티캣 전체 시스템 아키텍처

티캣 전체 시스템 아키텍처React에서 nginx를 거쳐 Spring Boot로 요청합니다. Spring은 PostgreSQL, Redis 및 내부 Node.js API를 호출합니다. Caffeine 좌석 캐시는 Spring 내부에 있습니다. Node.js 내부의 HD 지갑과 직렬 트랜잭션 큐가 서명한 트랜잭션을 체인 RPC를 통해 스마트컨트랙트에 전송합니다.CLIENTAPPLICATIONDATABLOCKCHAINHTTPSAPI 요청조회·저장대기실내부 호출서명 Tx · 체인 RPCReact예매 · 거래 화면nginxReverse proxy · keepalivePostgreSQL회원 · 예매 · 거래RedisSorted Set · Lua대기열 · 입장 TTLSpring Boot예매 · 좌석 · 거래 API입장 상태 검증Caffeine · 좌석맵 1초 캐시Node.js · Express지갑 · 서명 · 트랜잭션 내부 APIHD Wallet직렬 Tx QueueSmart ContractsTicket SBT · ERC-20 · ERC-1155 · 추첨
주요 요청 경로만 표시했습니다. 좌석 캐시는 Spring 내부 Caffeine, 대기열·입장 상태는 Redis에 저장합니다.

프로젝트 개요

목표

  • 티켓·포토카드의 온체인 소유·발행 규칙 구현
  • 예매 집중 시점의 부하와 NFT 이상거래를 처리하는 구조 구축
  • 서비스 상태와 비동기 발행 흐름을 확인하는 모니터링 구축

배경

  • 티켓 소유·양도·발행 이력과 NFT 거래 이상 징후의 검증 필요
  • 좌석 반복 조회로 DB·서버·프록시에 집중되는 부하 해소 필요
  • HTTP 응답 이후의 비동기 발행 실패까지 추적 필요
  • 개발·운영 스택이 공유하는 EC2의 모니터링 자원 소비 제한 필요

주요 기능

  • 양도 제한 티켓 SBT, 포인트 토큰, 포토카드 NFT와 검증 가능한 추첨 제공
  • 좌석 조회·선점, 가상 대기실과 비동기 티켓 발행 지원
  • 포토카드 거래의 이상 징후 심사와 관리자 판정·통계 제공

담당 역할

Blockchain Developer · Backend 성능 개선 · Monitoring

blockchain 디렉터리의 스마트 컨트랙트·내부 API 구현 전반과 프로젝트 모니터링 구축을 맡았습니다. 티켓팅 부하 테스트와 성능 개선, 가상 대기실, NFT 거래 이상거래 규칙·섀도 모델도 구현했습니다.

스마트 컨트랙트 구현
  • 양도 제한 티켓 SBT

    ERC-721 기반 TicketSBT에 티켓 발행·소각·소유 조회를 구현하고, 계정 간 이전과 승인 함수를 제한. 티켓 키로 tokenId를 결정하고 소각 후에도 발행 이력을 유지해 같은 키의 재발행 방지

  • 캣머니 포인트 토큰

    ERC-20 기반 CatMoney에 발행·소각·멱등 전송을 구현. 작업 종류와 호출자별로 실행 키를 분리하고 관리자·발행자·운영자 권한 및 일시 정지 범위 구성

  • 포토카드 NFT

    ERC-1155 기반 PhotocardNFT에 도안별 발행 한도와 메타데이터 URI, 최대 200건 배치 발행, 중복 키 건너뛰기와 멱등 전송 구현. 최초 발행 이후 도안의 수량·메타데이터 변경 제한

검증 가능한 추첨 구현
  • Commit–Reveal 추첨

    시드 해시와 참여자·도안 수량의 입력 해시를 먼저 온체인에 기록하고, 시드 공개 시 커밋 다음 블록의 해시를 결합해 난수 확정. 배정 요청의 입력 해시를 다시 검증하고 같은 난수·입력으로 배정 결과 재현

  • 자동 리빌과 재시작 복구

    미공개 추첨을 주기적으로 확인해 리빌하고, 최근 256블록의 DrawOpened 이벤트를 조회해 재시작으로 놓친 추첨 복구. 리빌 기한이 지난 추첨의 취소와 추첨 ID 재사용 제한 구현

블록체인 내부 API와 트랜잭션 처리
  • HD 지갑과 내부 API

    마스터 니모닉과 인덱스로 사용자 지갑을 파생하고 서명 시 개인키를 계산하는 구조 구현. Express·TypeScript로 지갑, 캣머니, 티켓, 포토카드, 추첨, 트랜잭션 조회 API를 제공하고 내부 키 인증·Zod 검증·OpenAPI 문서 연결

  • 논스 충돌과 중복 실행 처리

    단일 프로세스의 전송 큐로 트랜잭션을 직렬화하고 주소별 다음 논스를 직접 관리. 실패 시 논스를 다시 조회하도록 초기화하고, 컨트랙트의 실행 키와 기발행 조회로 재시도에 따른 중복 발행·차감·전송 방지

  • 비동기 확정 감시와 발행 증거 검증

    전송 접수와 블록 확정을 분리하고 영수증 감시·DB 미확정 행 재조회로 발행 상태 동기화. 포토카드 배치의 개별 이벤트와 과거 발행 이력을 대조해 수령인·도안이 다른 키 재사용이나 발행 이벤트 누락을 실패로 분류

  • 체인 환경 대응과 관측 지표

    SSAFY 체인의 Constantinople EVM에 맞춰 컴파일·테스트 환경을 구성하고 가스 가격 0 설정 적용. 전송 큐 대기, 전송 결과, 논스 초기화와 확정 지연 등을 Prometheus 지표로 노출

모니터링 전반 구축
  • EC2 자원을 고려한 스택 구성

    개발·운영 스택이 함께 실행되는 EC2의 자원 제약을 고려해 Prometheus·Grafana·Loki·Alloy 구성 선택. 앱과 별도 Docker Compose로 배포하고 컨테이너별 메모리 한도와 지표·로그 보관 정책 설정

  • 애플리케이션·호스트·DB 지표 수집

    Spring Actuator·Micrometer와 Node.js prom-client로 API·런타임·업무 지표를 노출하고, node-exporter·cAdvisor·postgres-exporter로 호스트·컨테이너·DB 상태 수집. 개발·운영 환경을 라벨로 구분해 Prometheus에서 15초 주기로 수집

  • 블록체인 업무 지표와 Grafana 대시보드

    티켓·포토카드 발행 적체, 체인 연결·블록 진행, 논스 큐 깊이·대기·초기화, 트랜잭션 드롭·확정 지연을 계측. API p95·DB 커넥션 대기·락·JVM·Node 상태와 함께 보는 대시보드 및 데이터소스를 코드로 구성

  • 중앙 로그 수집

    Alloy가 Docker 컨테이너 로그를 읽어 Loki로 전달하도록 구성. 로그 수집을 앱 실행 경로와 분리하고 서비스·컨테이너 중심 라벨, 모니터링 스택 로그 제외, 마스킹과 14일 보관 정책 적용

  • 장애 알림

    발행 적체·체인 RPC 두절·트랜잭션 드롭·DB 커넥션 대기·컨테이너 메모리 한도 접근 등의 Prometheus 알림 규칙 작성. Alertmanager에서 환경·심각도별 그룹화와 중복 알림 억제, Mattermost 호환 웹훅 연동 구성

  • Sentry 오류 추적

    프론트엔드와 Spring 서버에 Sentry 오류 수집을 연결하고 환경·릴리스별로 구분. 서버는 Logback 연동으로 예외 스택을 수집하고, 프론트엔드는 오류 필터링과 민감정보 정리 적용

티켓팅 부하 측정과 병목 개선
  • 좌석 페이지 체류 부하 테스트

    좌석 화면의 3초 폴링을 재현하는 k6 시나리오를 구성하고 가상 사용자 수를 단계적으로 증가. 클라이언트·서버 p95, 처리량, Hikari 대기, 요청 처리 스레드, CPU와 타임아웃을 비교해 병목 추적

  • 좌석맵 응답 캐시

    회차·구역별 좌석맵에 Caffeine 1초 TTL 캐시를 적용하고 동일 키의 동시 로딩을 합쳐 반복 DB 조회와 커넥션 점유 감소. 캐시된 좌석 스냅샷과 응답 시점의 serverTime 분리

  • nginx 연결 재사용

    캐시 적용 후 nginx의 TIME_WAIT 누적과 임시 포트 고갈에 따른 502를 확인하고 upstream keepalive 적용. 외부 측정기의 네트워크 병목을 구분하기 위해 EC2 내부에서 서버를 직접 호출하는 테스트도 수행

가상 대기실 구현
  • Redis 대기열과 원자적 입장 처리

    Redis Sorted Set으로 회차별 대기 순서와 입장 만료 시각을 관리. Lua로 만료자 정리·정원 확인·대기자 이동을 한 번에 처리하고, 입장 처리자 중복 실행을 Redis 락으로 제어

  • 정원과 입장 속도 제어

    회차별 기본 정원 5,000명, 1초 주기의 틱당 최대 200명 입장, 10분 무활동 만료를 설정으로 분리. 관리자 API에서 회차별 활성화와 정원·입장 속도·만료 시간 조정 지원

  • 대기 화면과 서버 접근 제어

    대기 순번·예상 대기 시간과 입장 상태를 보여주는 화면 및 라우팅 가드 구현. 서버 인터셉터에서 좌석 조회·선점 요청의 입장 여부를 확인하고, 예매 완료·이탈 시 자리를 반환하도록 연결

경매·NFT 거래 이상거래 심사 구현
  • 8개 규칙 기반 탐지

    동일 단말, 동일 연락처, 반복 거래쌍·양자 간 순환, 가격 이상, 신규 계정 급행 거래, 거래량 집중, 매집, 계정 탈취 의심 규칙 구현. 거래 맥락을 한 번 수집해 규칙별 근거와 위험도를 계산

  • FLAG·HOLD 모드

    기본 FLAG 모드에서는 이상 징후와 관리자 판정을 기록하면서 정산을 진행. HOLD 모드에서는 판정 기준에 해당하는 거래를 온체인 전송 전에 RISK_REVIEW 상태로 보류하고 관리자 승인·거절에 따라 후속 처리

  • 관리자 심사 기능

    심사 목록·상세·통계·승인·거절 API와 관리자 화면 구현. 규칙 근거, 판매자·구매자 정보, 입찰 타임라인과 최근 거래 내역을 함께 보여주고 판정 기록 저장

  • 머신러닝 섀도 모델 학습·채점

    정상·이상거래 시뮬레이션 3개 배치에서 학습 시점 기준 1,640건을 생성하고 마지막 547건을 분리 검증. 규칙 판정값을 입력에서 제외한 32개 피처로 로지스틱 회귀를 학습해 Java 서버가 위험 점수만 저장하도록 연결

기술 선택과 구현

커스터디 HD 지갑
선택 이유
3주간 실제로 운영하며 지표를 모으는 것이 목표였기 때문에, 짧은 기간에 많은 사용자가 쉽게 참여할 수 있도록 편의성을 우선했습니다. 외부 지갑 설치·연결과 시드 구문 관리 부담을 줄이기 위해 커스터디 방식을 선택했습니다.
구현 내용
니모닉과 인덱스로 사용자 지갑을 파생하고 서버에서 서명을 처리해, 별도 지갑 연결 없이 티켓과 포토카드를 이용하도록 구성했습니다.
Node.js · Express
선택 이유
Hardhat·ethers.js 등 블록체인 생태계를 활용하기 위해 Node.js를 선택했습니다. 블록체인 API를 혼자 담당해 빠르게 구현해야 했으므로 필요한 기능을 직접 조합할 수 있는 Express를 사용했습니다.
구현 내용
컨트랙트와 API의 타입을 공유하고 지갑·서명·트랜잭션 처리와 내부 API를 연결했습니다.
Caffeine · Redis · Lua
선택 이유
같은 좌석맵의 반복 조회는 Caffeine 로컬 캐시로 DB 비용을 줄이고, 처리 한계를 넘는 사용자는 Redis 대기열에서 관리하도록 나눴습니다. 정원 확인과 입장 사이의 경합을 막기 위해 Lua로 원자적으로 처리했습니다.
구현 내용
좌석맵에 1초 TTL 캐시를 적용하고, Redis Sorted Set과 Lua로 대기 순서·입장 만료·정원·입장 속도를 제어했습니다.
모니터링 도구
선택 이유
3주간의 운영에서 사용량·성능·오류 지표를 최대한 폭넓게 수집하는 것이 목적이었습니다. 초기 운영에서 다양한 오류가 발생할 것으로 예상해 지표·로그·예외·알림을 각각 관측하도록 도구를 구성했고, EC2 자원을 고려해 ELK 대신 Prometheus·Loki·Grafana 조합을 선택했습니다.
구현 내용
Prometheus·Grafana로 지표를, Loki·Alloy로 로그를 수집하고 Sentry로 예외를 추적하며 Alertmanager로 알림을 구성했습니다. k6 부하 결과를 서버 지표와 대조하고 수집 도구별 자원·보관 한도를 설정했습니다.
scikit-learn · 로지스틱 회귀
선택 이유
확정된 실거래 이상거래 라벨이 부족해 합성 시나리오에서 규칙의 사각지대를 먼저 검증하고, 피처별 영향과 오탐 원인을 추적할 수 있는 모델을 선택했습니다.
구현 내용
규칙 판정값을 제외한 거래 피처로 배치 단위 분리 검증을 수행하고, 학습된 계수와 전처리 기준을 JSON으로 내보냈습니다. Java 서버는 점수만 기록하며 심사·정산 결정에는 사용하지 않습니다.

문제 해결 사례

사례 1커스터디 지갑으로 진입 장벽을 낮추고 실사용자 105명 확보

1. 오류 분석

문제 현상
실제 사용자를 유입해야 하는 3주간의 운영에서, NFT 기능을 위해 사용자가 MetaMask 같은 외부 지갑을 미리 준비하고 연결해야 한다면 가입 장벽이 높아질 수 있었습니다.
원인
외부 지갑을 전제로 한 방식은 사용자가 지갑 설치, 니모닉 관리, 트랜잭션 서명에 익숙해야 합니다. 일반적인 회원가입 경험을 기대하는 사용자에게 이 절차는 추가 부담이었습니다.
추가 확인 사항
  • 회원가입과 NFT 이용 과정에서 사용자에게 외부 지갑 연결을 요구하지 않는 흐름을 확인했습니다.
  • 회원마다 고유한 파생 인덱스를 사용해 같은 인덱스에서 같은 지갑 주소가 생성되는지 확인했습니다.
  • 회원 DB에는 지갑 주소와 파생 인덱스만 기록하고, 마스터 니모닉과 개인키는 저장하지 않는 구조를 확인했습니다.
측정 기준
105명은 3주 운영 기간의 실사용자 수입니다. 커스터디 도입 전후의 가입 전환율은 별도로 비교 측정하지 않았습니다.

2. 개선 과정

  1. 1.3주 운영과 실사용자 유입이라는 목표에 맞춰 외부 지갑 준비 과정을 가입 흐름에서 제거
  2. 2.블록체인 서비스가 보관하는 하나의 마스터 니모닉과 회원별 고유 인덱스로 HD 지갑 주소 파생
  3. 3.회원가입이 완료되면 지갑 발급 요청을 남기고 백그라운드에서 지갑 생성
  4. 4.발급을 재시도해도 같은 인덱스를 사용하도록 해 지갑 주소가 바뀌거나 중복 발급되지 않게 처리
  5. 5.사용자가 개인키를 직접 다루지 않아도 서비스에서 필요한 블록체인 작업을 진행하도록 구성

커스터디 방식의 HD 지갑을 도입했습니다. 사용자는 일반 계정으로 가입하고, 서비스는 마스터 니모닉과 회원별 고유 파생 인덱스로 각자의 지갑을 생성합니다. 마스터 니모닉은 블록체인 서비스에서 관리하며 회원 DB에는 지갑 주소와 파생 인덱스만 저장합니다.

변경 전 · 변경 후

주황색: 추가·변경한 부분

커스터디 지갑으로 진입 장벽을 낮추고 실사용자 105명 확보 구조 변경 비교

커스터디 지갑으로 진입 장벽을 낮추고 실사용자 105명 확보 구조 변경 비교변경 전: 외부 지갑 준비: 설치·연결·시드 관리; 사용자 직접 서명: 블록체인 작업마다 지갑 사용; 티켓·NFT 이용: 자산 발행·소유 확인. 변경 후: 일반 회원가입: 외부 지갑 준비 과정 제거 (추가·변경); 서버 HD 지갑·서명: 회원별 자동 발급·서비스에서 서명 (추가·변경); 티켓·NFT 이용: 자산 발행·소유 확인. 외부 지갑을 준비하고 직접 서명하는 과정을 서비스의 자동 지갑 발급·서명으로 바꿨습니다.BEFORE외부 지갑 전제외부 지갑 준비설치·연결·시드 관리사용자 직접 서명블록체인 작업마다 지갑 사용티켓·NFT 이용자산 발행·소유 확인AFTER변경 후일반 회원가입외부 지갑 준비 과정 제거변경서버 HD 지갑·서명회원별 자동 발급·서비스에서 서명변경티켓·NFT 이용자산 발행·소유 확인
외부 지갑을 준비하고 직접 서명하는 과정을 서비스의 자동 지갑 발급·서명으로 바꿨습니다.

3. Before & After 비교

티캣 · 커스터디 지갑으로 진입 장벽을 낮추고 실사용자 105명 확보 비교
항목BeforeAfter
가입 경험NFT 이용을 위해 사용자가 외부 지갑을 준비하고 연결해야 하는 방식외부 지갑 없이 일반 회원가입 후 지갑이 자동으로 발급되는 방식
지갑 관리사용자가 자신의 니모닉과 개인키를 직접 관리해야 함서비스가 마스터 니모닉을 관리하고 회원별 고유 인덱스로 지갑을 파생

해결 결과

외부 지갑 설치·연결 없이 가입하고 NFT 기능을 이용하도록 진입 장벽을 낮췄고, 3주간 운영하며 실사용자 105명을 확보했습니다.

사례 2좌석 조회 캐시·연결 개선으로 p95 1.34초 → 13ms 단축

1. 오류 분석

문제 현상
2,400 VU 부하에서 DB 커넥션과 요청 처리 스레드가 포화되면서 좌석 조회 p95가 약 7.08초까지 늘고, 요청의 약 4.0%가 타임아웃으로 실패했습니다.
원인
같은 좌석맵을 요청마다 DB에서 다시 조립했고, 서버가 처리할 수 있는 양보다 많은 조회 요청이 몰렸습니다.
추가 확인 사항
  • Grafana에서 DB 커넥션 대기와 요청 처리 스레드 포화를 확인했습니다.
  • 캐시 적용 후에도 남은 지연을 추적해 nginx의 연결 생성·임시 포트 고갈을 확인했습니다.
측정 기준
p95는 서버 기준입니다. 1,200 VU 캐시 적용 전후와 3,600 VU 재측정은 서로 다른 부하 조건이며, 대기실 정원 5,000명은 설정값입니다.

2. 개선 과정

  1. 1.2,400 VU 실패 재현·병목 확인
  2. 2.좌석맵 1초 캐시 적용
  3. 3.nginx 연결 재사용
  4. 4.재측정 후 가상 대기실로 입장 제어

좌석맵을 1초간 캐싱해 반복 DB 조회와 커넥션 점유를 줄이고, nginx keepalive로 연결을 재사용했습니다. 가상 대기실을 추가해 정원을 넘는 사용자는 대기시키고 순차 입장하도록 했습니다.

변경 전 · 변경 후

주황색: 추가·변경한 부분

좌석 조회 캐시·연결 개선으로 p95 1.34초 → 13ms 단축 구조 변경 비교

좌석 조회 캐시·연결 개선으로 p95 1.34초 → 13ms 단축 구조 변경 비교변경 전: 반복 좌석 조회: 좌석 화면에서 3초마다 요청; nginx → Spring: 연결 생성·요청마다 DB 조회; PostgreSQL: 같은 좌석맵 반복 조회·커넥션 대기. 변경 후: 대기실·연결 재사용: Redis + Lua 입장 제어·nginx keepalive (추가·변경); Spring + 1초 캐시: Caffeine으로 동일 좌석맵 조회 공유 (추가·변경); PostgreSQL: 캐시 미스에만 좌석맵 조회. DB 앞에 캐시를 두고, 입장 제어와 연결 재사용으로 예매 시점에 몰리는 요청을 줄였습니다.BEFORE변경 전반복 좌석 조회좌석 화면에서 3초마다 요청nginx → Spring연결 생성·요청마다 DB 조회PostgreSQL같은 좌석맵 반복 조회·커넥션 대기AFTER변경 후대기실·연결 재사용Redis + Lua 입장 제어·nginx keepalive변경Spring + 1초 캐시Caffeine으로 동일 좌석맵 조회 공유변경PostgreSQL캐시 미스에만 좌석맵 조회
DB 앞에 캐시를 두고, 입장 제어와 연결 재사용으로 예매 시점에 몰리는 요청을 줄였습니다.

3. Before & After 비교

티캣 · 좌석 조회 캐시·연결 개선으로 p95 1.34초 → 13ms 단축 비교
항목BeforeAfter
좌석 조회 p95 · 1,200 VU약 1.34초 · 요청마다 DB 조회13ms · 약 99.0% 단축
초과 유입서버 내부에 요청이 쌓여 타임아웃 발생가상 대기실에서 대기 후 정원·입장 속도에 맞춰 진입

해결 결과

동일한 1,200 VU에서 캐시 적용 전후 서버 p95를 약 1.34초에서 13ms로 줄여 약 99.0% 단축했습니다. 3,600 VU의 서버 직접 호출에서는 p95 약 20ms·실패율 0%를 확인했습니다. 기본 정원 5,000명·틱당 최대 200명 입장의 가상 대기실로 초과 유입을 제어했습니다.

사례 3메타데이터·이미지 경로 분리로 NFT 이미지 407건 이관

1. 오류 분석

문제 현상
Pinata 무료 플랜의 파일 수 한도 500개를 넘어 핀이 507개가 되자 운영 전용 게이트웨이가 403을 반환했습니다. 경매 상세, 마켓 목록, 포카덱스의 이미지가 실제 그림 대신 대체 이미지로 표시됐습니다.
원인
브라우저가 Pinata 게이트웨이에서 화면용 이미지를 직접 가져오는 구조였습니다. 또한 공연마다 이름이 다른 메타데이터 JSON을 새로 핀해, 이미지를 재사용해도 파일 수가 계속 증가했습니다.
추가 확인 사항
  • 배포에 적용된 전용 게이트웨이는 403을 반환하지만 공용 게이트웨이는 같은 CID에 200을 반환하는 것을 확인했습니다.
  • Pinata API에서 핀 507개를 확인했고, 운영·개발 DB를 함께 대조해 안전하게 정리할 수 있는 미참조 핀 52개를 찾았습니다.
  • 52개를 정리해 핀 수를 455개로 낮추자 전용 게이트웨이가 다시 200을 반환했습니다.
측정 기준
이미지 응답 200은 운영에서 확인한 표본 3건의 결과입니다. 22건 생성 시 핀 증가량 7개는 기존 템플릿의 메타데이터 CID 재사용이 포함된 실측값입니다. 기존 NFT의 온체인 URI와 지갑용 IPFS 경로는 변경하지 않았습니다.

2. 개선 과정

  1. 1.IPFS 게이트웨이 한도·메타데이터 증가 원인 확인
  2. 2.미참조 핀 52개 정리·게이트웨이 복구
  3. 3.NFT 메타데이터와 화면용 이미지 경로 분리·이미지 주소 407건 이관
  4. 4.공연별 메타데이터 정보 정리·템플릿 CID 재사용
  5. 5.이미지 접근·온체인 등록 결과 검증

NFT 메타데이터와 화면용 이미지의 저장·조회 경로를 용도별로 분리했습니다. 메타데이터는 IPFS URI로 유지하고, 화면용 이미지는 객체 저장소의 공개 고정 URL에서 제공하도록 구성했습니다(구현: MinIO). 새 메타데이터에서는 공연별로 달라지는 정보를 제거해 같은 템플릿의 JSON·CID를 재사용했습니다. 기존 NFT의 온체인 URI와 지갑이 사용하는 IPFS 이미지 참조는 유지했습니다.

변경 전 · 변경 후

주황색: 추가·변경한 부분

메타데이터·이미지 경로 분리로 NFT 이미지 407건 이관 구조 변경 비교

메타데이터·이미지 경로 분리로 NFT 이미지 407건 이관 구조 변경 비교변경 전: 이미지·메타데이터 조회: 두 경로 모두 Pinata / IPFS에 의존; NFT 메타데이터: 공연별 새 JSON·CID; 화면용 이미지: Pinata 게이트웨이. 변경 후: 이미지·메타데이터 조회: 용도에 맞는 저장소·URI로 연결; NFT 메타데이터: IPFS·템플릿 CID 재사용 (추가·변경); 화면용 이미지: 객체 저장소·고정 URL (추가·변경). NFT 메타데이터는 IPFS에서 템플릿별 CID를 재사용하고, 화면용 이미지는 객체 저장소의 고정 URL로 제공합니다. 기존 NFT의 온체인 URI와 메타데이터 내 IPFS 이미지 참조는 유지했습니다.BEFORE변경 전이미지·메타데이터 조회두 경로 모두 Pinata / IPFS에 의존NFT 메타데이터공연별 새 JSON·CID화면용 이미지Pinata 게이트웨이AFTER변경 후이미지·메타데이터 조회용도에 맞는 저장소·URI로 연결NFT 메타데이터IPFS·템플릿 CID 재사용변경화면용 이미지객체 저장소·고정 URL변경
NFT 메타데이터는 IPFS에서 템플릿별 CID를 재사용하고, 화면용 이미지는 객체 저장소의 고정 URL로 제공합니다. 기존 NFT의 온체인 URI와 메타데이터 내 IPFS 이미지 참조는 유지했습니다.

3. Before & After 비교

티캣 · 메타데이터·이미지 경로 분리로 NFT 이미지 407건 이관 비교
항목BeforeAfter
화면용 이미지브라우저가 Pinata 전용 게이트웨이를 직접 호출해 한도 초과 시 403 발생메타데이터와 분리된 객체 저장소의 고정 URL에서 조회
메타데이터 파일 수공연마다 메타데이터 내용과 CID가 달라져 도안 수만큼 핀 증가같은 템플릿의 메타데이터 CID를 재사용

해결 결과

메타데이터와 화면용 이미지의 제공 경로를 분리해 이미지 주소 407건을 고정 URL로 이관했습니다. 미참조 핀 52개를 정리해 핀 수를 507개에서 455개로 낮춰 게이트웨이를 복구했습니다. 변경 후 도안 22건의 온체인 등록 성공률은 100%(22/22건)였고, 템플릿별 메타데이터 CID 재사용으로 핀은 7개만 증가했습니다. 확인한 이미지 표본 3건은 모두 200으로 응답했습니다.

사례 4FDS 합성 평가 재현율 37.2% → 93.1%로 개선

1. 오류 분석

문제 현상
8개 규칙 중 반복 거래쌍 규칙은 두 계정 사이만 확인해 A→B→C→A 형태의 3자 순환거래를 놓쳤습니다. 꾸민 카드에 적용한 가격 규칙 예외로 꾸밈 후 고가 거래도 탐지하지 못했습니다.
원인
개별 조건과 임계값으로 판단하는 규칙만으로는 카드 소유 이력, 거래 빈도, 가격과 계정 행동을 함께 평가하기 어려웠습니다.
추가 확인 사항
  • 합성 검증 배치에서 기존 규칙은 3자 순환거래 48건 중 1건, 꾸밈 후 고가 거래 20건 중 0건을 탐지했습니다.
  • 서로 다른 3개 시뮬레이션 배치 중 마지막 547건(이상거래 247건·정상 300건)을 통째로 분리해 기존 규칙과 모델을 같은 거래에서 비교했습니다.
  • 모델이 오탐한 정상 거래 12건은 모두 고정가 판매였습니다. 거래 유형 피처를 제외한 가상 재학습에서는 오탐 6건·탐지 223건으로 바뀌어 해당 피처의 영향을 확인했습니다.
  • 새 모델로 채점된 거래 548건에서 Java 점수와 Python 재계산값이 반올림 오차 범위에서 일치함을 확인했습니다.
측정 기준
탐지 성능은 시뮬레이터가 부여한 라벨 기준이며 실거래 성능이 아닙니다. 검증 배치의 이상거래 비율은 247/547건으로 높고 일부 피처가 시나리오를 거의 단독으로 구분해 성능이 낙관적일 수 있습니다. 피처 제외 실험은 오탐 원인 확인용 가상 비교로 실제 모델 변경이 아닙니다. 최종 저장 모델은 검증 뒤 전체 1,640건으로 다시 학습했으며, 548건 점수 대조는 채점 구현 검증이지 추가 탐지 성능 평가가 아닙니다.

2. 개선 과정

  1. 1.규칙과 동일한 거래 맥락에서 44개 후보 피처를 생성하고 규칙 판정값은 모델 입력에서 제외
  2. 2.앞선 두 배치 1,093건에서만 결측값 처리·극단값 범위·표준화 기준을 학습하고 마지막 배치 547건으로 검증
  3. 3.상수 피처를 제외한 32개 피처로 로지스틱 회귀 모델을 학습하고 동일 검증 거래에서 규칙과 탐지·오탐 비교
  4. 4.오탐 거래의 피처 기여도를 분석한 뒤 전체 1,640건으로 최종 모델을 다시 학습하고 Java 서버에 섀도 점수 기록 연결

학습된 계수와 전처리 기준을 JSON으로 저장하고, Java 서버가 거래 피처의 가중합에 시그모이드 함수를 적용해 0~1의 위험 점수를 계산하도록 했습니다. 모델 점수는 거래에 기록하되 관리자 심사 여부나 정산 보류 결정에는 사용하지 않았습니다.

변경 전 · 변경 후

주황색: 추가·변경한 부분

FDS 합성 평가 재현율 37.2% → 93.1%로 개선 구조 변경 비교

FDS 합성 평가 재현율 37.2% → 93.1%로 개선 구조 변경 비교변경 전: 거래 정보·행동 피처: 가격·계정 관계·거래 이력; 8개 탐지 규칙: 개별 조건·임계값으로 판정; 관리자 심사·정산: 규칙 판정에 따라 FLAG / HOLD. 변경 후: 거래 정보·행동 피처: 가격·계정 관계·거래 이력; 8개 탐지 규칙: 기존 판정 경로 유지; ML 섀도 모델: 위험 점수만 저장 (추가·변경); 관리자 심사·정산: 규칙 판정에 따라 FLAG / HOLD. ML은 기존 규칙 옆에서 점수만 기록합니다. 모델 점수가 관리자 심사나 정산 보류를 결정하지 않습니다.BEFORE변경 전거래 정보·행동 피처가격·계정 관계·거래 이력8개 탐지 규칙개별 조건·임계값으로 판정관리자 심사·정산규칙 판정에 따라 FLAG / HOLDAFTER변경 후거래 정보·행동 피처가격·계정 관계·거래 이력8개 탐지 규칙기존 판정 경로 유지ML 섀도 모델위험 점수만 저장변경관리자 심사·정산규칙 판정에 따라 FLAG / HOLD
ML은 기존 규칙 옆에서 점수만 기록합니다. 모델 점수가 관리자 심사나 정산 보류를 결정하지 않습니다.

3. Before & After 비교

티캣 · FDS 합성 평가 재현율 37.2% → 93.1%로 개선 비교
항목BeforeAfter
이상거래 247건 탐지기존 규칙 92건 탐지·재현율 37.2%모델 230건 탐지·재현율 93.1%
규칙의 사각지대3자 순환거래 1/48건, 꾸밈 후 고가 거래 0/20건 탐지모델이 각각 41/48건, 20/20건 탐지
정상 거래 300건 오탐기존 규칙 0건모델 임계값 0.5에서 12건, 모두 고정가 판매에 집중

해결 결과

합성 검증 547건에서 이상거래 재현율을 기존 규칙 37.2%에서 모델 93.1%로 55.9%p 높였습니다. 모델은 이상거래 247건 중 230건을 탐지했고, 정밀도 95.0%·PR-AUC 0.987을 확보했습니다. 정상 거래 오탐은 300건 중 12건(4.0%)이었으며, 별도 548건의 Java·Python 점수 대조로 서버 채점 구현을 검증했습니다.

프로젝트 회고

기술적 한계와 개선 방안

한계·아쉬운 점
커스터디 지갑은 가입 장벽을 낮췄지만, 사용자 지갑의 통제권이 마스터 니모닉에 집중되는 운영 책임이 남았습니다.
개선 방향
키 접근 통제·백업·복구 절차와 사용자의 외부 지갑 이전 경로까지 운영 설계에 포함하겠습니다.
한계·아쉬운 점
FDS 재현율 93.1%는 합성 거래 평가 결과로, 실거래에서의 탐지·오탐 성능은 아직 검증하지 못했습니다.
개선 방향
관리자 확정 판정이 쌓이면 별도의 실거래 데이터로 정밀도·재현율·임계값을 재평가하고, 그전까지는 점수만 기록하는 섀도 모드를 유지하겠습니다.