뭘 만들지도 모른 채 시작해서, 돈 받는 프로덕트가 되기까지 14개월
2025년 5월, 뭘 만들지도 정하지 못한 채 시작한 프로젝트가 14개월 뒤 결제와 교사 매칭, AI 리포트 생성이 돌아가는 유료 프로덕트가 됐습니다. 그 사이에 아키텍처를 두 번 접었고, 상품 하나를 만들었다 제거했고, 장애를 겪었습니다. 만든 것보다 접은 것 중심으로 정리한 회고입니다.
숫자부터 적겠습니다. 14개월, 팀 4명에 개발자는 저 하나, 백엔드 93,000줄에 커밋 940개(피크 월 314개), 릴리즈 태그 60개. 결제(PG 2사), 학생-교사 매칭, Temporal 기반 AI 리포트 생성 파이프라인이 프로덕션에서 돌아가고, 기업 고객을 포함한 유료 사용자가 있습니다.
이렇게 쓰면 계획대로 착착 진행된 것처럼 보이지만, 실제로는 아키텍처를 통째로 두 번 접었고, 상품 하나를 만들었다가 제거했고, 완료 이벤트가 조용히 유실되는 장애를 겪었습니다. 이 글은 만든 것의 목록이 아니라 접은 것과 그때의 판단을 중심으로 쓴 회고입니다. 잘 만든 이야기보다 접은 이야기가 다음 판단에 더 쓸모 있다고 믿어서입니다.
0. 뭘 만들지 모르는 채로 시작한 두 달 (2025.05~06)
시작은 “입시 교육 도메인에서 뭔가 만들자”였습니다. 뭘 만들지는 정해지지 않았습니다. 두 달 동안 코드는 한 줄도 없었고 기획과 검증만 있었습니다.
지금 보면 이 두 달이 낭비가 아니었습니다. 첫 코드가 플랫폼이 아니라 Python 데모 서버 하나(2025.07)였던 건 이 기간 덕분입니다. “학생 데이터로 진단 리포트를 만들어 주면 돈을 낼까?”라는 가설 하나만 검증하는 리포트 생성기. 커밋 6개짜리였지만, 이후 14개월 동안 만든 모든 것이 이 가설 위에 서 있습니다.
1. 첫 번째 접기: Java 모놀리스 (2025.08~11)
데모에 반응이 있어서 제대로 된 플랫폼을 만들기 시작했습니다. Java/Spring 모놀리스로 석 달, 커밋 94개. Dockerfile과 배포 스크립트까지 갖췄습니다.
그런데 이 시기의 저는 “제대로 만든다 = MSA”라고 믿었습니다. 회사에서 마이크로서비스를 다루고 있었고, 읽는 아티클마다 서비스 분리와 도메인 경계 이야기였습니다. 모놀리스는 임시고 정답은 MSA라는 생각으로, 만들던 모놀리스를 접고 갈아탔습니다.
2. 두 번째 접기: MSA 넉 달 (2025.12~2026.03)
account, auth, payment, notification, lms, supporters… JVM 서비스 8개에 API gateway, 공통 라이브러리까지. 교과서적으로 갔습니다.
넉 달 만에 접었습니다. 이유는 기술이 아니라 산수였습니다.
- 운영이 감당이 안 됩니다. 서비스 8개는 배포 파이프라인 8개, 로그 스트림 8개, 장애 지점 8개입니다. 낮에는 회사를 다니는 1인 개발자에게 “새벽에 어느 서비스가 죽었는지 찾기”는 성립하지 않는 운영 모델입니다.
- 인프라 비용이 사용자보다 먼저 스케일됩니다. 사용자가 늘기 전에 인스턴스와 DB, 게이트웨이 비용이 먼저 늘어 있었습니다.
MSA가 틀린 게 아니라, 조직 없는 MSA가 틀린 것이었습니다. 서비스 경계는 팀 경계를 따라갈 때 의미가 있는데 저는 팀이 아니라 한 명이었습니다. 교과서의 정답이 내 제약 조건(1인, 예산) 위에서는 오답이 될 수 있다는 걸, 넉 달치 스캐폴딩을 버리면서 배웠습니다.
3. 세 번째 시도: 모듈러 모놀리스 (2026.03.20~)
다시 모놀리스로 돌아왔습니다. 단, MSA 넉 달이 완전한 낭비는 아니어서, 그때 그린 도메인 경계를 Gradle 멀티모듈로 가져왔습니다. 물리적으로는 한 덩어리, 논리적으로는 bounded context별 모듈. 경계는 네트워크가 아니라 컴파일러가 지킵니다.
속도 차이는 극적이었습니다.
- 재작성 시작 5일 만에 첫 프로덕션 릴리즈
- 16일 만에 PG 결제 릴리즈 (분산락 동시성 제어 포함)
- 3.5개월간 커밋 940개, 릴리즈 태그 60개
같은 사람이 같은 도메인을 만드는데 속도가 몇 배가 된 이유는 하나입니다. 아키텍처가 제약 조건과 싸우지 않게 됐기 때문입니다. 이 구조를 어떻게 나눴는지는 bounded context 기준 도메인 모듈 분리와 헥사고날 아키텍처 적용기에 따로 정리했습니다.
4. 만들었다 제거한 상품: 멤버십
4월에 멤버십 상품을 만들었습니다. enrollment 부여, 코인 자동 지급, 의뢰 생성 시 활성 멤버십 검증까지 붙였습니다. 그리고 제거했습니다.
문제는 코드가 아니라 순서였습니다. 멤버십이 뭘 의미하는지 정하지 않은 채로 멤버십을 만들었습니다. 기획자도 저고 개발자도 저니까 “일단 있으면 좋을 것 같은” 개념이 검증 없이 코드가 된 겁니다. 막상 실제 사용자의 결제 흐름을 보니 필요한 건 코인을 사서 그걸로 결제하는 단순한 원장이 전부였고, 저는 그걸 크레딧이라는 개념으로 확대해석해 상품 계층을 하나 더 만들어 버린 것이었습니다.
멤버십은 지금 구독 결제 상품과 함께 재도입을 준비하고 있습니다. 이번엔 순서가 반대입니다. 구독으로 뭘 제공할지가 먼저 정의됐고, 코드는 그다음입니다. 같은 기능도 요구가 정의되기 전에 만들면 부채고, 정의된 후에 만들면 자산이 된다는 걸 이 상품으로 배웠습니다.
5. 첫 매출 (2026.06)
첫 결제는 개인 사용자가 아니라 B2B였습니다. 다른 지역의 학원 두 곳이 진단 서비스를 구매했습니다. 이후 입시 기업 한 곳과 진단 리포트 상품 공급 계약으로 이어졌습니다.
첫 매출에서 배운 건 단순합니다. 사줄 사람을 찾아다닌 게 아니라, 데모를 보여줄 수 있는 상태를 유지한 것이 영업이었습니다. 0단계의 Python 데모부터 지금까지, “지금 당장 보여줄 수 있는 동작하는 물건”이 있는 상태와 없는 상태는 대화의 종류가 다릅니다.
이 관계는 다음 상품으로도 이어지고 있습니다. 제가 냈던 아이디어 하나가 팀 논의를 거치며 처음과 다른 형태의 대화형 서비스로 피보팅됐고, 고객사에서 좋은 반응을 얻어 진행 중입니다. 개발자의 아이디어가 원안 그대로 살아남는 게 아니라 도메인을 아는 사람들의 손을 거쳐 팔리는 형태가 된다는 것도, 이 팀에서 배운 것 중 하나입니다.
6. 혼자 만든 게 아니었다
개발자는 14개월 내내 저 혼자였지만, 팀은 넷이었습니다. 최상위권 입시를 직접 통과한 현역 대학생, 현직 학원 원장, 입시 연구소장. 저만 빼고 전부 입시를 아는 사람들입니다.
처음에는 역할 분담이 명확하다고 생각했습니다. 입시 논의는 세 사람이 하고, 저는 그 결론을 구현하면 된다고요. 그런데 아니었습니다. 진단 리포트의 등급 로직 하나를 짜려 해도 5등급제와 9등급제가 왜 섞여 있는지 알아야 하고, “이 화면에서 학부모가 뭘 보고 싶어 하는가”를 같이 논의할 수 있어야 요구사항이 코드가 되기 전에 걸러집니다. 도메인을 모르는 개발자에게는 회의마다 번역이 필요하고, 아는 개발자에게는 회의가 곧 설계가 됩니다.
그리고 그 차이는 결국 코드에 드러났습니다. 수강권과 진단 이용권을 같은 사용권 모델로 합치지 않은 것, 진단 상품과 수업 상품의 경계를 나눈 것 같은 모델링 결정들은 전부 도메인 논의에서 나온 것이지 기술 판단이 아니었습니다. “개발만 잘하면 된다”로 시작해서 “잘 개발하려면 도메인을 알아야 한다”로 끝난 게 이 팀에서 배운 가장 큰 것입니다.
물론 매끄럽기만 했던 건 아닙니다. 넷 다 각자의 본업이 있어서 이 일은 늘 새벽에 이뤄졌고, 누군가는 답장을 하다가 죽은 듯이 잠들어 다음 날까지 연락이 안 되곤 했습니다. 초반에는 그게 서운함이나 사소한 오해가 되기도 했는데, 시간이 지나며 알게 됐습니다. 연락이 안 되는 게 무책임이 아니라 각자의 한계까지 하고 있다는 뜻이라는 걸요. 서로의 리듬을 이해하고 나서야 팀이 됐습니다.
일하는 방식도 바뀌었습니다. 초기에는 문서랄 게 없었습니다. 논의는 메신저에 흩어지고, 결정은 기억에 의존하고, “그거 하기로 했잖아”의 ‘그거’가 사람마다 달랐습니다. 개발자가 저 하나뿐이라 요구사항이 유실되면 그 비용도 전부 제 몫이었고, 그래서 제가 주도해서 Linear를 들였습니다. 논의가 티켓이 되고, 티켓이 구현과 배포로 이어지는 흐름이 생기고 나서야 비개발 팀원들과의 협업이 “대화”에서 “프로세스”가 됐습니다. 프로덕트를 만드는 일에는 코드베이스 말고도 정리가 필요한 시스템이 하나 더 있었던 셈입니다.
7. 가장 아팠던 장애
리포트 생성이 성공했는데 사용자에게는 영원히 “생성 중”으로 보이는 버그였습니다. 워커가 완료 이벤트에 잘못된 ID를 실어 보내고, 백엔드 가드는 그걸 조용히 버리고 있었습니다. 에러 로그 한 줄 없이요.
그나마 다행이었던 건 생성 파이프라인이 부분 결과 스냅샷을 남기는 구조여서, 유실된 건들을 수동 재실행으로 복구할 수 있었다는 점입니다. 아이러니한 건, 그 스냅샷 코드가 나중에 제가 “플랫폼 제약의 우회물이니 삭제 대상”이라고 판정한 바로 그 수동 체크포인트 시스템이었다는 겁니다. 못생긴 코드였지만 장애의 밤에는 그게 복구 수단이었습니다. 다만 “사람이 수동으로 돌린다”가 전제인 복구는 1인 운영에서 지속 가능하지 않습니다.
이 장애가 아팠던 건 원인이 한 줄짜리 버그여서가 아니라, 추적해 보니 그 버그가 살 수 있었던 계약 구조 자체가 문제였기 때문입니다. 결국 버그 수정 대신 실행 기반을 통째로 옮기는 결정으로 이어졌고, 그 과정은 7,600줄 Lambda 오케스트레이터 이전기에 따로 썼습니다. 복구가 수동 스냅샷이 아니라 워크플로 엔진의 기본 동작이 되는 구조로요. 요지만 옮기면: 복잡한 코드를 만나면 “무엇이 이 복잡함을 강제했나”를 먼저 물어야 하고, 답이 플랫폼 제약이라면 해법은 리팩토링이 아니라 플랫폼 교체입니다.
8. 혼자서 프로덕션을 굴리는 법
1인 프로덕트의 진짜 제약은 개발 속도가 아니라 내가 자는 동안입니다. 그래서 구조가 사람을 대신해야 합니다.
- 상태 전이는 Outbox와 재시도 백오프로: 이벤트 유실이 사람 손을 기다리지 않게
- 결제 동시성은 분산락과 PENDING 복구 배치로: 웹훅이 꼬여도 돈이 안 새게
- PG는 두 곳으로: 7월에 결제 채널 장애가 났을 때 hotfix로 채널을 다중화했고, 이후는 한 PG의 장애가 매출 전체의 장애가 아니게 됐습니다
- 반복 작업은 AI에게: 이슈 생성, PR 작성, 리뷰 반영을 코딩 에이전트의 커스텀 스킬로 만들어 두고, 저는 판단이 필요한 일만 합니다. 14개월 중 마지막 3.5개월의 커밋 밀도(월 300+)는 이 워크플로우 없이는 불가능했습니다
한 가지 짚어 둘 것은, AI 위임이 통한 건 도구 덕이 아니라 아키텍처 덕이라는 점입니다. 계층 규칙과 모듈 경계, “이 코드는 어디에 놓인다”가 컨벤션 문서로 정리돼 있으니, 에이전트가 만든 코드가 제자리에 떨어지고 저는 판단만 검토하면 됩니다. 구조가 없는 코드베이스에서 AI는 결과를 예측할 수 없는 도구지만, 구조가 명확한 코드베이스에서는 위임 가능한 작업의 단위가 명확해집니다. 3번에서 말한 “아키텍처가 제약 조건과 싸우지 않게 됐다”의 후반전이 이것입니다. 아키텍처가 사람의 속도를 올린 만큼, AI의 속도도 올렸습니다.
9. 아직 못 한 것
정직하게 적습니다.
- 지표 기반 개선이 약합니다. 만들고 파는 것까지는 왔는데, “이 기능이 전환율을 얼마나 바꿨나”를 묻는 루프는 아직 못 만들었습니다.
- 규모를 겪지 않았습니다. 지금 사용자 규모에서 초당 수천 건의 문제는 존재하지 않습니다. 다만 자원 한계 상황의 설계(OOM, 동시성, 백프레셔)는 다른 자리에서 겪어 봤고, 그 감각으로 미리 만들 것과 미루는 것을 구분하고 있습니다.
- Temporal 이전이 진행 중입니다. 종합진단 파이프라인 컷오버가 끝나면 Lambda가 은퇴하고, 그 결산은 후속 글로 남길 예정입니다.
정리: 사이드 프로젝트와 프로덕트의 차이
14개월 전의 저에게 한 문장만 보낼 수 있다면 이걸 보내겠습니다.
아키텍처는 야망이 아니라 제약 조건에 맞춰라. 상품은 정의되기 전에 만들지 마라. 그리고 언제든 보여줄 수 있는 상태를 유지해라.
사이드 프로젝트와 프로덕트의 차이는 코드 품질이 아니었습니다. 남의 돈이 들어오고 남의 업무가 내 시스템 위에서 돌아가기 시작하면, 그러니까 되돌릴 수 없는 책임이 생기는 순간부터 모든 판단의 기준이 바뀝니다. 그 기준으로 두 번 접었고, 접은 만큼 빨라졌습니다.