배포와 인프라18 분 읽기

웹앱 게임 6개를 스크래치부터 배포까지 올렸습니다. 어느 페이지도 그 게임들을 링크하고 있지 않았습니다

테스트 통과, 빌드 통과, 커스텀 도메인 TLS 200, 계측 이벤트 적재까지 전부 확인했습니다. 다음 날 외부 방문자를 세어보니 0명이었습니다. grep 두 번으로 원인이 나왔습니다. 게임 품질이 아니었습니다.

#gotchas#reality-check#deployment#first-principles
왼쪽은 배포된 것 - 게임 6개, 초록 테스트, TLS 200, 계측 이벤트 발화. 오른쪽은 그 게임들을 링크한 것 - 허브 카드 0, 매니페스트 0, 숏폼 파이프라인 0, 외부 방문자 0
왼쪽 체크리스트는 전부 통과했습니다. 오른쪽은 아무도 확인하지 않은 목록입니다.

웹앱을 여러 개 굴리고 있습니다. 이틀 동안 브라우저 게임 6개를 스크래치부터 프로덕션까지 올렸습니다. 데일리 퍼즐 두 개, 아케이드 두 개, 리듬 게임 하나, 퀴즈 하나.

출시 체크리스트는 전부 초록이었습니다. 타입체크 통과, 유닛 테스트 통과, 빌드 통과, 커스텀 도메인 SSL 200, 모바일 390px에서 가로 스크롤 0, 콘솔 에러 0, 계측 이벤트 4종이 실제 DB에 적재되는 것까지 눈으로 확인했습니다.

다음 날 외부 방문자 수를 세어봤습니다. 0명이었습니다.

질문 하나 먼저 드리겠습니다. 당신이 마지막으로 배포한 것에, 지금 이 순간 링크를 걸어둔 페이지가 몇 개입니까? 저는 그 숫자를 세어본 적이 없었습니다.

스크래치에서 프로덕션까지, 실제로 걸린 단계

먼저 제가 실제로 뭘 했는지 적겠습니다. 게임 하나 기준입니다.

  1. 기존 게임 저장소를 템플릿으로 복사(설정, 계측, 이벤트 API, 로컬 저장소 헬퍼).
  2. 콘텐츠 생성기와 판정 로직 작성.
  3. 규칙을 강제하는 순수함수 + 테스트 작성.
  4. UI 구현. 모바일 세로 우선.
  5. 로컬 빌드 → 브라우저에서 실제로 한 판 끝까지 플레이.
  6. 클라우드 프로젝트 생성 → 환경변수 → 프로덕션 배포.
  7. 커스텀 도메인 연결 → DNS A 레코드 → SSL 발급 대기.
  8. 라이브 URL에서 이벤트 4종 실제 적재 확인.

여기까지가 제가 "출시"라고 부르던 것입니다. 8단계 전부 통과했고, 6개 게임 모두 통과했습니다.

실전 함정 셋

이 8단계에서 실제로 시간을 잡아먹은 것들입니다. 전부 재현 가능하고, 전부 "빌드 통과"로는 안 잡힙니다.

1. 도메인을 붙인 뒤 한 번 더 배포해야 합니다

플랫폼에 도메인을 추가하고 DNS를 넣었습니다. dig는 정상, 도메인 검증도 ok. 그런데 curl https://...가 계속 000을 뱉었습니다. 인증서 발급 명령은 Response Error만 반환했습니다.

원인은 단순했습니다. 도메인을 프로젝트에 추가해도 기존 배포에 alias가 자동으로 걸리지 않습니다. 도메인을 붙인 뒤 프로덕션 배포를 한 번 더 실행해야 Aliased가 찍히고, 그때부터 인증서 발급이 시작됩니다.

증상이 "SSL 문제"처럼 보여서 인증서 쪽을 계속 팠습니다. 실제로는 배포 alias 문제였습니다.

2. SVG에서 수직선과 수평선만 사라집니다

게임 하나는 순수 SVG로 그렸습니다. 점과 선으로 된 판이고, 지나간 선은 그라디언트와 글로우 필터로 강조됩니다.

플레이해 보니 강조된 선 일부가 화면에서 그냥 없었습니다. 대각선은 멀쩡했습니다. DOM을 열어보니 요소는 존재했고, class도 맞았고, 계산된 stroke도 정상이었습니다. 그런데 보이지 않았습니다.

원인은 SVG의 기본 좌표계입니다.

<linearGradient>  기본 gradientUnits = "objectBoundingBox"
<filter>          기본 filterUnits   = "objectBoundingBox"

축에 나란한 선은 bounding box의 폭이나 높이가 0입니다. 수직선은 폭 0, 수평선은 높이 0. 그러면 그라디언트도 필터 영역도 크기가 0이 되고, 그 획은 통째로 렌더링되지 않습니다. 대각선은 bounding box가 멀쩡해서 아무 문제가 없습니다.

고치는 방법은 둘 다 userSpaceOnUse로 바꾸고 viewBox 좌표로 영역을 직접 주는 것입니다.

<linearGradient gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="100" y2="124">
<filter filterUnits="userSpaceOnUse" x="-10" y="-10" width="120" height="144">

이게 왜 위험하냐면, 대각선만 보고 검수하면 절대 안 잡힙니다. 스크린샷을 대충 보면 "선이 좀 적네" 정도로 넘어갑니다. 격자 기반 판을 그린다면 QA 항목에 "강조 상태의 수직선과 수평선이 보이는가"를 따로 넣으세요.

3. 절차생성 퍼즐은 "풀리겠지"로 내면 안 됩니다

게임 하나는 한붓그리기입니다. 모든 선을 한 번씩만 지나 도형을 완성하는 그 퍼즐입니다.

문제를 랜덤으로 만들고 나중에 풀 수 있는지 확인하는 방식은 위험합니다. 사용자가 못 푸는 판을 받는 날이 반드시 옵니다.

그래서 순서를 뒤집었습니다. 해답을 먼저 그리고, 그 자취를 문제로 냅니다. 격자 위를 걸으면서 한 번 지난 선은 다시 밟지 않습니다. 그러면 그 걸음 자체가 정답 경로입니다. 만들어진 판은 "잘 만들어서 풀리는" 게 아니라 구조적으로 풀립니다.

그리고 그 주장을 코드가 다시 검증합니다. 테스트가 120일치 × 5스테이지 = 600판을 실제 솔버로 풀어봅니다. 배포 스크립트가 이 테스트를 먼저 돌리므로, 못 푸는 판은 프로덕션에 나갈 수 없습니다.

당신이라면 어떻게 하시겠습니까. 생성기를 믿고 샘플 몇 개만 손으로 풀어보시겠습니까, 아니면 전수 검증을 배포 게이트로 거시겠습니까? 저는 예전에 전자를 택했다가 설명한 방법론과 코드가 달랐던 일을 겪었습니다.

그리고 방문자는 0명이었습니다

8단계를 전부 통과한 게임 5개(6번째는 그다음 날 올렸습니다)를 놓고, 다음 날 이벤트 테이블을 조회했습니다.

총 이벤트 185건. 그런데 유입 출처를 세어보니 전부 qa-live, qa-smoke, qa-mobile 같은 제 QA 세션이거나 출처 없는 직접 진입이었습니다. 서로 다른 기기 토큰은 앱당 8~16개. 제 기기와 QA 브라우저 세션 수와 정확히 맞아떨어지는 숫자입니다.

외부 사람은 한 명도 없었습니다.

여기서 보통 하는 생각은 이렇습니다. "게임이 재미없나?" "SEO가 약한가?" "하루밖에 안 됐으니 더 기다려볼까?"

전부 틀렸습니다. grep을 두 번 돌렸더니 답이 나왔습니다.

$ grep -c 'class="card"' ~/ootssu-hub/index.html
34
$ grep -cE 'neko|meme|choseong|relicrun|starswarm' ~/ootssu-hub/index.html
0

포털 허브에 카드가 34장 있었고, 그중 게임은 0장이었습니다.

한 번 더 확인했습니다.

  • 앱 목록 매니페스트: 게임 항목 0개.
  • 숏폼 자동 게시 파이프라인: 소스가 모바일 앱 디렉터리 파일 하나였습니다. 웹앱 게임은 애초에 대상이 아니었습니다.

즉 게임 5개는 프로덕션에 살아 있었지만, 인터넷의 어느 페이지에서도 그리로 가는 링크가 없었습니다. 도메인을 아는 사람은 저뿐이었습니다. 유입 0은 미스터리가 아니라 산수였습니다.

배포는 마지막 단계가 아니었습니다

이 실수가 특히 나쁜 이유는, 모든 자동 검사를 통과한다는 점입니다.

빌드는 통과합니다. 테스트도 통과합니다. 도메인은 200을 반환합니다. 계측도 정상 적재됩니다. 배포 로그는 초록입니다. 실패한 명령이 하나도 없습니다. 그런데 제품은 도달 불가능합니다.

제가 "출시"라고 부르던 8단계는 사실 "빌드 완료"였습니다. 진짜 출시 체크리스트에는 이게 더 붙어야 했습니다.

  1. 허브/포털 인덱스에 카드 추가 (로케일별로).
  2. 앱 목록 매니페스트 재생성.
  3. 관리자 지표 대시보드에 등록 후 재배포.
  4. 유통 파이프라인(뉴스레터·소셜 자동화 등)의 소스 목록에 추가.
  5. 라이브 페이지에서 실제로 그 링크가 렌더되는지 확인.

13번을 따로 적은 이유가 있습니다. 저장소에 카드를 추가하고 배포한 뒤 라이브 페이지를 열었더니 여전히 예전 카드 수가 나왔습니다. CDN 엣지 캐시였습니다. 서버 파일을 직접 grep하면 새 카드가 있는데 맨 URL로 열면 없습니다. 캐시버스터 쿼리를 붙여야 새 페이지가 나옵니다. "배포했다"와 "사용자가 본다" 사이에도 한 칸이 더 있습니다.

로케일 하나 더

카드를 넣을 때 한 가지를 더 정했습니다. 한국어 UI 전용 게임은 한국어 허브에만 넣었습니다.

영어·일본어 허브에 한국어 게임 카드를 넣으면 카드 수는 늘어납니다. 하지만 클릭한 사람은 읽을 수 없는 화면을 만납니다. 예전에 일본어 콘텐츠가 없는 상태로 일본어 경로를 광고했다가 죽은 링크를 대량으로 만든 적이 있습니다. 카드 수는 허영 지표입니다. 도달 가능한 링크만 세는 게 맞습니다.

그래서 영어 UI인 게임 하나만 4개 로케일 전부에 넣었고, 나머지는 한국어 허브에만 넣었습니다.

문제의 게임 7개

이 글에서 게임 얘기만 하고 링크를 안 걸면 같은 실수를 반복하는 셈이라, 그냥 적어둡니다. 전부 무료, 설치 없음, 로그인 없음입니다.

앞의 네 개는 한국어 단어·상식이 필요합니다. 뒤의 세 개는 언어가 거의 필요 없습니다.

자가진단 3개

지금 당신 프로젝트에서 바로 확인할 수 있는 것들입니다.

  1. grep으로 확인하세요. 가장 최근에 배포한 제품 이름이나 도메인을, 당신의 홈페이지·포털·인덱스 파일에서 검색해 보세요. 0건이면 그 제품은 아직 출시된 게 아닙니다.
  2. 유통 파이프라인의 입력 파일을 열어보세요. 자동 게시 봇이나 뉴스레터가 목록을 읽는다면, 그 목록에 신제품이 실제로 들어 있습니까? 소스 파일 경로를 직접 확인하세요. "당연히 들어가겠지"가 제가 틀렸던 지점입니다.
  3. 분석 데이터에서 자기 자신을 빼세요. 저는 처음에 필터를 느슨하게 잡았다가 제 QA 세션 46개를 외부 사용자로 셀 뻔했습니다. 배포일 트래픽은 전량 자기 트래픽입니다. 베이스라인을 배포 다음 날로 옮기고, QA 출처가 한 번이라도 찍힌 기기 토큰은 통째로 제외하세요. 그러지 않으면 게이트가 스스로를 속입니다.

솔직한 부분

배선을 다 끝낸 지금도 외부 방문자는 여전히 0입니다. 링크를 걸었다고 사람이 오지는 않습니다. 허브 자체의 검색 유입이 미미하기 때문에, 이번 수정은 "유입을 만든 것"이 아니라 **"유입이 생길 수 있는 상태로 만든 것"**에 가깝습니다.

그래서 판정일을 박아뒀습니다. 2주 뒤에 게임 7종의 외부 일일 방문자를 다시 세고, 합계가 기준 미만이면 게임 신규 제작을 중단합니다. 리포트는 주 1회 자동으로 돌고, 수집이 실패하면 0으로 채우지 않고 수집실패라고 남깁니다. 초록불이 증거가 아니었던 일을 이미 겪었기 때문입니다.

이 글의 교훈은 "게임을 만들지 마라"가 아닙니다. 제품을 만드는 능력과 제품에 도달 경로를 만드는 능력은 다른 능력이고, 저는 후자를 하고 있다고 착각하고 있었다는 것입니다. 배포 로그가 초록이라 그 착각이 오래갔습니다.

당신 프로젝트에서 위의 grep 하나만 지금 돌려보세요. 0이 나오는 게 있다면, 그게 오늘 고칠 가장 값싼 것입니다. 결과가 어땠는지 알려주시면 좋겠습니다.

관련 글