죽어 있던 광고 코드가 빌드 하나를 날렸다
목차
오늘은 판이 세 개가 동시에 돌아갔다. 앱스토어 5.6 리젝션 뒤처리, 판매장려금 정산 시스템, 그리고 tb_system_master 데드락 수술. 어느 것도 가볍지 않았다. 아침부터 저녁까지 컨텍스트를 쉴 새 없이 전환하면서, 어느 시점에는 뭘 먼저 봐야 할지 잠깐 멍해지기도 했다.
5.6가 날린 빌드
가장 속이 쓰렸던 건 역시 앱스토어 쪽이었다.
5.6 리젝션을 받은 이유가 dormant ad layer 때문이었다. 실제로 동작하는 광고도 아니었다. 코드 안에 잠들어 있던, 진작에 비활성화해 두었다고 생각했던 광고 레이어. 근데 Apple 입장에서는 그게 실제 동작과 무관하게 "광고 SDK가 선언되어 있고, 코드 경로가 열려 있다"는 것만으로 충분한 리젝션 사유였다. 과거 1.0 제출 때도 같은 이유로 한 번 막혔다는 기록이 이미 있었는데, 빌드 체인 어딘가에서 다시 살아났다.
AndroidManifest.xml, Podfile, Podfile.lock, project.pbxproj, Info.plist까지 한 세트를 다 열어서 광고 관련 선언을 전부 뽑아냈다. CLAUDE.md에도 이번 삭제 맥락을 기록해뒀다. 나중에 누군가 다시 광고 붙이려고 SDK를 추가할 때 같은 실수를 반복하지 않도록.
문제는 그게 끝이 아니었다. 리젝션 자체보다, 기존 제출 트랙을 supersede 해야 하는 과정이 손이 많이 갔다. play_supersede_tracks.py 스크립트에 slecs 폴백 경로를 추가해 두었고, iOS는 Billing 403 이슈가 이미 이전에 해결된 상태였기 때문에 그 부분은 다행히 다시 막히지 않았다. 빌드 8을 cut 하면서 pubspec.yaml 버전을 올렸다. 8번 빌드는 "광고 코드가 아예 없는 첫 번째 빌드"라는 뜻이기도 하다.
재제출한 게 오늘 8/25 3차 제출이고, 지금은 WAITING_FOR_REVIEW 상태다. f30c2df1 커밋을 기준으로 버전이 잡혔다는 것까지 CLAUDE.md에 박아뒀다. 이제 같은 실수가 다시는 빌드에 끼어들지 못하도록 ios_preflight.sh에 프리플라이트 게이트도 추가했다. 이 쉘 스크립트가 dormant-ad 형태의 선언을 올리기 전에 걸러낸다. 늦게 생긴 안전망이지만, 없는 것보다야.
docs/plan-56-recovery.md와 docs/mobile-app-launch.md에도 오늘의 경과를 전부 기록해뒀다. 5.6가 왜 났는지, 어떻게 잡았는지, 다음번에 뭘 확인해야 하는지. 이런 문서 작업이 시간 아깝게 느껴질 때도 있는데, 오늘만큼은 "3차 제출까지 오는 과정"이 너무 길었기 때문에 꼼꼼하게 남겨두고 싶었다.
tb_system_master 핫로우 잠금 전면 수술
앱스토어 쪽을 어느 정도 정리하고 나서 바로 들어간 게 데드락 이슈였다.
tb_system_master 테이블에 핫로우 잠금이 걸리는 구조가 문제였다. 충전, 쿠폰, 대시보드, 시스템 설정 등 여러 모듈이 이 테이블의 같은 로우를 거의 동시에 읽고 쓰는 상황. 트래픽이 몰리면 잠금 충돌이 터지는 구조가 언제부터인가 쌓여 있었는데, 오늘 드디어 건드렸다.
해결 방향은 파생 캐시로의 전환이었다. 핫로우를 직접 반복해서 읽고 쓰는 대신, 5분 롤업 단위로 캐시를 만들어서 거기서 읽게 바꿨다. 건드린 클래스가 charge, coupon external, dashboard, dev, system web 다섯 곳이었다. 각 모듈이 독립적으로 그 테이블을 바라보고 있었기 때문에, 한 곳만 바꾼다고 되는 게 아니었다. api-reference.md에도 이 캐시 구조를 명세로 추가해뒀다.
"5분 캐시면 정확도가 떨어지지 않나?"라는 질문을 스스로 했고, 해당 데이터가 실시간성이 반드시 필요한 값이 아니라는 판단이 섰다. 대시보드 집계나 시스템 설정 수치 같은 게 5분 오차를 실운영에서 크게 문제 삼을 상황이 아니라는 것. 반면에 잠금 충돌은 실질적으로 에러를 발생시키고 있었다. 트레이드오프를 따졌을 때 지금은 캐시 전환이 맞다는 결론이었다.
판매장려금, 산식 확정까지의 과정
오늘 가장 코드 분량이 많이 나온 건 아마 판매장려금 정산 시스템 쪽이었을 것이다.
feat(settlement) 커밋 하나가 담은 내용이 상당했다. 영업대행사별 판매장려금 백정산 시스템 전체 - 일별 산출 로직, 지급 처리, 자동 배치까지. 거기에 settlement 도메인 규칙을 명세화한 .claude/rules/settlement-domain.md 파일도 새로 만들었다. 이 도메인이 워낙 규칙이 복잡해서, 나중에 누군가 손댈 때 왜 이 산식이 이 모양인지 맥락 없이 마주치지 않도록.
근데 큰 feat 커밋을 올리고 나서 곧바로 fix 커밋이 세 개 더 붙었다. 이게 오늘의 핵심 삽질이었다.
판매장려금 산식에서 직충전 축을 포함하고 있었던 게 문제였다. 운영 실측을 보니 직충전 경로는 현재 기준으로 처리 건이 없다. 사용자 쪽과 확정한 결과, 판매장려금 base는 쿠폰 거래만으로 잡아야 한다는 결론이 났다. 산식 자체를 고쳐야 했다. 계산 유틸 클래스를 다시 열어서 직충전 축을 완전히 제거했다.
그런데 산식만 고치면 끝이 아니었다. rebate-list.jsp 화면에 직접충전액 컬럼이 버젓이 올라가 있었다. 운영에서 실측 건이 0인 컬럼을 화면에 계속 띄워놓는 건 사용자를 혼란스럽게 할 뿐이다. 컬럼을 화면에서 제거했다. 단, 계산 로직, DB 컬럼, 엑셀 export는 그대로 유지했다. 나중에 경로가 살아날 경우를 대비한 것이기도 하고, 지금 당장 DB까지 건드리는 건 범위가 크다는 판단이었다.
문구도 하나 잡았다. 기준요율이라고 표기되어 있던 걸 기존요율로 바꿨다. 작은 것 같지만 이게 사용자 입장에서는 아예 다른 의미다. 기준이라고 하면 어떤 기준값인가 싶어서 찾아다니게 된다. 그냥 기존에 적용하던 요율이라는 뜻인데. 원금 컬럼도 표현을 평문화했다. 이런 문구 수정은 사실 코드 변경 자체보다 결정 과정이 더 오래 걸릴 때가 많다. 오늘은 그나마 빨리 정리됐다.
영수증 진위확인, 공개 페이지와 배치 두 방향으로
판매장려금 쪽을 정리하고 나서 영수증 쪽으로 넘어갔다.
/receipt/verify 라는 공개 접근 가능한 영수증 진위확인 페이지를 새로 만들었다. receiptVerify.jsp가 신규 파일이고, invoiceShareView.jsp에서도 이 페이지로 링크가 연결된다. 관련 컨트롤러도 추가했다. 내부 관리자 화면이 아니라 일반 사용자가 직접 접근할 수 있는 경로이기 때문에, 버튼 문구나 CTA 톤을 store 실사용 톤으로 맞추는 style 커밋도 따로 뗐다. 미묘한 차이인데, 내부 도구 느낌 나는 언어와 실제 서비스 사용자 화면 언어가 다르다. 보는 사람이 다르면 쓰는 말도 달라야 한다.
백필 배치는 ReceiptBackfillBatch라는 이름으로 매일 04:20에 돌도록 잡았다. 영수증이 누락된 건들을 새벽에 조용히 채워주는 용도다. 쿼리 매퍼도 새로 추가했고, scheduler.jsp 화면에도 이 배치가 노출되도록 했다. api-reference.md에도 명세를 추가해뒀다.
문서 작업들, 그리고 psy 자동화
오늘 docs 커밋이 꽤 많이 붙었다. 앱스토어 관련 경과 기록, AGENTS.md 신규 추가, ads_controller 분리 기록 등. 특히 AGENTS.md는 브라우저 자동화 작업을 할 때 agent-browser CLI를 경유해야 한다는 룰을 명시해둔 것이다. 이게 없으면 나중에 자동화 코드가 다른 경로로 붙어서 관리 포인트가 흩어진다.
하루 끝에 chore(psy) 커밋이 하나 찍혔다. 오늘 날짜 기준 심리 테스트 자동 생성 - daydream-type, iching-hexagram-destiny, midnight-namebook 세 종. 매일 이 배치가 도는 구조라 특별히 뭘 한 건 없고, 그냥 찍히는 커밋이다. 근데 저 커버 이미지 파일명들 볼 때마다 이 자동화 파이프라인 잘 돌아가고 있구나 싶어서 묘하게 뿌듯하다.
하루를 보내고 나서
솔직히 오늘은 여러 가지를 동시에 밀어붙인 날이라 어느 하나를 깊게 팠다는 느낌보다는, 전방위적으로 불을 끈 날이라는 느낌이 강하다.
앱스토어 쪽은 일단 WAITING_FOR_REVIEW에 들어갔으니까 지금 할 수 있는 건 다 했다. 리젝션 이유를 완전히 제거했고, 프리플라이트 게이트로 재발 방지까지 걸어뒀다. 다음 빌드부터는 같은 이유로 막힐 일이 없어야 한다.
판매장려금 산식은 사용자 확정을 받고 나서 코드를 바꿨기 때문에 방향 자체는 맞다. 다만 feat → fix → fix → fix 흐름이 좀 찜찜하다. 큰 기능 올리기 전에 산식 기준을 더 먼저 확정하고 들어갔으면 fix 커밋들이 덜 붙었을 텐데. 순서의 문제였다.
데드락은 캐시 전환으로 구조가 바뀌었는데, 실제 운영 트래픽 상에서 잠금 충돌이 줄어드는지 내일부터 지켜봐야 할 것 같다.
영수증 진위확인 페이지는 깔끔하게 끝난 편이다. 공개 페이지라는 게 생각보다 신경 쓸 포인트가 많은데, 문구 하나까지 style 커밋으로 따로 뗀 게 나름 적당한 선이었다.
오늘 총 커밋 수가 많았지만, 각 트랙이 독립적으로 돌아갔기 때문에 서로 얽히지는 않았다. 그 점은 다행이었다. 얽혔다면 훨씬 긴 하루였을 것이다.
댓글 0
첫 댓글 달아줘.