포스트

RummiArena 바이브 로그 2026-04-03 상세 해설

RummiArena 바이브 로그 2026-04-03 상세 해설

원문: 바이브 로그 2026-04-03
프로젝트: RummiArena — 루미큐브 온라인 멀티플레이어 + AI 대전 플랫폼
해설 작성: 2026년 4월 4일
전편 연결: [2026-04-02 해설] 8/13 파이프라인 달성, Docker DinD라는 마지막 벽


개요: 이 문서는 어제의 속편이다

어제(4월 2일) 로그가 “같이 일 못하겠다”는 탄식으로 시작해 8/13으로 마감된 미완의 하루였다면, 오늘(4월 3일) 로그는 그 미완을 완성하는 하루다. 17개의 초록불, 362개의 E2E 테스트 통과, 그리고 AI 대전 성능 데이터. 이 세 가지가 오늘의 뼈대를 이룬다.

표면적으로는 “다 됐다”는 성공담처럼 보이지만, 그 안을 들여다보면 어제 못지않은 밀도가 있다. DinD에서 Kaniko로의 전환, 빌드 직렬화라는 한 줄의 발견, 34개 E2E 테스트를 모두 초록으로 만든 3단계 cleanup 전략, AI 모델의 게임 규칙 이해도를 숫자로 측정하는 방법. 그것들을 하나씩 풀어낸다.


1장. 17/17 — 완성된 파이프라인의 의미

1.1 숫자의 무게

“17/17”은 숫자로 읽으면 단순하다. 하지만 이 숫자에 도달하기까지 Pipeline #94, #95, #96 세 번의 시도가 필요했다. 어제의 10번 실패에 오늘의 3번 실패를 더하면, 이 파이프라인 하나를 완성하는 데 총 13번의 실패가 있었다는 뜻이다.

그런데 이 숫자가 중요한 이유는 단순히 “됐다”가 아니라, 이 시점부터 개발자가 결과에 집중할 수 있게 됐다는 것이다. 파이프라인이 완성되기 전까지는 “내가 쓴 코드가 맞는지”보다 “파이프라인이 왜 안 되는지”가 더 큰 문제였다. 17/17 이후로는 코드를 push하면 자동으로 검증되고, 결과만 보면 된다. 개발자의 인지 부하가 근본적으로 줄어드는 전환점이다.

1.2 파이프라인의 구성

17개의 스테이지를 분류하면 다음과 같다:

lint(4개): 코드 스타일과 정적 분석. Go 코드와 프론트엔드 코드의 문법 오류, 타입 오류, 스타일 위반을 검사한다. 실행 전에 잡을 수 있는 버그들이다.

test(2개): 단위 테스트와 통합 테스트. 개별 함수와 모듈이 올바르게 동작하는지 검증한다.

quality(2개): SonarQube(코드 품질)와 Trivy(보안 취약점). 어제 고투 끝에 통과시킨 게이트들이다.

build(4개): Docker 이미지 빌드. game-server, ai-adapter, frontend, admin의 4개 서비스를 각각 컨테이너 이미지로 빌드하고 레지스트리에 push한다.

scan(4개): 빌드된 이미지에 대한 보안 스캔. 빌드 결과물을 다시 한 번 Trivy로 스캔한다.

update-gitops(1개): GitOps 저장소 업데이트. 새로 빌드된 이미지 태그를 ArgoCD가 바라보는 GitOps 저장소에 반영한다. 이것이 실제 Kubernetes 배포로 이어진다.

1.3 새로 추가된 scan 4개

어제 로그(8/13)와 오늘 로그(17/17)의 차이를 보면 scan 4개가 새로 생겼다. 어제는 lint 4 + test 2 + quality 2 + build 4 + update-gitops 1 = 13개였다. 오늘은 여기에 scan 4개가 추가돼 17개가 됐다.

이 scan 스테이지는 빌드된 Docker 이미지를 Trivy로 다시 스캔하는 것이다. 코드 레벨에서의 보안 스캔(quality 단계)과 이미지 레벨에서의 보안 스캔(scan 단계)은 서로 다른 취약점을 잡는다. 코드 스캔은 소스코드의 의존성 취약점을 잡지만, 이미지 스캔은 베이스 이미지(예: ubuntu:22.04, alpine:3.18)에 포함된 OS 레벨 패키지의 취약점도 잡는다. 같은 소스코드를 빌드하더라도 베이스 이미지가 오래됐다면 취약점이 있을 수 있다. 이 단계가 그것을 잡는다.

DevSecOps 파이프라인이 한 단계 성숙해진 것이다.


2장. DinD에서 Kaniko로 — 마지막 벽을 넘는 방법

2.1 어제의 벽: Docker DinD

어제 로그에서 “Docker DinD라는 마지막 벽”이 등장했다. 오늘 로그의 첫 번째 핵심은 그 벽을 어떻게 넘었는지다. 답은 DinD를 Kaniko로 교체하는 것이었다. Pipeline #94에서 이 교체가 이루어졌다.

DinD(Docker-in-Docker)와 Kaniko는 같은 문제(Kubernetes Pod 안에서 Docker 이미지를 빌드하는 것)를 전혀 다른 방식으로 해결한다.

DinD는 컨테이너 안에서 Docker 데몬을 다시 실행한다. 외부 컨테이너 안에 내부 Docker 데몬이 있고, 그 데몬이 이미지를 빌드한다. 이것이 작동하려면 컨테이너가 privileged mode로 실행되어야 한다. privileged mode는 컨테이너가 호스트 시스템의 커널 권한을 거의 그대로 갖게 된다는 의미다. 보안 관점에서 허용하기 어려운 설정이다.

Kaniko는 Docker 데몬 없이 Dockerfile을 실행한다. Kaniko는 Dockerfile의 각 명령(RUN, COPY, ADD 등)을 유저스페이스(userspace)에서 직접 실행하고, 파일시스템 스냅샷을 찍어서 이미지 레이어를 구성한다. Docker 데몬이 필요 없기 때문에 privileged mode도 필요 없다. Google이 Kubernetes 환경에서의 안전한 컨테이너 빌드를 위해 개발한 오픈소스 도구다.

2.2 Kaniko의 구체적 작동 방식

Kaniko의 작동 과정을 단계별로 살펴보면 다음과 같다.

Kaniko 컨테이너가 실행되면 소스코드(빌드 컨텍스트)를 가져온다. Git 저장소에서 clone하거나, Kubernetes PersistentVolume에서 마운트하거나, GCS/S3 같은 오브젝트 스토리지에서 다운로드한다. Dockerfile의 각 명령을 순서대로 실행한다. FROM ubuntu:22.04는 해당 베이스 이미지를 레지스트리에서 pull해서 Kaniko 컨테이너의 내부 파일시스템에 풀어놓는 것이다. RUN apt-get install -y curl 같은 명령은 해당 파일시스템 위에서 직접 실행된다. 각 명령 실행 후 파일시스템 변경사항을 스냅샷으로 찍어 레이어로 만든다. 모든 레이어가 완성되면 OCI 이미지 포맷으로 조립해 레지스트리에 push한다.

이 모든 과정에서 Docker 데몬이 개입하지 않는다. 레지스트리에 대한 인증은 Kubernetes Secret으로 주입받는다.

2.3 Kaniko의 장점과 한계

장점은 분명하다. privileged mode 불필요, 보안 위험 감소, Kubernetes RBAC과의 자연스러운 통합, 레이어 캐시를 원격 레지스트리에 저장할 수 있어 반복 빌드 시간 단축.

한계도 있다. DinD에 비해 빌드 속도가 느릴 수 있다. Kaniko는 파일시스템을 직접 조작하기 때문에 일부 복잡한 RUN 명령에서 예상치 못한 동작이 발생할 수 있다. 그리고 이미지 빌드가 주 목적이기 때문에, DinD처럼 컨테이너 실행이나 런타임 조작이 필요한 작업에는 쓸 수 없다.

RummiArena처럼 단순히 서비스 이미지를 빌드하고 레지스트리에 push하는 용도에서는 Kaniko가 DinD보다 훨씬 적합한 선택이다.

2.4 Pipeline #95: 타임아웃과 #96: 직렬화

Kaniko로 전환한 #94가 성공했지만, 또 다른 이유로 실패했다. #95에서는 빌드 타임아웃이 문제였다. Kaniko가 처음 실행될 때는 캐시가 없어서 베이스 이미지를 처음부터 다운로드해야 한다. 이것이 기본 타임아웃(아마 10~15분)을 초과했다. 타임아웃을 30분으로 늘렸다.

#95도 실패했다. 이번에는 다른 이유였다. frontend와 admin 서비스가 동시에 빌드되면서 레지스트리에 레이어 캐시를 동시에 push했다. 두 빌드가 동시에 같은 레지스트리 엔드포인트에 접근하면서 대역폭을 나눠 먹었고, 결국 둘 다 느려지거나 실패했다.

해결책은 직렬화(serialization)였다. game-server와 ai-adapter 빌드가 끝난 다음에야 frontend와 admin 빌드가 시작되도록 순서를 정했다. GitLab CI의 needs 키워드를 사용했다:

1
2
3
build-frontend:
  needs: ["build-game-server"]  # 이 한 줄
  ...

이 한 줄을 쓰기 위해 이틀이 걸렸다는 원문의 표현은 과장이 아니다. 문제의 근본 원인(동시 빌드의 레지스트리 대역폭 경합)을 찾는 데 두 번의 타임아웃, 즉 두 번의 긴 기다림과 로그 분석이 필요했기 때문이다.


3장. 362개의 E2E 테스트 — 방 격리와 관점의 전환

3.1 E2E 테스트란 무엇이고 왜 어려운가

E2E(End-to-End) 테스트는 실제 사용자가 경험하는 전체 흐름을 자동화된 방식으로 검증하는 테스트다. 단위 테스트가 함수 하나를 격리해서 테스트하는 것이라면, E2E 테스트는 “사용자가 로그인하고, 방을 만들고, 상대방과 게임을 시작하고, 루미큐브 타일을 놓고, 게임이 끝나는” 전체 시나리오를 실제 브라우저와 서버를 돌려서 테스트한다.

E2E 테스트의 가장 큰 도전은 테스트 격리(test isolation) 다. 각 테스트는 독립적으로 실행되어야 하고, 이전 테스트의 상태가 다음 테스트에 영향을 주면 안 된다. 그런데 멀티플레이어 게임 서버는 그 본질적 특성상 상태를 공유한다. 방(room)이 서버에 생성되면 그것은 서버의 상태로 남는다. 하나의 테스트가 만든 방이 다음 테스트에 영향을 주는 것이 바로 “테스트 오염(test contamination)”이다.

3.2 409 ALREADY_IN_ROOM: 오염의 정체

이날 34개의 테스트가 빨간색이었다. game-lifecycle 20개, game-ui-state 9개, game-ui-multiplayer 4개, practice 1개. 공통 에러는 409 ALREADY_IN_ROOM이었다.

HTTP 상태 코드 409는 “Conflict(충돌)”를 의미한다. 클라이언트가 현재 이미 방에 있는 상태인데 새로운 방에 들어가려 하자, 서버가 “이미 방에 참가 중입니다”라는 충돌 응답을 돌려준 것이다.

이것이 발생한 구조를 재현하면 다음과 같다:

1
2
3
4
5
6
7
8
9
10
11
테스트 A 실행:
  → 테스트 A가 방 생성 (방 ID: room-001)
  → 테스트 A가 게임 진행
  → 테스트 A가 게임 종료
  → [방 cleanup이 완료되지 않은 채 테스트 B 시작]

테스트 B 실행:
  → 테스트 B가 방 생성 시도
  → 서버: "플레이어 X가 아직 room-001에 있습니다"
  → 서버: "409 ALREADY_IN_ROOM" 반환
  → 테스트 B 실패

테스트 A가 끝날 때 방 정리가 제대로 되지 않았거나, 정리되기 전에 테스트 B가 시작됐거나, 비동기 종료 과정에서 경쟁 조건(race condition)이 발생한 것이다.

3.3 3단계 Cleanup 전략

해결책은 두 가지 방향에서 왔다. 방 생성 재시도 로직과 방 정리의 3단계 폴백(fallback)이다.

방 생성 재시도: 방 생성이 409로 실패하면, 먼저 기존 방에서 나가는 cleanup을 시도하고, 그 다음 방 생성을 다시 시도한다. 그냥 실패로 처리하는 것이 아니라, 실패를 감지하고 상태를 복구한 다음 재시도한다.

3단계 방 나가기 cleanup:

1단계: 프론트엔드 프록시를 통해 방에서 나가기 요청을 보낸다. 일반적인 클라이언트가 사용하는 정상 경로다.

2단계: 1단계가 실패하면, 게임 서버에 직접 HTTP 요청을 보내서 플레이어를 방에서 나가게 한다. 프록시를 우회하는 방법이다.

3단계: 2단계도 실패하면, 호스트 권한으로 방 자체를 강제 삭제한다. 가장 강력하고 가장 급진적인 방법이다. 방에 있는 플레이어들을 모두 내보내고 방을 없앤다.

이 3단계 폴백 구조가 33개의 빨간 점을 초록으로 바꿨다. 어떤 방식으로든 정리가 되면 다음 테스트가 깨끗한 상태에서 시작할 수 있게 된다.

3.4 마지막 2개: 관점의 전환

33개가 해결되고 나서 마지막으로 남은 2개는 ai-battle 테스트였다. 이 테스트는 “AI 사고 중…” 텍스트가 화면에 나타나는 것을 기다리는 방식으로 작성되어 있었다.

120초를 기다려도 나타나지 않았다. 타임아웃을 더 늘려도 안 됐다. 문제는 텍스트가 렌더링되지 않고 있다는 사실이었고, 그 원인은 서버가 AI_THINKING WebSocket 메시지를 클라이언트에 보내지 않고 있었기 때문이었다.

WebSocket은 HTTP와 달리 서버가 먼저 클라이언트에 메시지를 push할 수 있는 양방향 통신 프로토콜이다. 멀티플레이어 게임에서 “상대방이 AI이고, AI가 지금 생각하고 있다”는 정보를 클라이언트에 알려주려면 서버가 AI_THINKING 이벤트를 WebSocket으로 전송해야 한다. 그런데 이 메시지가 전송되지 않고 있었으니, 프론트엔드는 AI가 생각 중인지 알 수 없었고, “AI 사고 중…” 텍스트도 렌더링되지 않았다.

여기서 개발자가 선택한 해결책이 흥미롭다. 서버를 고쳐서 AI_THINKING 메시지를 보내게 만드는 것도 옵션이었지만, 그 대신 테스트의 관점을 바꿨다. “AI 사고 중” 텍스트를 기다리는 대신, ActionBar가 사라지는 것을 확인한다. ActionBar는 내 차례일 때만 표시되는 UI 요소다. AI 차례가 되면 내 ActionBar가 사라진다. 따라서 ActionBar가 없다는 것은 내 턴이 아니라는 것, 즉 AI가 생각하고 있다는 것과 같은 의미다.

1
2
3
4
5
6
기존 접근: "AI_THINKING 메시지가 왔을 때 렌더링되는 텍스트를 기다린다"
           → 서버가 메시지를 안 보내면 영원히 기다린다

새 접근: "ActionBar가 없는지 확인한다"
         → ActionBar는 항상 동기적으로 렌더링된다
         → AI 차례라는 사실을 다른 경로로 검증한다

이것은 테스트 공학의 핵심 원칙 중 하나인 “테스트 가능한 것을 테스트하라(Test what is testable)”와 “동일한 의도를 더 안정적인 방식으로 검증하라”의 실천이다. 원하는 상태를 직접 관찰할 수 없다면, 그 상태의 다른 결과물을 관찰하면 된다.

3.5 E2E 362개의 구성

최종적으로 통과한 362개의 E2E 테스트는 단순한 숫자가 아니다. 이 숫자는 게임의 “핵심 경로(critical path)”를 얼마나 넓게 커버하는지를 나타낸다.

원문에서 언급된 conservation 테스트 24개가 특히 주목할 만하다. 타일 총수가 변하지 않는다는 불변 조건(invariant)을 검증하는 24개의 테스트가 독립적으로 존재한다는 것은, 게임 데이터 무결성에 대한 명시적인 품질 기준이 수립됐다는 의미다. 어떤 코드를 추가하더라도 타일이 새로 생겨나거나 사라지면 24개의 테스트 중 하나가 즉시 빨간불을 켠다.

이것이 362개가 만드는 “안전망”이다.


4장. 12.5%의 의미 — AI 모델의 게임 실력 측정

4.1 place rate란 무엇인가

오늘 로그에서 가장 흥미로운 부분은 AI 모델 성능 데이터다. “DeepSeek Reasoner의 place rate가 5%에서 12.5%로 올랐다”는 문장. place rate를 이해하려면 루미큐브의 AI 플레이 구조를 먼저 이해해야 한다.

루미큐브에서 AI 플레이어가 해야 할 핵심 행동은 두 가지다. 자신의 손패(rack)에서 타일을 테이블에 올려놓는 것(place)과, 올려놓을 것이 없을 때 더미에서 새 타일을 가져오는 것(draw). place rate는 AI가 턴을 가질 때마다 “타일을 올려놓는” 행동을 선택하는 비율이다.

place rate가 낮다는 것은 AI가 매 턴마다 “올려놓을 것이 없다”고 판단해 타일을 계속 가져오기만 한다는 의미다. 이것은 두 가지를 의미할 수 있다. 실제로 올려놓을 수 있는 조합이 없는 경우, 또는 올려놓을 수 있는 조합이 있지만 AI가 그것을 인식하지 못하는 경우. 후자가 문제다.

place rate는 AI가 루미큐브의 규칙(3개 이상의 연속 같은 색 타일, 또는 3개 이상의 같은 숫자 다른 색 타일)을 얼마나 잘 이해하고 적용하는지를 간접적으로 측정하는 지표다.

4.2 DeepSeek Reasoner: C등급 판정의 근거

DeepSeek Reasoner(DeepSeek R1의 추론 모드)의 성능이 5%에서 12.5%로 개선됐지만, 여전히 C등급으로 판정됐다. 비교 기준은 GPT(아마 GPT-4.1 계열)의 28%다.

더 구체적인 근거는 무효율(invalid placement rate)이다. DeepSeek이 11번 타일을 놓으려 시도했고, 그 중 4번이 서버에서 INVALID_MOVE로 거부당했다. 거부율 36%(4/11). 즉, DeepSeek이 “이건 올려놓을 수 있겠다”고 판단한 배치의 3분의 1 이상이 실제로는 규칙에 맞지 않는 것이었다. 반면 GPT는 “대부분 유효한 배치를 제출한다”고 원문이 밝힌다.

이 차이가 “게임 규칙 이해도”의 차이다. DeepSeek Reasoner는 수학, 코딩, 논리 추론 벤치마크에서 OpenAI o1 수준에 필적하는 성능을 보이지만, 루미큐브라는 구체적인 게임의 규칙을 실시간으로 적용하는 능력에서는 GPT보다 뒤처진다. 이것이 LxM(Ludus Ex Machina) 프로젝트의 핵심 통찰 중 하나다. 범용 벤치마크 성능과 특정 도메인 게임 플레이 능력은 일치하지 않는다.

4.3 DeepSeek Reasoner에 대한 배경 이해

DeepSeek R1 시리즈는 중국 스타트업 DeepSeek이 개발한 오픈소스 추론 모델이다. 2025년 1월에 처음 공개됐을 때, MIT 라이선스로 공개되면서 OpenAI o1에 필적하는 수학·코딩·추론 성능을 보여 글로벌 AI 커뮤니티에 큰 충격을 줬다.

DeepSeek R1의 아키텍처는 Mixture of Experts(MoE) 방식으로, 671B 파라미터 중 추론 시 약 37B만 활성화된다. 이 구조 덕분에 비용이 낮다. 원문에서 언급된 “턴당 $0.001”은 이 효율적인 아키텍처에서 비롯된다. 반면 GPT-4 계열은 Dense 모델로 추론 시 모든 파라미터가 활성화되기 때문에 비용이 높다.

DeepSeek R1은 순수 강화학습(RL)만으로 추론 능력을 훈련한 최초의 오픈소스 모델이라는 점에서 특별한 위치를 갖는다. 사람이 레이블링한 사고 과정 데이터 없이, 오직 “최종 답이 맞는가”라는 보상 신호만으로 스스로 추론 패턴을 발전시켰다.

그러나 이 모델이 루미큐브라는 시각적·공간적·규칙 기반 게임에서 GPT에 뒤처지는 것은, 범용 추론 능력과 특정 게임 도메인의 규칙 적용 능력이 서로 다른 축에서 작동함을 보여준다.

4.4 비용 효율 분석: 숫자로 읽는 AI 경제학

원문의 비용 비교는 단순하면서도 강력하다:

모델턴당 비용place rate비용 대비 효율
DeepSeek Reasoner$0.00112.5%기준 1
GPT$0.02528%DeepSeek의 6.6배 비용
Claude(언급 안 됨)(언급 안 됨)DeepSeek의 23.7배 비용

place rate만 보면 GPT > DeepSeek이지만, 비용 대비 효율로 보면 DeepSeek이 GPT보다 6.6배 효율적이다. 12.5%의 place rate를 $0.001에 달성하는 것과, 28%의 place rate를 $0.025에 달성하는 것을 비교하면:

  • GPT: $1당 28,000%·turn (28%/turn × 1000turns/$)
  • DeepSeek: $1당 12,500%·turn (12.5%/turn × 1000turns/$)

단순 계산으로는 GPT가 더 효율적이지만, 개발자가 말하는 “비용 대비 효율” 맥락은 조금 다르다. 같은 예산으로 DeepSeek은 25배 더 많은 게임을 진행할 수 있다. 데이터 수집과 실험 목적에서는 더 많은 게임 수가 통계적 신뢰도를 높인다. 또한 프롬프트를 개선하면 비용은 그대로이면서 place rate를 올릴 수 있다. 개선 여지가 크다는 것은 투자 수익률 측면에서 유리하다.

4.5 few-shot과 중국어 프롬프트: 다음 실험의 방향

개발자가 언급한 두 가지 개선 방향이 흥미롭다.

few-shot 예시 추가: few-shot은 프롬프트에 “이렇게 하면 됩니다”라는 예시를 몇 개 포함하는 것이다. 예를 들어, “패에 [2빨, 3빨, 4빨]이 있으면 이렇게 배치하세요”라는 예시를 주면 모델이 유효한 배치 패턴을 더 잘 학습할 수 있다. 단, DeepSeek R1 계열 모델은 few-shot 프롬프트가 오히려 성능을 낮출 수 있다는 연구 결과도 있기 때문에 실험적 접근이 필요하다.

중국어 프롬프트: DeepSeek은 중국어 학습 데이터 비율이 높기 때문에, 중국어로 지시하면 더 정확하게 이해할 가능성이 있다. 동일한 게임 규칙을 영어와 중국어로 각각 설명하고 성능 차이를 측정하는 것이 A/B 실험의 내용이 될 것이다. 이것은 LxM 프로젝트가 단순한 모델 성능 비교를 넘어, “프롬프트 언어가 게임 플레이에 미치는 영향”이라는 흥미로운 연구 변수를 다루게 될 수 있음을 시사한다.


5장. 품질 게이트의 완성 — “코드가 돌아간다”에서 “코드가 올바르다”로

5.1 Sprint 역사 한 눈에 보기

원문은 간결하게 스프린트 역사를 정리한다:

Sprint 1: Game Engine 개발 — 루미큐브의 핵심 게임 로직을 구현했다. 타일 배치 규칙 검증, 랙과 테이블 상태 관리, 턴 전환 등.

Sprint 2: AI 붙이기 — OpenAI, Claude, DeepSeek 등의 AI 모델을 게임 플레이어로 통합했다. 각 모델에게 게임 상태를 설명하고 액션을 요청하는 API 인터페이스를 구현했다.

Sprint 3: OAuth와 WebSocket 안정화 — 실제 사용자 인증(OAuth)과 실시간 통신(WebSocket)을 안정적으로 만들었다.

Sprint 4: 버그 잡기 — 어제(4월 2일) 로그의 내용이다. 스크린샷 21장, 결함 24건, 파이프라인 10번.

Sprint 5 Day 3(오늘): 품질 게이트 완성 — CI/CD 17/17 + E2E 362/362.

이 궤적을 보면 RummiArena가 단순한 취미 프로젝트가 아님을 알 수 있다. 각 스프린트가 명확한 목표를 갖고, 달성 여부를 측정 가능한 지표로 확인한다. 이것은 프로덕션 소프트웨어를 만드는 방식이다.

5.2 “코드가 돌아간다”와 “코드가 올바르다”의 차이

원문이 명명한 이 전환점은 소프트웨어 품질 공학의 핵심 구분이다.

“코드가 돌아간다”는 것은 기능이 작동한다는 것이다. 게임을 시작할 수 있고, 타일을 놓을 수 있고, 게임이 끝난다. 하지만 어떤 조건에서, 얼마나 안정적으로, 보안 취약점 없이 돌아가는지는 보장하지 않는다.

“코드가 올바르다”는 것은 그 모든 조건에서도 올바르게 동작한다는 것이다. 362개의 E2E 테스트가 커버하는 핵심 경로에서는 의도한 대로 동작한다. 24개의 conservation 테스트가 보장하는 타일 무결성 조건이 항상 성립한다. Quality Gate가 보장하는 코드 품질과 보안 기준을 통과했다.

CI/CD 파이프라인과 E2E 테스트가 함께 완성되는 것이 이 전환을 가능하게 한다. 이 두 가지가 없으면 “올바르다”는 주장은 개발자의 직감에 의존한다. 두 가지가 있으면 “올바르다”는 주장은 숫자로 표현된다.


6장. 에이전트 25회 — 역대 최다와 “좌절하지 않는다”

6.1 25회 투입의 분류와 패턴

이날의 에이전트 투입 현황은 다음과 같다:

DevOps(7회): 가장 많은 비중. 파이프라인 문제(Kaniko 전환, 타임아웃, 직렬화)를 해결하는 작업이 주를 이뤘다.

PM(5회): 프로젝트 관리 관점. 스프린트 진행 상황 정리, P0 블로커 목록 업데이트, 내일의 작업 계획 수립 등.

QA(4회): E2E 테스트 실패 분석과 수정 방안 도출.

AI Engineer(4회): DeepSeek place rate 분석, 프롬프트 개선 방향 제안.

Frontend Dev(3회): ActionBar 기반 테스트로의 전환 등 UI 관련 작업.

Go Dev(2회): 서버 사이드 코드 검토.

이 분포가 하루 동안의 작업 초점을 그대로 반영한다. DevOps 문제(파이프라인)가 가장 크고, PM 작업(정리와 계획)이 그 다음이다. AI Engineer와 QA가 비슷한 비중으로 뒤를 잇는다.

6.2 “클로드 오늘 진짜 멍청이가 된 것 같음”

이날의 시작이 인상적이다. 어제의 탄식이 코드에 대한 것(“같이 일 못하겠다”)이었다면, 오늘의 탄식은 에이전트에 대한 것이다. 어제 3개의 에이전트가 내부 에러로 실패했다.

이 표현은 AI 도구를 실제로 쓰는 사람이 아니면 쉽게 나오지 않는 말이다. 에이전트가 실패할 때의 답답함, 그것이 일시적 오류인지 구조적 한계인지 모를 때의 불확실함. “오늘 진짜 멍청이가 된 것 같음”이라는 표현은 그 답답함의 솔직한 표현이다.

그리고 원문은 이것을 담담하게 메타화한다. “에이전트가 실패하면 다시 투입한다. 다시 투입하면 대부분 성공한다. 실패의 원인은 대개 일시적이다. 네트워크, 타임아웃, 메모리.”

가장 인상적인 문장이 여기 있다. “인간이 ‘같이 일 못하겠다’고 느끼는 순간에도 기계는 감정이 없으니 그냥 다시 시도한다. 그리고 성공한다. 어쩌면 그게 에이전트의 가장 큰 장점일지도 모른다. 좌절하지 않는다는 것.”

이것은 AI 에이전트의 특성에 대한 예리한 관찰이다. 에이전트는 지치지 않는다. 이전 실패에 대한 감정적 여운 없이 다음 시도를 시작한다. 이것이 1인 개발자가 에이전트를 25회 투입할 수 있는 심리적 기반이기도 하다. 에이전트가 실패해도 “다시 해달라고 해도 되나”라는 미안함이 없다.

6.3 역대 최다 25회의 경제학

25회는 어제의 20회보다 많다. 이날의 작업 강도가 더 컸다는 뜻이다. 그러면서도 “과한가”라는 의문이 이날은 등장하지 않는다. 어제 20회에서 그 의문을 갖고 “과하지 않다”고 결론 내렸기 때문이다.

이것은 학습의 증거다. 어제의 경험이 오늘의 행동 패턴을 바꿨다. 25회 투입을 정당화하는 내면의 계산이 이미 완료됐다. 에이전트를 더 많이 쓸수록 더 빨리 문제가 해결된다는 경험적 증거가 쌓인 것이다.


7장. 내일을 향해 — “방어”에서 “공격”으로

7.1 P0 블로커 해소의 의미

“P0 블로커가 전부 해소됐다”는 문장은 제품 개발 용어의 핵심을 담고 있다. P0(Priority 0)는 가장 높은 우선순위를 의미한다. 블로커는 다른 작업의 진행을 막는 선결 조건이다. P0 블로커가 해소됐다는 것은 “이것이 없으면 다음 단계로 갈 수 없다”는 조건들이 모두 충족됐다는 의미다.

CI/CD 파이프라인과 E2E 테스트가 그것이었다. 파이프라인 없이는 코드 변경이 안전하게 배포될 수 없다. E2E 테스트 없이는 새 기능이 기존 기능을 망가뜨렸는지 알 수 없다. 이 두 가지가 모두 완성된 지금, 다음 단계로 이동하는 것이 가능해졌다.

7.2 내일의 세 가지 작업: 공격적 국면

DeepSeek 프롬프트 최적화: place rate 12.5%에서 목표인 15%로, 가능하면 GPT의 28%에 근접하게. few-shot 예시 추가, 중국어 프롬프트 실험, 게임 상태 표현 방식 개선 등이 실험 대상이다.

3모델 전체 대전: GPT, Claude, DeepSeek이 동시에 같은 루미큐브 게임에서 대결하는 실험. 1대1 대전이 아닌 3자 대전은 각 모델의 전략적 행동 차이를 더 풍부하게 관찰할 수 있는 환경을 만든다.

Rate Limit 구현: 어제 Security 에이전트가 지적한 미비점. 악의적 클라이언트가 서버에 과도한 요청을 보내는 것을 방지하는 기능이다. 이것이 구현되면 프로덕션 배포에 한 걸음 더 가까워진다.

7.3 “초록불이 켜지면 진행. 빨간불이 켜지면 수정”

원문의 마지막 문장들이 짧고 강렬하다. “새 코드를 쓰고, push하고, 파이프라인을 지켜보면 된다. 초록불이 켜지면 진행. 빨간불이 켜지면 수정. 단순하지만 강력한 리듬.”

이것은 완성된 CI/CD 파이프라인과 E2E 테스트가 만들어주는 개발 리듬이다. 개발자는 더 이상 “이 코드가 다른 것을 망가뜨렸을까?”라는 불안을 안고 push할 필요가 없다. 파이프라인이 자동으로 확인해주고, 결과를 초록/빨간으로 알려준다. 이 리듬을 만드는 데 Sprint 5일이 걸렸다는 것이 이 구조의 가치를 반증한다.


8장. 기술 용어 심화 해설

8.1 GitLab CI needs 키워드와 DAG 파이프라인

GitLab CI는 기본적으로 파이프라인을 스테이지(stage) 단위로 순차 실행한다. 같은 스테이지 안의 잡(job)들은 병렬로 실행된다. needs 키워드는 이 기본 동작을 바꿔서, 특정 잡이 다른 잡의 완료를 기다리게 한다.

needs: ["build-game-server"]build-frontend 잡에 추가하면, build-frontendbuild-game-server가 성공적으로 완료된 이후에만 시작된다. 같은 스테이지라도 순서가 생기는 것이다. 이렇게 needs로 연결된 잡들의 구조를 DAG(Directed Acyclic Graph, 방향성 비순환 그래프)라고 한다. 잡들 사이의 의존 관계가 방향이 있고(A가 끝나야 B가 시작) 순환이 없는(A → B → A 같은 순환 없음) 그래프를 이룬다.

이 구조 덕분에 레지스트리 대역폭 경합 문제가 해결됐다. game-server와 ai-adapter가 레지스트리 push를 마친 뒤에야 frontend와 admin이 push를 시작하니, 동시 push 충돌이 사라진다.

8.2 WebSocket과 AI_THINKING 이벤트

WebSocket은 HTTP와 달리 한 번 연결이 맺어지면 양방향으로 지속적으로 데이터를 주고받을 수 있는 프로토콜이다. 멀티플레이어 게임에서 실시간 상태 동기화에 필수적이다.

RummiArena에서 AI_THINKING 이벤트는 서버가 클라이언트에게 “지금 AI가 턴을 처리하고 있다”를 알려주는 WebSocket 메시지다. 클라이언트는 이 메시지를 받으면 “AI 사고 중…” UI를 표시하고, 내 액션을 비활성화한다.

이 메시지가 전송되지 않는 문제는 두 가지 접근으로 해결할 수 있다. 첫 번째는 서버를 수정해서 메시지를 보내게 하는 것. 두 번째는 테스트를 수정해서 그 메시지에 의존하지 않게 하는 것. 개발자는 두 번째를 선택했는데, 이 선택의 근거는 테스트 안정성이다. WebSocket 메시지 전송 타이밍은 서버 부하에 따라 달라질 수 있어 테스트가 불안정(flaky)해지기 쉽다. 반면 ActionBar의 존재 여부는 UI 상태로 동기적으로 결정되어 더 안정적인 테스트를 만들 수 있다.

8.3 DeepSeek의 비용 구조와 MoE 아키텍처

원문에서 “DeepSeek은 턴당 $0.001, GPT는 $0.025, 25배 차이”라는 수치가 나온다. 이 비용 차이의 근원은 모델 아키텍처에 있다.

GPT-4 계열은 Dense(조밀) 아키텍처다. 모든 파라미터가 모든 추론에 참여한다. 파라미터가 많을수록 추론 비용이 선형적으로 증가한다.

DeepSeek R1은 MoE(Mixture of Experts, 전문가 혼합) 아키텍처다. 총 671B 파라미터가 있지만, 추론 시에는 약 37B만 활성화된다. 입력 토큰마다 가장 적합한 “전문가 네트워크” 서브셋이 선택돼 실행된다. 이 덕분에 대형 모델의 표현력을 유지하면서 추론 비용을 작은 모델 수준으로 유지할 수 있다.

DeepSeek R1 API의 가격은 입력 1M 토큰당 $0.55, 출력 1M 토큰당 $2.19이다. 이것은 비교 가능한 GPT-4 계열 모델보다 5~10배 저렴하다. 루미큐브 한 턴에 소모되는 토큰이 수백~수천 토큰 수준이라면, 턴당 $0.001은 설득력 있는 수치다.

8.4 E2E 테스트의 테스트 피라미드에서의 위치

소프트웨어 테스트는 흔히 피라미드 구조로 표현된다. 맨 아래에 단위 테스트(unit test)가 많은 수로 존재하고, 중간에 통합 테스트(integration test)가 있으며, 맨 위에 E2E 테스트가 적은 수로 존재한다. 아래로 갈수록 빠르고 저렴하지만 범위가 좁고, 위로 갈수록 느리고 비싸지만 범위가 넓다.

RummiArena의 362개 E2E 테스트는 이 피라미드의 꼭대기에 있다. 단위 테스트가 수천 개, 통합 테스트가 수백 개 있을 테고, E2E 테스트 362개는 그 위에 얹힌다. E2E 테스트가 실제 브라우저, 실제 서버, 실제 WebSocket, 실제 데이터베이스를 모두 동원해 실행되기 때문에 하나의 테스트 실행에 수십 초가 걸릴 수 있다. 362개를 모두 돌리면 몇 시간이 걸릴 수도 있다. 이것이 CI 파이프라인에 포함돼 매 push마다 자동으로 실행된다는 것은, RummiArena의 품질에 대한 진지한 투자다.


9장. 이틀이라는 연속성

9.1 어제와 오늘: 하나의 서사

4월 2일과 4월 3일 바이브 로그를 연달아 읽으면, 이것이 하나의 연속된 서사임을 알 수 있다. 어제의 탄식이 오늘의 성취가 됐다. 어제 결론이 나지 않은 Docker DinD 문제가 오늘 Kaniko로 해결됐다. 어제 발견한 E2E 실패들이 오늘 3단계 cleanup으로 해결됐다.

이 연속성이 중요한 이유는, 개발이 선형적이지 않음을 보여주기 때문이다. 어제의 실패가 오늘의 해결책을 위한 정보였다. 어제 10번 파이프라인을 돌린 것이 오늘 3번 만에 마무리하는 배경이 됐다. 실패는 손실이 아니라 투자였다.

9.2 1인 개발자의 속도에 대하여

Sprint 1부터 Sprint 5까지 이 개발 속도는 전통적인 1인 개발자로서는 이례적으로 빠르다. Game Engine 개발, AI 통합, OAuth/WebSocket 안정화, 버그 픽스, CI/CD 완성, E2E 테스트 362개. 일반적으로 소규모 팀이 몇 달에 걸쳐 할 작업이다.

그 속도의 원천이 Agent Teams다. 6인, 11인, 혹은 오늘의 25회 투입. 각 에이전트가 독립적으로 분석하고 코드를 수정하고 리뷰하는 병렬 작업이 개발 속도를 압축한다. 그리고 이 속도는 단순히 “AI가 코드를 써줘서”가 아니다. “AI가 나 대신 생각해줘서”다. 어떤 에이전트에게 “이 테스트 실패 원인을 분석해줘”라고 요청하면, 개발자는 그 답을 기다리는 동안 다른 일을 할 수 있다. 병렬성이 시간을 압축한다.

9.3 362개의 초록 점이 만드는 기반

로그는 이 문장으로 끝난다. “362개의 초록 점이 내일의 기반이 된다.”

어제 8칸을 오르고 오늘 9칸을 올라 총 17칸(파이프라인 17개). 그리고 362개의 안전망이 깔렸다. 이 기반 위에서 내일부터는 공격적인 실험이 가능하다. DeepSeek 프롬프트를 바꿔도, 3모델 대전을 구현해도, Rate Limit을 추가해도, 362개의 테스트가 “기존 기능이 망가졌는지”를 자동으로 감시한다.

이것이 테스트 주도 개발(TDD)의 정신이기도 하다. 테스트를 먼저 쓰는 것이 아니라, 테스트가 쌓인 다음에야 자신 있게 코드를 바꿀 수 있다는 것. 362개의 초록 점이 그 자신감의 물리적 표현이다.


맺음말: 공격으로의 전환

이틀의 로그를 통해 RummiArena 프로젝트는 “방어적 국면”에서 “공격적 국면”으로 넘어갔다. 어제는 버그를 잡고 파이프라인을 고치는, 과거의 문제를 해결하는 방어적 작업이었다. 오늘은 그 방어선을 완성하고 앞으로의 실험을 위한 기반을 다지는 전환점이었다.

17개의 초록불과 362개의 초록 점. 이 숫자들은 단순히 기술적 완성도를 나타내는 것이 아니다. 개발자가 다음 실험을 할 때 “이 변경이 다른 것을 망가뜨릴까?”라는 두려움 없이 전진할 수 있다는 심리적 안전망이다.

내일 DeepSeek 프롬프트를 바꾸면 place rate가 몇 퍼센트 올라갈까. GPT와 Claude와 DeepSeek이 3자 대결을 벌이면 누가 이길까. Rate Limit을 구현하면 보안 점수가 어떻게 바뀔까. 이 모든 질문이 이제 실험 가능해졌다.

그것이 오늘 이틀의 작업이 만들어낸 것이다.


이 해설 문서는 바이브 로그(2026-04-03) 원문을 기반으로, Kaniko vs DinD 기술 현황, DeepSeek Reasoner 모델 특성 및 비용 구조, E2E 테스트 격리 전략 등 최신 기술 정보를 포함해 작성되었습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.