사이드프로젝트 slecs

결함 9건 정리하고 포트폴리오 데모 4개 올린 오후

목차

오후 반나절을 세 가지 결이 전혀 다른 일들이 뒤섞인 채로 보냈다. 처음 두 시간은 라이브를 앞둔 결제-정산 서비스의 불끄기였고, 중간에 Android 빌드 크래시 근본원인을 파고들었다가, 나머지 시간은 포트폴리오 데모 사이트를 한꺼번에 4개 밀어 넣었다. 각 작업이 서로 다른 레포, 다른 맥락이었지만 사실 이게 총괄 자리의 일상이다. 맥락 스위칭이 습관이 아니면 뭔가 빠진다.


결제와 정산, 라이브 직전 결함 정리

오후 12시 직후부터 가장 긴급했던 건 결제 파이프라인이었다. 가상계좌 충전 흐름에서 1원인증 완료 이후 충전 게이트가 무한루프에 빠지는 상황이 발견됐다. 재현 조건이 pay_member 행이 아직 생성되지 않은 신규 회원이었는데, 인증 완료 시그널을 받고 충전 게이트를 열려고 조회하면 해당 행이 없어서 예외를 던지고, 그게 재시도 루프에 물려서 계속 돌았다. 수정 자체는 간단했다 - 존재 체크를 먼저 끊어주면 됐다. 다만 이 케이스가 발견된 맥락이 "라이브 전 최종 QA" 타이밍이었다는 게 아찔했다. 신규 가입 직후 바로 충전을 시도하는 경우는 실제로 꽤 빈번하다.

1원인증 루프 말고도 같은 가상계좌 충전 흐름에서 결함이 3건 더 나왔다. 총 4건을 한 커밋에 묶었는데, 그게 맞는 판단이었는지는 여전히 반반이다. 커밋 단위를 잘게 쪼개면 리뷰하기 좋지만, 이 케이스들은 모두 같은 charge 플로우 안에서 맞물려 있어서 하나만 고치면 다른 데서 터지는 식이었다. 묶어서 통째로 검증하는 게 빨랐다.

정산 쪽도 그날 건드려야 할 게 쌓여 있었다. 총 5건인데, P1으로 분류한 건 운영자 커미션 확정 원자화였다. 원자화라고 말했지만 실제로는 커미션 계산과 상태 업데이트가 두 쿼리로 분리돼 있어서, 중간에 실패하면 어중간한 상태가 남을 수 있는 구조였다. 이걸 단일 트랜잭션 단위로 묶었다. 배치 배치 레이어에서 처리되는 로직이라 단위 테스트가 쉽지 않아서, 유틸 클래스에 경계 조건을 추가해서 간접 검증하는 방식으로 커버했다. SQL 매퍼도 두 파일을 수정했는데, 쿼리가 분산돼 있어서 변경 영향 범위를 추적하는 게 손이 더 갔다.

P2 4건은 데이터 정합성 관련이었다. 여기까지 정리하고 나서야 "라이브 전에 다 막았다"는 느낌이 들었다.


Android AGP9 R8 크래시, 근본원인까지 파고든 과정

결제-정산 결함을 처리하다 알림이 들어왔다. Flutter 앱 데일리 테스터 런에서 첫 실행 크래시가 간헐적으로 발생하고 있다는 것이다. 처음에는 단순 flaky라고 봤는데 계속 같은 패턴이었다. WorkDatabase_Impl 생성자에서 터지는 크래시였다.

증상이 "첫 실행에서만 나고, 두 번째 실행부터 안 난다"였다. 이게 힌트였다. R8가 WorkManager의 Room 내부 클래스 생성자를 인라이닝하거나 스트립하는 과정에서 초기화 순서 문제가 생기는 케이스다. AGP9에서 minify가 기본 ON으로 바뀐 건데, 이전 AGP 버전에서는 명시 설정 없이도 minify가 꺼져 있었다. 마이그레이션 과정에서 이 변경이 조용히 적용된 거다.

수정은 build.gradle.kts에서 minifyshrink를 명시적으로 OFF하는 것이었다. 배포 빌드에서는 proguard rule을 제대로 써야 하지만, 지금 단계(Alpha 테스트)에서는 명시 OFF가 맞는 판단이었다. 이걸 확인하는 과정에서 3개 앱 레포 모두 같은 패턴이었다는 걸 알게 됐고, 각각 빌드번호를 올려서 재출시했다. 크래시픽스가 맞다면 Play Store 전파 이후 데일리 런 통계에서 확인된다.

임시방편으로 데일리런 스크립트에 3회 재시도 로직도 추가했다. 근본원인을 잡기 전까지 버텨야 하는 상황이었는데, 첫 실행 flaky는 재시도 한 번이면 회피가 가능해서 모니터링 공백을 최소화할 수 있었다. closed_install.sh 스크립트도 추가해서 앱별 설치 흐름을 분리했다.

에뮬 13대 신버전 업데이트와 스냅샷 영구화 패스가 남아 있는 건 따로 기록해뒀다. Play 전파 대기 타이밍이 맞지 않아서 그날은 손을 못 댔다.


포트폴리오 데모 사이트 4개, 한 오후에 밀어 넣기

결제·정산 결함과 Android 크래시를 어느 정도 정리하고 나서, 오후 중후반은 포트폴리오 데모 작업에 집중했다. 데모 사이트에 올릴 항목들이 밀려 있었다. 한 번에 4개를 올렸다.

데모 핵심 기능
webinar-platform 3단계 채팅 권한, 대댓글 제어, 서버 모니터링
coupon-i18n 발급 중복 차단, 보관함 상태 분류, KO/EN 전환, 어드민 CRUD
franchise-intake 매장/상품/OEM 입력, 파일 첨부, 관리자 통합 조회·엑셀
camera-filter LUT .cube/HALD 업로드, WebGL 셰이더, JSON 즉시 배포

작업 순서는 webinar-platform부터였다. 올리고 보니 295px 이하 헤더가 오버플로우되는 버그가 있었고, WCAG 명암비도 기준 미달인 영역이 있었다. 썸네일도 동시에 갱신했다. coupon-i18n도 비슷하게 올리고 나서 버튼 명암비 문제가 보여서 활성/비활성 상태 둘 다 잡았다. franchise-intake 역시 뱃지 명암비 3.30에서 5.02로 올렸다.

사실 데모 HTML 하나 만드는 데 생각보다 손이 많이 간다. 실제 작동처럼 보여야 하니까 단순 목업이 아니라 상태 전환과 인터랙션이 다 살아 있어야 한다. camera-filter 같은 경우는 WebGL 셰이더로 실제 LUT 필터가 돌아가는 걸 보여줘야 해서, 폰 목업 탭에 촬영, 필터칩, 강도 조절, 페이월 흐름까지 구현했다. 구독 게이팅 로직을 데모에서 어디까지 보여주느냐가 생각보다 판단이 필요한 부분이었다.

CLAUDE.md의 라이브 데모 목록도 커밋마다 갱신했는데, 초반에 stale한 상태가 있었다. P 배열이 단일진실이라 거기 맞춰 정정했다. webinar-platform을 올리면서 11개에서 12개, franchise-intake 추가로 13개로 늘어났다. 데모가 쌓일수록 카드 그리드가 정보 밀도 면에서 좀 더 볼 만해지는 느낌이 든다.

camera-filter는 구현 메모도 따로 CLAUDE.md에 남겼다. 셰이더 구조나 LUT 파싱 방식이 나중에 다시 볼 때 맥락이 없으면 헷갈리는 부분이 있어서다.


소셜 발행과 SEO, 틈새에 처리한 것들

완전히 다른 결의 작업이 두 개 더 있었다. 소셜 발행 스크립트에서 배치 3연발 패턴을 제거하고, 매시 cron에 랜덤 지연을 얹어서 사이트당 3-6시간 간격을 보장하게 바꿨다. 봇 티가 나는 게 제일 피하고 싶은 상황이라, 발행 리듬 자체를 인간처럼 불규칙하게 만드는 게 목적이었다. Bluesky랑 Tumblr는 이미 라이브 상태고, Mastodon은 아직 대기 중이다.

SEO 쪽에서는 F1 SportsEvent JSON-LD에 performer(포디엄 드라이버)를 추가하고, description/organizer/eventStatus/endDate/image도 채웠다. GSC에서 이벤트 경고가 떠 있는 상태였는데 필드 누락이 원인이었다. 게임 쪽은 Steam 유저 평점을 aggregateRating으로 Product JSON-LD에 넣었다. 상품 스니펫 경고도 같은 흐름이다.


총 30개 가까운 커밋이 한 시간대에 쌓였는데, 실제 집중 투입 시간은 각 작업마다 달랐다. 결제-정산 결함이 제일 긴장감이 높았고, Android 크래시 근본원인 찾는 게 제일 시간이 걸렸다. 포폴 데모는 물량전이었다. 맥락 전환이 잦으면 피로가 쌓이는데, 이날은 우선순위 순서가 맞게 흘러줘서 각 작업 끝날 때마다 "닫혔다"는 감각이 있었다. 그게 버텨주는 이유다.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.