빌려조잉 프로젝트 소개: 6인 팀이 6주 만에 만든 C2C 대여 플랫폼
목차
빌려조잉은 물건을 서로 빌리고 빌려주는 C2C 공유 플랫폼입니다. 캠핑 텐트나 빔프로젝터처럼 한두 번 쓰고 방치되는 물건을 이웃끼리 대여하면 빌려주는 쪽은 수익을 얻고 빌리는 쪽은 비용을 아낍니다. SSAFY(삼성 청년 SW 아카데미) 자율 프로젝트로 6인 팀이 6주 동안 만들었고 삼성전자 주식회사 프로젝트 우수상을 받았습니다.

한눈에 보기
| 항목 | 내용 |
|---|---|
| 한 줄 소개 | 물건을 이웃끼리 빌리고 빌려주는 C2C 대여 플랫폼, 실시간 채팅으로 거래를 조율하고 에스크로로 대여료를 지킨다 |
| 기간 | 2025.10.10 ~ 2025.11.20 (6주), SSAFY 자율 프로젝트 |
| 팀 구성 | 6명 (프론트엔드 2명, 백엔드 4명) |
| 내 역할 | 팀 리드, 백엔드 35% + 프론트엔드 10% |
| 수상 | 삼성전자 주식회사 프로젝트 우수상 |
| 기술 | Java, Kotlin, Spring Boot, WebSocket(STOMP), Redis Pub/Sub, MongoDB, MySQL, FastAPI, LangChain |
| 코드 | github.com/dj258255/joying |
왜 만들었나
카메라나 캠핑 장비처럼 한두 번 쓰고 마는 물건을 살지 말지 고민한 경험은 다들 있습니다. 팀이 기획 단계에서 모은 실제 불편은 이랬습니다.
- 한 번만 쓰면 되는데 사기엔 너무 비싸다.
- 당장 필요한데 빌릴 곳이 없다.
- 중고 거래는 상태 확인이 어렵고 사기 걱정도 있다.
- 개인 간 거래는 서로 믿을 근거가 부족하다.
빌려조잉은 실시간 채팅으로 거래 과정을 투명하게 남기는 방식으로 이 문제에 접근했습니다. 보증금은 플랫폼이 에스크로로 잡아 두고 거래가 끝난 뒤에는 서로 리뷰를 남겨 신뢰를 쌓습니다.
누구를 위한 서비스인가
| 쓰는 사람 | 이 사람이 하는 일 |
|---|---|
| 물건을 빌려주는 사람 | 물건을 등록하고 AI가 추천한 제목과 가격, 해시태그를 확인하거나 고쳐 씁니다. 채팅으로 대여 조건을 조율하고 반납을 확인합니다 |
| 물건을 빌리는 사람 | 검색과 필터로 물건을 찾아 채팅으로 문의하거나 대여를 제안합니다. 결제와 보증금을 내고 반납 후 리뷰를 남깁니다 |
| 양쪽 모두 | 서로의 프로필에서 이전 리뷰와 평점을 확인합니다. 거래 중에는 같은 채팅방에서 가상 송금 내역과 상태 변화를 함께 봅니다 |
주요 기능
1. 실시간 채팅 기반 거래
채팅방 하나가 거래 하나를 대신합니다. 채팅 중에 거래를 생성하고 확인할 수 있으며 대여 조건을 조율하는 동안 시스템 메시지로 진행 상태가 자동으로 남습니다. 거래가 끝나면 같은 채팅방에서 리뷰를 작성합니다.

2. 회원 인증과 계좌 인증
카카오 OAuth2 로그인과 JWT 기반 인증을 붙였습니다. 계좌를 등록할 때는 오픈뱅킹 1원 인증으로 실명을 확인합니다.
3. AI 자동 상품 등록
물건 사진을 올리면 GPT-4o Vision이 상태를 읽습니다. 네이버 쇼핑 API로 시세를 조사한 뒤 적정 대여료와 게시글 초안을 만들어 주면 판매자는 이 초안을 그대로 쓰거나 고쳐서 등록합니다.

4. 결제와 보증금 에스크로
결제는 토스페이먼츠로 진행하고 보증금은 플랫폼이 에스크로로 잡아 둡니다. 반납이 확인되면 보증금은 돌아가고 대여료는 정산됩니다.
5. 검색, 필터링, 리뷰 신뢰도
가격, 해시태그, 지역, 평점, 당일 대여 여부로 물건을 걸러서 찾습니다. 거래가 끝나면 판매자와 구매자가 서로 리뷰를 남기고 이 평점은 다음 거래에서 신뢰의 근거가 됩니다.
구조
백엔드는 도메인별 패키지로 나뉘어 있습니다. 회원과 계좌, 상품, 대여, 채팅, 결제와 에스크로, 리뷰, 검색이 각자 컨트롤러부터 리포지토리까지 갖춘 독립 모듈입니다. 프론트엔드는 기능별 디렉토리 구조로 화면을 나눕니다. AI 서버는 FastAPI와 LangChain으로 이미지 분석과 시세 조사, 게시글 생성 파이프라인을 맡습니다.

저장은 성격에 따라 나눴습니다. 회원과 상품, 채팅방처럼 관계가 중요한 데이터는 MySQL에, 채팅 메시지처럼 쓰기가 잦고 스키마가 유연해야 하는 데이터는 MongoDB에, 세션과 안읽은 메시지 개수처럼 순간 조회가 잦은 데이터는 Redis에 뒀습니다.
내가 한 일
팀 6명 중 리드를 맡았고 백엔드 35%, 프론트엔드 10%를 담당했습니다. 회원 시스템과 실시간 채팅 시스템 전체를 설계하고 구현했습니다. 결제 에스크로는 담당자가 3주차에 빠지면서 이어받았습니다.
회원 시스템과 채팅 시스템
카카오 OAuth2 로그인, JWT 인증, 오픈뱅킹 1원 인증 계좌 확인을 만들었습니다. 채팅은 WebSocket(STOMP) 위에 Redis Pub/Sub으로 실시간 전달을 붙이고 MongoDB에 메시지를 쌓는 구조로 처음부터 끝까지 혼자 설계했습니다.
트레이드오프 셋
메시지 브로커: Kafka 대신 Redis Pub/Sub. 서버를 늘리면 채팅 메시지를 어떻게 뿌릴지가 문제였습니다. Kafka는 브로커 구성과 토픽 설계가 따로 붙습니다. EC2 t3.medium 한 대에는 이미 Spring Boot와 MySQL, MongoDB, Redis가 올라가 있어 새 클러스터를 띄울 메모리가 없었습니다. 동시 접속 50~100명에 피크 초당 100~200건이라는 기준을 먼저 잡아 두었더니 이미 세션 저장소로 쓰던 Redis의 Pub/Sub만으로 그 기준의 1,000배 이상 여유가 나왔습니다. Pub/Sub은 구독자가 없을 때 발행된 메시지가 사라진다는 약점이 있습니다. 다만 MongoDB에 먼저 저장하고 Pub/Sub으로 알리는 순서라 전달이 실패해도 데이터는 남았습니다. 더 강력한 도구가 늘 더 나은 선택은 아니었습니다.
저장소: 하나로 통일하지 않고 성격별로 나눔. MySQL만 쓰면 초당 100~200건씩 쌓이는 채팅 메시지의 인덱스 유지 비용이 그대로 붙습니다. MongoDB만 쓰면 회원과 상품처럼 관계가 얽힌 도메인의 트랜잭션 보장이 약해집니다. 같은 조건에서 메시지 하나를 넣는 데 MySQL은 약 15ms, MongoDB는 약 5ms로 3배 차이가 났습니다. 채팅방은 MySQL, 메시지는 MongoDB로 나누되 둘 사이에 트랜잭션이 걸리지 않는다는 점을 감안해 방 상태는 MySQL 트랜잭션 안에서만 다뤘습니다.
인증 토큰: LocalStorage 대신 HttpOnly Cookie. LocalStorage는 저장과 만료 확인, 갱신, 인터셉터, 로그아웃 정리까지 직접 짜야 했고 XSS 한 번으로 토큰이 털립니다. HttpOnly Cookie는 백엔드 속성 몇 개와 프론트 한 줄로 끝나고 스크립트가 접근할 수 없습니다. 대신 로컬 개발 환경에서 SameSite 정책 때문에 쿠키가 전송되지 않는 문제가 생겨 Vite 프록시로 운영 보안 설정은 그대로 두고 로컬만 풀었습니다.
리드로서 신경 쓴 것
새 기능을 시작할 때마다 어떤 상태가 되면 완성인지를 먼저 글로 적어 팀과 맞췄습니다. 막판에 인수 기준이 어긋나 다시 짜는 일을 줄이려는 목적이었습니다. 기술 결정은 혼자 통보하지 않고 후보의 부하 테스트와 실측값을 함께 공유해 같은 기준 위에서 판단하게 했습니다.
결제 에스크로는 DB 스키마만 있고 서비스 로직은 판매자 쪽 플로우만 구현된 상태로 넘어왔습니다. 토스페이먼츠 에스크로 문서를 3일간 분석하고 빌리는 사람 관점의 역방향 로직을 구현해 양방향 거래 플로우를 완성했습니다. 마감 1주일 전에는 화면 10개 중 7개가 API 연동 없이 하드코딩된 상태라는 것도 발견했습니다. Swagger로 API 문서를 자동 생성한 뒤 연동이 안 된 화면은 직접 React 코드를 고쳐 배포 전날까지 끝냈습니다. 기다리기보다 먼저 손을 드는 쪽이 프로젝트를 움직였습니다.
일정과 작업 방식
팀은 GitLab 사내 인스턴스에서 Jira로 이슈를 관리했습니다. 커밋과 브랜치명에는 이슈 번호를 남기는 방식(타입/역할_설명 {#번호})으로 작업을 추적했습니다. 병합 요청 템플릿에는 지라 링크를 남기는 항목이 고정돼 있었습니다.
저장소의 실제 커밋 기록(2025-10-20 ~ 2025-11-19, 총 1,017건)을 주 단위로 나누면 다음과 같습니다.
| 주차 | 기간 | 커밋 수 | 그 주에 남은 것 |
|---|---|---|---|
| 1주차 | 10.10 ~ 10.16 | 기록 없음 | 저장소 밖 기획, API 명세와 ERD 정리 |
| 2주차 | 10.17 ~ 10.23 | 53 | 저장소 초기 세팅, 이슈·MR 템플릿, 회원·상품 엔티티 초안 |
| 3주차 | 10.24 ~ 10.30 | 105 | 상품 CRUD, 채팅 화면 초안, 결제·에스크로 엔티티 |
| 4주차 | 10.31 ~ 11.06 | 192 | OAuth2·JWT 인증, 오픈뱅킹 1원 인증, 에스크로 개발, 채팅 인증을 쿠키 기반으로 전환 |
| 5주차 | 11.07 ~ 11.13 | 256 | 웹푸시, 채팅 메시지 수정·삭제, 실시간 알림, 결제 프론트엔드 |
| 6주차 | 11.14 ~ 11.20 | 411 | AI 자동 게시글, 빌리는 사람 쪽 결제 플로우 완성, 막판 버그 수정 |
마지막 주 하루 이틀 전인 11월 17~19일 사흘에만 298건, 전체의 29%가 몰려 있습니다. 3주차에 결제 담당자가 빠진 뒤 에스크로를 이어받았습니다. 남은 판매자(RENT) 쪽 연동을 정리하고 11월 14~19일에는 빌리는 사람(BORROW) 쪽 결제 플로우와 거래 생성 버그를 마무리했습니다.
직접 재 본 결과
수치는 모두 EC2 t3.medium 단일 서버, 단일 요청 기준으로 잰 것입니다. 동시 접속이나 실제 운영 트래픽 기준이 아닙니다.
| 확인한 것 | 전 | 후 |
|---|---|---|
| 채팅방 목록 조회 10개 (N+1 제거, Redis MGET) | 1,350ms (51쿼리) | 85ms (4쿼리) |
| 채팅방 목록 페이지네이션 100페이지 (offset → cursor) | 450ms | 12ms |
| 메시지 전송 시 권한 확인 (MySQL → Redis 캐싱) | 30ms | 1ms |
| 채팅 메시지 저장 1건 (같은 조건에서 MySQL vs MongoDB) | MySQL 약 15ms | MongoDB 약 5ms |
| 네트워크 재연결 후 놓친 메시지 20건 복구 | 복구 불가 | 50~100ms |
Redis는 프로젝트 막바지에 공개된 CVE-2025-49844(CVSS 10.0, RCE)의 취약 버전 범위에 있었습니다. 7.0.15에서 7.2.11로 올리고 쓰지 않는 Lua 스크립팅을 비활성화했습니다. 패치 전후로 성능 차이는 없었습니다.
아직 못 한 것
- 수치는 대부분 단일 서버와 추정 트래픽 기준입니다. k6 같은 대규모 부하 테스트나 실제 멀티 인스턴스 운영까지는 가지 못했습니다.
- 서버를 여러 대로 늘렸을 때의 캐시 동기화와 장애 주입 테스트는 다음 과제로 남겼습니다.
- Coroutine으로 I/O를 넘기는 방식은 진짜 논블로킹과 다릅니다.
Dispatchers.IO스레드도 I/O가 끝날 때까지는 블로킹됩니다. 대기는 다른 스레드로 자리를 옮길 뿐입니다. - 메시지 순서는 서버 타임스탬프 기준으로 단일 서버 환경까지만 검증했습니다. 서버가 여러 대가 되면 시계 동기화가 새 변수로 들어옵니다.
더 보기
- 코드: GitHub 저장소
댓글
댓글 수정/삭제는 GitHub Discussions에서 가능합니다.