큐를 회수하고, soft-404를 죽이고, 하루를 닫다
목차
오늘 하루 중 제일 먼저 내 머리를 점령한 건 결제 쪽 작업이었다. 정확히는 "쿠폰 전환 이후 큐가 흘러넘칠 때 어떻게 회수하느냐"는 문제. 커밋 메시지에 쓴 "즐거운 전환 큐 회수배치"라는 표현이 좀 웃기게 보일 수도 있는데, 내 입장에선 전혀 즐겁지 않았다.
이중사용 방지라는 게 생각보다 복잡하다
쿠폰이나 포인트 계열 결제 로직을 다뤄본 사람은 알겠지만, "하나를 두 번 쓰지 못하게 한다"는 요구사항이 단순해 보여도 실제 구현에서 꽤 많은 경우의 수를 만난다. 특히 오늘처럼 배치와 실시간이 동시에 같은 자원을 건드리는 구조라면 더.
오늘 작업의 핵심은 크게 세 갈래였다.
첫째, 큐 회수 배치. 전환 완료됐지만 무언가 이유로 큐에 남아있는 항목들을 배치로 긁어서 정상 종료 처리하는 루틴. 이게 slecs/batch/settlement 쪽에 들어갔다. 배치 로직 자체는 어렵지 않은데, 언제 트리거하느냐, 재시도 시 상태를 어떻게 체크하느냐가 관건이었다.
둘째, 통장 대사. 내부 장부와 실제 입출금 기록을 맞춰보는 로직. utl/payment 패키지 두 개를 건드렸는데, 결제 유틸리티가 두 개 있다는 것 자체가 이 프로젝트의 레거시적 복잡도를 암시한다. 하나는 아마 구버전 코드이거나 특정 채널용. 두 파일 모두 수정했다는 건 공통 인터페이스 레벨에서 변경이 있었다는 얘기다.
셋째, 재고 sweep 차단. 이게 제일 까다로웠다. "접수 즉시"라는 조건이 붙어있다 - 결제가 접수되는 그 순간부터 해당 재고를 sweep 대상에서 제외해야 한다는 뜻이다. 그 전까지는 일정 시간 후 잠금을 해제하는 방식이었던 것 같고, 그 틈에 이중사용이 발생하고 있었거나 발생 가능성이 있었던 거다.
SQL 매퍼(slecs/coupon/쿼리 매퍼)도 수정됐다. 상태값으로 필터링하는 쿼리에 조건이 추가됐을 거다. 이런 류의 배치 쿼리는 인덱스 히트 여부가 성능에 직결되니까, 쿼리 작성하면서 실행계획도 들여다봤을 것 같은데 - 실제로 그랬다. coupon/web과 dev/web 컨트롤러도 건드렸으니 API 엔드포인트 레벨에서 상태 조회나 강제 처리 인터페이스도 손 댄 듯하다. dev 쪽은 아마 테스트나 운영 수동 트리거용.
작업하다 보면 이런 결제·정산 도메인은 코드 자체보다 "이 상태가 정확히 어떤 의미인가"를 이해하는 데 시간이 더 걸린다. 오전 중 상당 시간을 기존 코드 읽으면서 상태 전이 흐름 머릿속에 그리는 데 썼다. 배치에서 처리하는 "회수" 대상이 실시간 전환 로직과 어떻게 겹치는지, 통장 대사 타이밍이 배치 실행 순서와 어떻게 맞물려야 하는지. 코드 한 줄 짜기 전에 이미 머리 반이 나간 느낌이었다.
이중사용 방지 로직을 짤 때 가장 경계한 건 "차단이 너무 빨라도 문제, 너무 늦어도 문제"라는 딜레마다. 접수 즉시 잠근다고 해도 배치가 그 상태를 인식하기 전에 sweep을 돌려버리면 의미가 없다. 결국 트랜잭션 경계와 배치 실행 순서에 대한 가정을 코드에 명시적으로 박아넣는 방식으로 정리했다. 코멘트 달기 귀찮아하는 편인데, 이 부분은 나중에 내가 봐도 헷갈릴 것 같아서 꼼꼼하게 적었다.
SEO 쪽 삽질은 좀 다른 종류였다
오후에 넘어가면서 seo-monitor 쪽으로 전환했다. 여기는 Python 영역 - meta-check.py. 오늘 추가한 기능이 세 가지인데, 커밋 메시지에 "3종"이라고 써놨다.
구조적 문제 자동감지(404/5xx/soft-404), autofix 스킵, legal 컨테이너 has_noindex 자동치유.
이 중에서 제일 신경 쓴 건 soft-404 관련 legal 컨테이너 이슈였다. 문서에까지 두 개의 커밋을 썼을 만큼 - "app-legal cms_site 행 has_noindex 필수"라는 게 별도 docs 커밋으로 분리됐다는 건, 이게 단순 기능 추가가 아니라 "반드시 이렇게 세팅해야 재발 안 한다"는 운영 룰로 굳혀야 하는 내용이었다는 뜻이다.
soft-404 무한반복이라는 게 왜 생기냐면, 검색엔진 크롤러 입장에서는 페이지가 200을 반환하지만 콘텐츠가 "이 페이지는 존재하지 않습니다" 같은 에러성 내용이면 soft-404로 분류한다. 여기서 legal 계열 페이지가 특정 조건에서 has_noindex가 빠진 채 크롤링되면, 크롤러가 계속 다시 오는 동시에 soft-404로 판정받는 루프에 빠진다. 작은 것 같지만 SEO 점수에는 계속 흠집을 낸다.
auto-heal 로직을 짜면서 고민한 게 있다. "자동으로 고친다"는 건 좋은데, 고쳤다는 사실을 어떻게 기록하고 확인하느냐. 잘못 건드리면 고치려다 더 망가지는 케이스도 충분히 있다. 그래서 autofix 스킵 조건도 같이 설계했다. 특정 패턴이나 상태에서는 자동으로 건드리지 않고 플래그만 남기는 방식. "고칠 수 있는 것만 고치고, 나머지는 눈에 띄게 남겨둔다"는 원칙.
404/5xx 감지 로직 자체는 사실 예전부터 있었던 건데, 오늘 그걸 구조적으로 재정비했다. 단순히 상태코드 체크하는 게 아니라, URL 패턴이라든가 redirect 체인이라든가, 구조적으로 문제가 있는 경우를 좀 더 세밀하게 분류하도록 했다.
문서 작업이 두 커밋인 게 보이는데, 하나는 전반적인 배포 반영 기록이고 하나는 legal 룰 명시다. 이런 종류의 문서는 솔직히 귀찮다. 코드 다 짜고 나서 "아 이제 문서도 써야지" 하는 그 피로감. 그냥 넘기면 며칠 뒤 내가 왜 이렇게 짰는지 기억 못하거나, 다른 사람이 건드렸다가 무한반복 이슈 다시 터트리는 거 눈에 보이니까 억지로라도 썼다. 그래도 has_noindex 필수를 별도 docs 커밋으로 분리한 건 잘한 선택이다 - 나중에 git log 뒤질 때 훨씬 찾기 쉽다.
보안 모니터링 문서화, 이게 오늘 할 일인 줄 몰랐다
솔직히 오늘 이걸 할 계획은 없었다. 결제 배치 작업하고 SEO 모니터 정리하면 충분하다고 생각했는데, 보안 모니터링 관련해서 WAF 설정과 exploit/scraper 차단 내용을 문서로 정리해야 할 타이밍이 됐다.
README.md, docs/bots-infra.md, docs/hedvion-CLAUDE.md - 세 파일에 걸쳐서 들어갔다. daily security_monitor라는 표현이 커밋에 있는데, 매일 자동으로 돌아가는 보안 모니터링 루틴이 있다는 거고, 그게 WAF 레벨에서 exploit 시도나 스크래퍼 트래픽을 어떻게 차단하는지를 인프라 문서에 반영한 거다.
hedvion-CLAUDE.md에까지 들어간 건 중요하다. 이 파일은 봇 공통 컨텍스트로 매 호출마다 로드되는 파일이다. 거기에 보안 모니터 관련 내용이 들어간다는 건, 앞으로 Claude가 작업할 때 이 보안 레이어를 인지하고 있어야 한다는 거다. 단순 참고 문서가 아니라 운영 가이드의 일부.
WAF exploit/scraper 차단 문서화라는 게 "어떤 패턴이 막히고 있다"를 기록하는 작업인데, 이런 건 늦게 할수록 누락이 생긴다. 운영하다 보면 막은 게 어느 순간 잊혀지고, 나중에 왜 이 룰이 있는지 아무도 모르는 상황이 된다. 그래서 귀찮아도 지금 쓰는 게 맞다.
README에도 들어갔다는 게 좀 의미 있다. 보통 README는 새로운 사람이 제일 먼저 보는 파일이니까, 보안 모니터링이 있다는 사실 자체를 앞에 노출하는 셈이다.
자동 생성 테스트 세 편, 이건 그냥 돌아간 거다
chore(psy): daily auto-generated tests - 커밋 타임스탬프 보면 아마 자동으로 생성된 거다. escape-room-type, friend-cancel-reaction, ghost-reputation. 심리테스트 콘텐츠 세 개.
커버 이미지까지 같이 생성됐다. .webp 파일 세 개, JSON 파일 세 개. 파이프라인이 정상 작동하고 있다는 신호다. 내가 손댄 게 아니라 자동으로 처리된 거지만, 이게 커밋 로그에 뜬다는 건 정상 작동 중이라는 증거다. 가끔 파이프라인이 조용히 죽어있다가 며칠 후에야 발견되는 경우가 있는데, 매일 이렇게 커밋이 찍히면 그냥 로그 보면서 살아있다는 걸 확인할 수 있다.
테스트 제목들이 요즘 감성을 잘 탄다. 방탈출 유형, 친구 약속 취소 반응, 유령 평판. 뭔가 특정 연령대 타깃이 명확하게 보인다.
오늘 하루를 돌아보면
작업의 성격이 세 개가 완전히 달랐다. Java 배치/결제 도메인, Python SEO 모니터, 인프라 문서. 동일한 사람이 하루에 이 세 가지를 다 건드렸다는 게 총괄 포지션의 현실이다. 깊이 파고드는 작업과 가볍게 정리하는 작업이 섞이고, 코드와 문서가 번갈아 온다.
오늘 가장 머리를 쓴 건 단연 결제 배치 작업이었다. 상태 전이 로직, 이중사용 방지, 통장 대사 타이밍 - 이 세 가지가 서로 물려있어서, 하나 건드리면 다른 쪽에서 뭔가 깨질 것 같은 그 느낌을 계속 안고 작업해야 했다. 지금은 테스트 통과됐고 의도한 대로 돌아가고 있는 것 같다. "것 같다"는 게 찝찝하긴 하다. 결제 관련 코드는 진짜로 배포 이후 며칠은 긴장이 된다.
SEO 쪽은 솔직히 잘 됐다. legal 컨테이너 has_noindex 자동치유라는 게 예전에도 수동으로 처리하던 부분인데, 그걸 자동화하면서 문서에까지 룰로 박아뒀으니 이제 같은 이슈로 시간을 쓸 일은 없다. 이런 류의 작업이 눈에 잘 안 띄어서 과소평가받기 쉬운데, 실제로는 반복 작업 줄이는 데 효과가 크다.
보안 문서는 오늘 할 일 리스트에 없었는데 결국 했다. 적당한 타이밍에 억지로라도 쓰는 게 나중을 위해 낫다는 걸 경험으로 알기 때문에. 머리는 쓰기 싫고 손은 움직여야 하는 그 특유의 문서 작업 피로감과 함께.
퇴근하기 전에 커밋 로그 한 번 훑었는데, 오늘 찍힌 커밋이 다 의미 있는 것들이다. 자동 생성 포함해서 여섯 개. 빈 커밋 없이, 허수 없이. 그걸로 오늘은 충분하다.
댓글 0
첫 댓글 달아줘.