배포하는 코드, 제대로 알고 계신가요 — 8개월 걸린 사이드 프로젝트 Kongbu

GitHub 저장소를 AI 퀴즈로 바꿔 '내가 배포하는 코드'를 진짜 이해하게 만드는 서비스. 하루 만에 만든 Tokka와 정반대로, 이건 8개월이 걸렸다.

배포하는 코드, 제대로 알고 계신가요

얼마 전에 “카카오톡 분석 서비스를 하루 만에 배포한 이야기”를 썼다. 그 글이 자랑처럼 읽혔다면, 이 글은 그 반대편에 있는 이야기다.

Kongbu(공부)는 하루가 아니라 8개월이 걸렸다. 첫 커밋이 2025년 11월 8일이고, 오늘(2026년 7월 18일)에서야 “런치”라고 부를 만한 상태에 겨우 도달했다. 커밋 수는 251개.

하루짜리 서비스만 만들다 보면 착각하기 쉬운데, 어떤 아이디어는 그렇게 빨리 끝나지 않는다. 오히려 안 끝나서 문제인 경우가 더 많다.

뭐 하는 서비스냐면

한 줄로 말하면, GitHub 저장소를 퀴즈로 바꿔주는 서비스다. 내 레포를 연결하면 AI가 코드를 읽고, 그 코드에 대한 문제를 낸다. 일반적인 CS 문제가 아니라 내가 실제로 배포한 코드에 대한 문제다.

왜 이런 걸 만들었냐면, 요즘 코드를 짜는 방식이 좀 이상해졌기 때문이다. Copilot이 함수를 뱉고, Claude가 리팩터링을 하고, 나는 diff를 대충 훑고 머지 버튼을 누른다. 돌아가긴 한다.

그런데 두 달 뒤에 그 파일에서 버그가 나면, 내가 짠 적도 없는(정확히는 짠 기억이 없는) 코드를 다시 읽고 있다. “이거 누가 짰어” 하고 git blame을 돌리면 범인이 나다. 초기 README에 박아둔 문장이 이 상황을 나보다 잘 요약한다.

AI가 코드를 작성하지만, 당신은 그것을 이해하고 있나요? 지식 격차가 곧 기술 부채가 됩니다.

Kongbu 랜딩 페이지 — '배포하는 코드, 제대로 알고 계신가요?'라는 헤드라인과 useAuth 훅 코드로 만든 실제 퀴즈 예시

랜딩에 박아둔 퀴즈 예시가 딱 이 서비스의 요지다. useAuth 훅을 보여주면서 “이 상태 관리에 쓰인 React 패턴이 뭐냐”고 묻는다. 답은 Context + Hook 패턴이다. 별거 아닌 것 같지만, 이걸 자기 코드베이스 전체에 대고 물어보기 시작하면 생각보다 뜨끔한 순간이 많다.

이름은 그냥 “공부”다

이름 짓는 데 크게 고민하지 않았다. 코드를 공부하게 만드는 서비스니까 그냥 “공부(Kongbu)“다. 사실 처음엔 로마자로 “Gongbu”라고 썼는데, 발음이며 도메인이며 여러 이유로 첫날 커밋이 끝나기도 전에 “Kongbu”로 갈아엎었다. 커밋 로그에 Rebrand from Gongbu to Kongbu (#1)이 박제되어 있다. 로고는 {K}. 중괄호 안에 K를 넣은 건, 결국 이게 코드에 관한 서비스라는 걸 계속 상기시키고 싶어서였다.

사이드 프로젝트가 왜 이렇게 커졌나

솔직히 이 글에서 제일 하고 싶은 얘기가 이거다. 처음 기획은 단순했다. “레포 넣으면 퀴즈 나온다.” 그게 전부였다. MVP는 사실상 첫 주말에 나왔다. 11월 8일과 9일 이틀 동안 커밋 60개를 밀어넣으면서 백엔드, 프론트, 랜딩, 프로필, 스트릭, 퀴즈 공유, 코드 하이라이팅까지 다 붙였다. 그 이틀의 마지막은 인증 지옥이었는데, 401 에러 루프와 race condition으로 토큰이 날아가는 문제를 쫓다가 결국 Zustand store를 단일 진실 공급원으로 통합하고 나서야 잠잠해졌다. 커밋 로그에 debug: 인증 흐름 추적을 위한 상세 로깅 추가 같은 절박한 메시지가 그대로 남아 있다. 이후 11월 중순까지 잔손질을 하고 나니 쓸 만한 물건이 됐고, 여기서 끝냈어야 했는지도 모른다.

그런데 사이드 프로젝트에는 중력이 있다. 한번 굴러가기 시작하면 기능이 알아서 붙는다. git log를 훑어보면 이 표류의 궤적이 그대로 남아 있다.

  • 2026년 2월 22일: Pricing 페이지와 Stripe 과금을 붙였다. 사용자가 없는 서비스에 결제부터 붙인 것이다. 이 결정의 대가는 7월에 치르게 된다(뒤에 나온다).
  • 2026년 3월 29일: 퀴즈 생성 파이프라인을 하루 만에 갈아엎었다. 커밋 로그에 Phase 1, 2, 3이 같은 날 나란히 찍혀 있다. 그냥 “문제 만들어줘”라고 던지던 걸, Bloom’s taxonomy로 난이도를 설계하고, 초안을 만든 뒤 스스로 비평(critique)하고 다시 다듬는(refinement) 다단계 파이프라인으로 바꿨다. LLM한테 한 번에 좋은 문제를 기대하는 대신, 여러 번 되물어서 품질을 끌어올리는 쪽이다. 같은 날 아티클(공개 저장소 URL) 기반 퀴즈 생성도 붙었다.
  • 2026년 5월 5일: 여기서부터 좀 이상해졌다. 온라인 저지(Online Judge)를 붙였다. LeetCode 같은 거다. Monaco 에디터, 비동기 채점, Socket.IO로 결과 푸시, AI 피드백, 리더보드, 콘테스트, 에디토리얼, 제출 diff까지 — 이게 전부 어린이날 하루, 커밋 네 개다. “퀴즈 서비스”에 왜 온라인 저지가 필요한가? 라는 질문에 그때의 나는 나름의 대답이 있었던 것 같은데 지금은 잘 기억나지 않는다.
  • 2026년 7월 초: 게이미피케이션(XP, 레벨, 배지, 스트릭 프리즈)을 드디어 화면에 노출하고, 팀 온보딩 트랙과 저장소 코드에서 카타(kata) 문제를 자동 생성하는 백그라운드 워커를 붙였다. 퀴즈에서 틀린 개념에 맞는 저지 문제를 추천해주는 기능도 이때 들어갔다.

정리하고 보니 이건 사이드 프로젝트라기보다 그냥 제품이다. 8개월 동안 혼자(정확히는 AI 에이전트들과 함께) 이걸 붙이고 있었다는 게 새삼 좀 무섭다.

다만 “8개월 걸렸다”는 말에는 정정이 필요하다. 달력으로 8개월이지, 8개월 내내 만든 게 아니다. 월별 커밋을 세어 보면 11월 90개, 3월 71개, 7월 29개에 몰려 있고, 그 사이는 처참하다. 4월 3개, 5월 4개, 6월 6개 — 석 달 동안 커밋 13개다. 위 목록의 굵직한 변화들도 대부분 하루짜리 폭주다. 그러니까 이 프로젝트의 실체는 “8개월의 꾸준함”이 아니라, 며칠씩의 폭주와 몇 달씩의 방치가 번갈아 쌓인 것이다. 사이드 프로젝트가 오래 걸리는 이유는 일이 많아서라기보다, 안 하는 기간이 길어서다.

중간에 R08, R09, R10, R11 같은 리뷰 라운드가 커밋에 계속 등장한다. 여러 페르소나(디자인, 접근성, 보안, UX)로 스스로를 반복해서 리뷰한 흔적이다. 사이드 프로젝트에 코드 리뷰 프로세스까지 만들어버린 셈이다. 뻘짓이라면 뻘짓이고, 재밌었다면 재밌었다.

진짜 문제는 “안 켠 기능”이었다

7월에 런치를 준비하면서 알게 된 사실이 하나 있다. 런치 준비라고 해봐야 실제 작업일은 7월 2~3일과 마지막 날인 18일, 사흘이었는데, 그 사흘 동안 한 일의 대부분은 새 기능을 만드는 것이 아니라, 이미 몇 달 전에 만들어놓고 한 번도 켜지 않은 기능을 켜는 것이었다.

게이미피케이션이 대표적이다. XP, 레벨, 배지, 리더보드, 데일리 골, 스트릭 프리즈까지 백엔드와 API 래퍼가 전부 완성돼 있었다. 그런데 그걸 실제로 호출하는 클라이언트 코드가 사실상 0건이었다. 스트릭 카드 하나만 예외였고, 나머지는 DB 테이블과 엔드포인트만 존재하는 “다크 백엔드”였다. 콘테스트와 에디토리얼도 마찬가지였다. 목록을 읽어오는 화면은 있는데 정작 콘테스트를 만들거나 해설을 쓰는 API가 없어서, 사용자 입장에선 영원히 비어 있는 “죽은 탭”이었다.

이게 사이드 프로젝트 표류의 진짜 증상이라고 생각한다. 흔히 “기능이 자꾸 늘어난다”고 말하는데, 내 경우 실제 병증은 “만든 걸 켜지 않고 계속 쌓아둔다”에 가까웠다. 뒷단은 완성인데 앞단이 없으니, 밖에서 보면 아무것도 없는 것과 똑같다. 런치란 결국 새로 짓는 일이 아니라, 몇 달째 꺼져 있던 스위치들을 하나씩 올리는 일이었다.

진짜 어려웠던 건 메시지였다

기술적으로 제일 오래 붙잡은 게 파이프라인이었다면, 제품으로서 제일 오래 헤맨 건 “이걸 한 문장으로 뭐라고 설명할 거냐”였다.

초기 README와 랜딩의 카피는 이랬다. “AI 시대, 코드를 공부하다.” 틀린 말은 아닌데, 아무한테도 안 꽂힌다. 너무 넓고 착하다. “학습 플랫폼”이라는 단어를 보는 순간 사람들은 탭을 닫는다.

지금 라이브의 카피는 이렇게 바뀌었다. “배포하는 코드, 제대로 알고 계신가요?” 같은 서비스인데 훨씬 불편하다. 그리고 불편해서 좋다. “공부하세요”가 아니라 “너 지금 이해 못 하고 배포하고 있지?”라고 찌르는 쪽이다. 이 한 줄을 찾는 데 8개월 중 상당 부분이 들어갔다고 하면 과장이지만, 코드보다 이 문장 하나 고치는 게 더 어려웠던 건 사실이다.

세 가지 핵심 가치도 그 연장선이다. 코드 소유권 유지, 깊은 코드베이스 이해, 빠른 엔지니어 온보딩. 말은 그럴듯한데, 결국 다 “AI가 짠 코드를 네가 진짜 아느냐”의 다른 표현이다.

Kongbu 작동 방식 — GitHub 연결, AI 분석, 학습 & 마스터의 3단계 흐름을 보여주는 랜딩 섹션

동작 자체는 단순하다. GitHub 저장소를 연결하고, AI가 코드를 분석해서 문제를 만들고, 그걸 풀면서 이해한다. 객관식, O/X, 코드 완성 같은 문제 유형이 있고, 쉬움·보통·어려움으로 난이도가 나뉜다. 한국어와 영어 둘 다 지원한다. 문제 생성은 Azure OpenAI를 쓰는데, 자격 증명이 감지되면 알아서 붙도록 해뒀다.

그래서 실제로 뭐가 제일 쓸 만한가

이건 만든 사람의 편애가 섞인 얘기인데, 나한테 제일 와닿는 기능은 PR 기반 퀴즈다. 저장소 전체가 아니라 특정 Pull Request의 변경사항만 가지고 문제를 만든다. 코드 리뷰를 하기 전에, 그 PR이 정확히 뭘 바꾸는지 나한테 되물어보는 것이다.

사실 이 기능의 원래 출발점은 지금처럼 웹에서 버튼을 누르는 형태가 아니었다. 3월 13일에 먼저 만든 건 GitHub App 형태의 PR 봇이었다. 웹훅을 HMAC으로 검증하고, 설치 토큰과 PAT를 폴백으로 물리고, 큐로 작업을 멱등하게 처리해서, PR이 열리면 봇이 그 diff로 퀴즈를 만들어 check-run과 코멘트로 PR에 직접 붙여주는 구조였다. 즉 “리뷰 전에 되묻기”를 사람이 웹에 들어와서 하는 게 아니라, PR 화면 안에서 봇이 먼저 문제를 던지게 하려던 것이다. 지금 웹 UI의 PR 퀴즈는 그 아이디어를 좀 더 얌전한 형태로 옮겨온 셈이다.

“LGTM 👍” 찍고 넘어가던 리뷰가, 문제 세 개 틀리고 나면 갑자기 진지해진다. 남이 짠 코드든 AI가 짠 코드든, 승인 버튼을 누르는 순간 그건 내 책임이 되니까.

또 하나는 온라인 저지에 붙인 카타 자동 생성(Repo Kata)이다. 저장소 코드를 백그라운드 워커가 읽어서, 유틸이나 파서, 알고리즘처럼 자기완결적인 함수를 골라 언어에 무관한 stdin/stdout 문제로 바꾼다. 여기서 재밌는 결정을 하나 했는데, 이렇게 만든 문제를 그냥 저장하지 않는다. 레퍼런스 솔루션을 실제 채점기(Judge0)에 돌려서 전 테스트케이스를 통과할 때만 저장한다. 실패하면 채점 리포트를 다시 넣어 한 번 더 생성해보고, 그래도 안 되면 그 문제는 거부한다. 채점기를 검증기로 재사용한 것이다. 코드를 눈으로 이해하는 걸 넘어 직접 손으로 다시 짜보게 만들되, 애초에 풀리지 않는 문제가 나가지 않도록 스스로를 검증하게 해뒀다. 퀴즈에서 틀린 개념에 맞는 저지 문제를 추천해주는 것도 같은 맥락이다. “이 부분 약하네” 하고 끝내는 대신 바로 풀 문제를 던져준다.

팀 온보딩 기능도 이 무렵에 제대로 켰다. 이건 만들기 전에 뭘 만들지가 더 중요했던 경우인데, 한참 들여다보고 나서야 “빠진 건 진도 추적이 아니라 배정(assignment) 레이어”라는 걸 깨달았다. 이미 학습 경로와 모듈, 사용자별 진도 테이블은 다 있었다. 없던 건 “누구에게 어떤 경로를 맡길 것인가”였다. 그래서 배정 테이블 하나와 팀의 기본 학습 경로 설정만 추가했고, 셀링포인트는 초대를 수락하는 순간 온보딩이 자동으로 시작된다는 것으로 잡았다. 광고 문구로만 있던 Team 플랜 기능이 그제서야 실제로 동작하기 시작했다.

어떻게 굴러가나

기술 스택은 평범한 편이다. 프론트는 Next.js 14(App Router) + TypeScript + Tailwind + shadcn/ui + Zustand, 백엔드는 Express + TypeScript에 PostgreSQL과 Redis. AI는 Azure OpenAI, 저장소 접근은 GitHub OAuth와 PAT를 섞어 쓴다. 조직 사설 저장소를 위해 PAT를 별도로 물릴 수 있게 해둔 게 실무에서 은근히 쓸모 있었다.

재미없어 보이지만 나한테 제일 중요했던 부분은 배포 파이프라인이다. Tokka 때도 썼던 얘기인데, 이미 굴러가는 인프라가 있으면 “만드는 것”과 “배포하는 것” 사이의 거리가 확 줄어든다.

Kongbu도 Gitea CI로 이미지를 빌드하고, ArgoCD가 K8s 클러스터에 배포하는 GitOps 구조를 그대로 탄다. DB 마이그레이션은 ArgoCD의 sync 훅 Job으로 배포 직전에 자동 적용되게 만들었고, sync-wave를 걸어 postgres → 마이그레이션 → 앱 순으로 흐르게 했다. 마이그레이션이 실패하면 앱 롤아웃 자체가 막힌다. 깨진 스키마 위에 새 코드가 올라가는 상황을 원천 차단하려는 것이다.

그런데 이 깔끔한 구조는 사실 아주 지저분한 사고의 결과물이다. 어느 날 보니 dev DB가 통째로 비어 있었다. 테이블이 하나도 없었다. 알고 보니 postgres 볼륨이 재생성됐는데 초기 스키마(init.sql)를 자동으로 다시 넣어주는 경로가 애초에 없었다. 클러스터가 fresh DB를 스스로 부트스트랩한 적이 단 한 번도 없었던 것이다. 게다가 마이그레이션 러너가 세 벌이나 난립해 있었고, 그중 일부는 아무 데서도 호출되지 않는 죽은 코드였다. 심지어 롤백용 SQL까지 정렬되어 실행되는 바람에 프로덕션 인덱스를 드롭할 뻔했다. “base 스키마를 마이그레이션 #0으로 두는 단일 소스” 구조는, 6월 27일에 이 난장판을 치우면서 나온 결론이다. 결과만 보면 깨끗하지만 계기는 “DB가 텅 비어 있다”는 사고였다.

배포 배관 쪽도 조용히 삐걱댔다. 이미지 레지스트리가 502를 뱉기 시작해 push가 실패했고(뒤에 있는 NFS 마운트가 유실된 게 원인이었다), 그 바람에 IaC의 이미지 태그가 갱신되지 않아 배포가 한동안 정체됐다. 정작 웃긴 건 테스트와 CI였다. 런치 직전에 “출시 준비가 됐나”를 점검해 보니, 서버 소스 157개 파일에 테스트 파일이 두 개, 클라이언트는 141개 파일에 0개였다. 클라이언트 package.json에 "test": "jest"가 선언돼 있긴 했는데 jest 설정도 테스트도 없어서 사실상 죽은 스크립트였다. 이미지를 빌드하는 Gitea CI는 있었지만 테스트를 돌리는 CI는 없었고, 7월 2일에야 typecheck·lint·test·build를 도는 워크플로를 처음으로 붙였다. 굴러가긴 오래 굴러갔는데, 안전벨트는 런치 직전에야 맸다.

이 정도 깔아두면 git push 이후는 대체로 알아서 흘러간다. 그리고 이 8개월의 상당 부분은 이런, 아무도 안 봐주는 “배관”을 손보는 데 들어갔다. 화면에 보이는 기능보다 안 보이는 파이프라인에 시간이 더 든다는 게 사이드 프로젝트의 흔한 함정이다.

켜고 보니 새어 나가던 것들

런치 준비는 “안 켠 기능을 켜는” 일이라고 했는데, 켜고 나니 그동안 조용히 새고 있던 버그들이 드러났다.

제일 아팠던 건 과금이었다. 2월에 붙여둔 Stripe 얘기다. 게이팅이 두 층으로 나뉘어 있었는데, 결제 웹훅이 사용자의 플랜 값만 갱신하고 정작 비용을 막는 LLM 쿼터 등급은 건드리지 않았다. 그러니까 Pro를 결제해도 하루 생성 한도가 무료와 똑같이 걸려 있었다. 돈은 받는데 서비스는 안 풀리는, 수익화 관점에서 제일 나쁜 종류의 버그다. 웹훅에서 등급과 한도를 함께 동기화하고, 이미 결제한 사용자는 백필 마이그레이션으로 메꿨다.

퀴즈 생성이 95%에서 영원히 멈추는 증상도 있었다. 완료를 알리는 소켓 이벤트를 놓친 작업(연결이 끊기거나 새로고침한 경우)이 영영 대기 상태로 남았다. 그래서 워치독이 주기적으로 폴링해 완료를 대신 처리하고, 오래 응답이 없으면 실패로 돌려 한 번에 재시도할 수 있게 했다. 화면상 “거의 다 됐어요”에서 멈춰 있는 진행바만큼 사람을 조용히 떠나게 하는 것도 없다.

솔직한 후기 — 랜딩에서 지운 것들

자랑만 하면 재미없으니 솔직한 얘기도 하나 하자면, 런치를 준비하면서 랜딩 페이지에서 가장 많이 한 일은 뭔가를 더한 게 아니라 지운 것이다.

한때 랜딩에는 “AI가 코드의 상당 부분을 작성한다”는 미검증 수치, “500+ 개발자”·“10,000+ 퀴즈” 같은 고정 통계, 그리고 그럴듯한 사용자 후기 몇 개가 박혀 있었다. 사회적 증거(social proof) 자리를 임시로 채워둔 것들이었다. 런치 직전에 이걸 전부 들어냈다. 아무도 안 쓴 서비스의 “사용자 수” 칸을 정직하게 비워두면 그것대로 안 팔린다는 딜레마는 여전한데, 그렇다고 없는 숫자와 없는 후기를 진짜인 척 박아두는 건 이 서비스가 하려는 이야기(“네가 배포하는 걸 정말 아느냐”)와 정면으로 충돌했다. ROI를 자랑하던 자리도 사용자가 자기 숫자를 직접 넣어보는 시나리오로 바꿨다.

그리고 8개월을 쓴 것 치고, 이게 정말 사람들이 원하는 물건인지는 여전히 잘 모르겠다. “내가 배포하는 코드를 이해하고 있느냐”는 질문은 나한테는 절실했지만, 남들도 그런 죄책감을 느끼는지는 별개의 문제다. 이건 붙여봐야 안다.

확실한 건 두 가지다. 첫째, 하루 만에 만든 서비스든 8개월 걸린 서비스든, “배포까지 가는 길”을 미리 깔아두면 아이디어에서 프로덕션까지의 거리는 계속 짧아진다. 둘째, 코드는 AI가 점점 더 많이 짜겠지만, 그 코드를 이해할 책임은 아직 나한테 남아 있다. Kongbu는 그 불편한 사실을 계속 상기시키려고 만든 서비스다.

GitHub 계정이 있고 공개 저장소가 하나라도 있다면, kongbu.jiun.dev에서 직접 자기 코드로 퀴즈를 만들어볼 수 있다. 공개 저장소는 무료다. 몇 문제 틀리고 나면, 배포 버튼을 누르기 전에 diff를 한 번 더 보게 될지도 모른다.

Comments

Loading comments...