$cat projects/1/README.md

PiKi

2026.03 - 서비스 중4FE 3BE 3DesignBackend DeveloperIT 연합 동아리 Depromeet 18기 팀 프로젝트

여러 쇼핑몰에 흩어진 위시를 한곳에 모아 1:1 토너먼트로 '진짜 사고 싶은 것'을 고르게 돕는 쇼핑 어시스턴트. 위시 수집부터 토너먼트 결정, 결과 영수증까지 하나의 흐름으로 연결한 서비스로, 양대 스토어에 출시했습니다. 두 달마다 돌아오는 AWS 계정 이관을 워크플로 하나로 자동화한 이야기를 확인해보세요.

KotlinSpring BootJavaJPAMySQLRedisAWSTerraformSentryGitHub Actions
▸ github▸ behance▸ app store▸ google play
$cat overview.md

PiKi는 여러 쇼핑몰(29CM·무신사·지그재그 등)에 흩어진 위시를 한곳에 모아, 비슷한 가격대끼리 자동 매칭한 1:1 토너먼트로 '진짜 사고 싶은 것'을 가려내는 쇼핑 어시스턴트입니다.

App Store·Google Play에 출시해 운영 중이며, 최종 발표에서 대상과 모의투자 최다 유치 1위를 기록했습니다.

쇼핑몰로 나가는 외부 요청이 많아, 그 부하가 메인 서버에 번지지 않도록 렌더·추출을 별도 모듈로 떼어낸 구조로 설계했습니다.

저는 이 서비스의 백엔드 전반을 맡았습니다. 버그가 나면 증상에서 멈추지 않고 원인을 데이터로 확인할 때까지 파고들고, 고친 뒤에도 그 수정이 진짜 문제를 막는지 테스트로 확인합니다.

$cat role.md
$cat highlights.md
외부 요청을 격리한 멀티 모듈 구조

외부 쇼핑몰로 나가는 요청이 많은 서비스라, 그 부하와 불안정이 메인 API에 번지지 않도록 렌더·추출을 별도 모듈로 분리했습니다. 링크가 들어오면 봇 탐지를 피하는 헤드리스 브라우저 렌더가 페이지를 열고 추출 모듈이 상품 정보를 구조화해 메인 API에 넘기는 식으로, 무거운 외부 작업과 핵심 로직을 격리했습니다.

버튼 한 번으로 끝나는 AWS 계정 이관

운영비가 없는 동아리 프로젝트라 서버를 AWS 크레딧으로 운영했는데, 크레딧이 계정 단위로 주어져 약 두 달마다 서비스 전체를 새 계정으로 옮겨야 했습니다. 서버와 DB, 이미지 버킷, 설정값, 권한까지 통째로 옮기는 일이 1년에 여섯 번 돌아오는 셈이었습니다. 첫 이관은 직접 단계별 절차를 정리해 수동으로 진행했는데 컷오버에 15분이 걸렸고, 그중 13분은 컷오버 중에 새 서버를 준비하다 세 번 재시도한 시간이었습니다. 그래서 이관 전체를 GitHub Actions 워크플로 하나로 옮겼습니다. 새 계정 발판, 설정값과 권한 이식, 레포 3개의 Terraform, 서버 준비, 운영에 영향 없는 리허설(덤프, 복원, 행수 대조), 새 계정 사전 배포까지 모두 컷오버 앞에서 끝내고, 컷오버에는 쓰기 정지 · DB 이식 · 행수 대조 · DNS 전환만 남겼습니다. 이미지도 쓰기 정지 전에 대부분 옮겨 두고, 정지 뒤에는 그 사이 늘어난 분량만 옮깁니다. 인프라를 만들고 부수는 워크플로라 안전장치부터 설계했습니다. 실행하려면 MIGRATE를 직접 입력해야 하고, 대상 계정번호를 실제 접속한 계정과 대조하며, DNS가 이미 새 계정을 가리키면 새 DB를 덮어쓰지 않도록 멈춥니다. 쓰기 정지 뒤 어느 단계에서 실패하거나 DNS 전환이 시간 안에 확인되지 않으면 기존 서버가 자동으로 되살아나고, 같은 워크플로를 단계 단위로 다시 실행할 수 있습니다. 새 계정에는 장기 키 대신 OIDC로만 접근해 이관이 끝나도 지울 자격증명이 남지 않고, DB 비밀번호는 서버 안에서만 읽혀 실행 로그 어디에도 지나가지 않습니다. 모든 단계의 시작과 결과는 Discord로 보고되어 어디서 막혔는지 바로 보입니다. 사람이 할 일은 새 계정 CloudShell에 스크립트를 한 번 붙여넣는 것과 안내받은 DNS 레코드를 바꾸는 것뿐이고, dev로 먼저 돌려 검증한 뒤 같은 워크플로로 prod를 옮겼습니다.

토큰 회전 동시성 제어

동시 요청이 몰려도 토큰 회전이 정확히 한 번만 일어나도록 판정을 Redis Lua로 원자화하고, 회전 직후 grace 창으로 정상 동시 갱신과 도난을 구분했습니다. 수정이 진짜 경쟁 상태를 막는지 8스레드 동시 호출 테스트로 검증했습니다.

Discord 봇 기반 운영 인증 게이트

백오피스가 외부에 열려 있는 만큼, '누가 들어올 수 있나'가 기능만큼 중요했습니다. Discord 슬래시커맨드로 접근을 열되 인터랙션을 Ed25519 서명으로 직접 검증하고, JDK 기본 구현의 raw 공개키 처리 문제를 피해 BouncyCastle로 검증하며 오래된 요청은 재생 공격으로 막아, 계정 없이도 운영자만 접근하게 만들었습니다.

트래픽이 몰릴 때의 GC 멈춤

외부 방문객을 초청한 데모데이에 사용자가 몰리며 앱이 멈칫하던 걸, Grafana로 GC를 추적해 원인을 찾았습니다. 컨테이너가 cgroup 메모리를 잘못 인식해 JVM이 Serial GC로 돌고 있었고, 힙만 늘리면 STW가 더 길어지는 구조라 G1으로 바꿔 멈춤을 없앴습니다.

$ls architecture/
AWS 자원 구조
AWS 자원 구조
클릭해서 크게 보기
$ls screenshots/
앱 공유로 위시 담기
클릭해서 크게 보기
앱 공유로 위시 담기
이미지로 위시 담기
클릭해서 크게 보기
이미지로 위시 담기
링크로 위시 담기
클릭해서 크게 보기
링크로 위시 담기
토너먼트로 고르기
클릭해서 크게 보기
토너먼트로 고르기
결과 영수증 저장·공유
클릭해서 크게 보기
결과 영수증 저장·공유
친구 초대
클릭해서 크게 보기
친구 초대
운영 백오피스
클릭해서 크게 보기
운영 백오피스
알림 템플릿 편집·미리보기
클릭해서 크게 보기
알림 템플릿 편집·미리보기
공지 작성·예약·발송
클릭해서 크게 보기
공지 작성·예약·발송
운영 통계 대시보드
클릭해서 크게 보기
운영 통계 대시보드
추출 라우팅 정책
클릭해서 크게 보기
추출 라우팅 정책
Discord 인증 게이트
클릭해서 크게 보기
Discord 인증 게이트
$cat results.md
▸
대상 + 모의투자 최다 유치 1위

Depromeet 18기에서 대상(1위)을 수상하고, 모의투자에서 최다 유치 1위를 기록했습니다.

▸
App Store · Google Play 정식 출시

기획부터 양대 스토어 심사 통과와 정식 출시까지 완주했고, 누적 사용자 1,000명을 넘었습니다.

▸
이관 컷오버 15분 → 1분 53초

prod 이관에서 쓰기 정지부터 DNS 전환 확인까지 걸린 시간입니다(DNS 전파 시간 제외). 계정 발판부터 검증까지 전 과정은 약 23분이었습니다.

$cat retrospection.md
잘 됐던 것

가장 크게 남는 건 개발 흐름 자체를 커맨드와 스킬로 자동화한 경험입니다. 이슈 생성과 보드 정리부터 작업·테스트, PR 생성과 리뷰어 지정, 리뷰 봇, 배포 승격, 운영 지표 조회까지 하나의 파이프라인으로 엮었습니다. PR을 만드는 스킬 하나만 서른 번 넘게 다듬었고, 반복되던 일을 흐름에서 걷어내면서 팀의 속도와 일관성이 눈에 띄게 올라갔습니다.

버그는 증상에서 멈추지 않고 근본 원인까지 파고들었습니다. 로그아웃은 동시성과 시간, DB 500은 트랜잭션 전파, 힙 경고는 GC 알고리즘처럼, 추측 대신 지표로 추적하고 문제를 재현해 원인을 좁혔습니다. 고친 뒤에는 수정을 일부러 되돌려 테스트가 실패하는지 확인해, 그 테스트가 진짜 문제를 잡는 장치인지까지 검증했습니다.

비개발 팀원이 개발자를 거치지 않고 공지·알림을 관리하고 지표를 확인할 수 있게 만든 백오피스는, 팀이 서비스를 더 주도적으로 다루는 도구가 됐습니다.

개선할 것

기능별 부하 테스트는 했지만, 서버 자체가 실제 동시 트래픽을 어디까지 감당하는지는 미리 밀어보지 못했습니다. 그 탓에 GC 설정 같은 서버 레벨의 한계가 데모데이 트래픽에서야 드러났습니다. 다음엔 개별 로직뿐 아니라 서버 전체를 부하로 먼저 검증하려 합니다.

배운 것

보안과 사용성처럼 상충하는 요구는 'grace 창', 'TTL allowlist'처럼 시간·범위를 설계 변수로 두면 양립시킬 수 있다는 걸 배웠습니다. 또한 한계와 범위를 정직하게 명시하는 것이 팀과 사용자의 신뢰를 만든다는 것을 실감했습니다.