벽과 누적
에세이 문체 — 작업을 배경으로, 생각을 전경으로
데이터 분석 봇이 사내 RDS를 읽기 시작한 날이다. 어제까지는 외부 광고 데이터와 실험 도구만 보던 봇이었다. 오늘부터는 결제와 멤버십 테이블을 직접 들여다본다. 권한 모델은 단순하게 갔다. 봇 자체에 분석가 프로필을 하나 새로 만들고, 읽기 권한만 묶었다. 다만 한 가지 단서가 붙는다. 공유 MCP 서버는 자격 증명 정도만 마스킹하기로 설계되어 있어서, 이름이나 전화번호는 호출하는 쪽에서 가린다. 분석에 실명이 필요한 1퍼센트의 경우는 CS 봇을 통하기로 했다. 굳이 별도의 해제 도구를 만들지 않았다. 필요하지 않은 것은 만들지 않는 게 낫다.
스킬을 정리하면서 두 개를 옮기고 두 개를 새로 썼다. 마케팅팀이 매주 쓰는 보고서 톤을 살펴보니 알림톡 효과 측정과 CRM 캠페인 분석이 비어 있었다. 깊이 고민할 일은 아니어서 메인에 머지하고 자동 배포를 켰다. GitHub Actions OIDC 트러스트를 새 EC2에 묶고, 매번 SSM으로 손수 배포하던 흐름을 끊었다. 작은 변화지만 손이 자유로워진다.
GA4 연동에서 한 번 막혔다. 서비스 계정 이메일을 GA4 사용자로 추가하려 하자 워크스페이스 도메인 정책이 거부했다. 외부 도메인의 계정을 조직 자산에 붙일 수 없다는 뜻이다. 우회는 OAuth 2.0 사용자 인증으로 갔다. 회사 구글 계정으로 한 번 로그인해 리프레시 토큰을 받고, 봇이 그걸로 영구히 호출한다. 또 하나의 함정은 속성 ID와 계정 ID가 둘 다 아홉 자리 숫자라는 것이다. 한참을 잘못된 ID로 403을 받고 있었다. accountSummaries 호출 한 번이면 트리가 보인다는 걸 그제야 떠올렸다.
저녁에는 다른 봇이 신고됐다. 개발 봇이 어제부터 분석 명령에 응답하지 않는다는 것이다. 헬스 체크는 통과하고 짧은 인사도 받는데, 실제 작업에 들어가는 순간 fork가 거부된다. 컨테이너 안의 PID 카운터가 2,261까지 올라 있었다. 8일 동안 무중단 운영하면서 5분마다 git pull을 도는 cron의 자식 프로세스가 어딘가에서 회수되지 않고 있었다. 컨테이너를 다시 띄우자 PID는 5로 떨어졌다. 같은 프레임워크 위에 있는 다른 두 봇은 PID가 한 자릿수였다. 동일한 코드 경로를 공유하면서도 한쪽만 누적되는 이유가 따로 있다는 뜻이다. 원인 파악은 큐에 P0으로 넣었다.
하루 동안 두 가지 다른 종류의 실패를 봤다. 하나는 경계의 실패였다. 워크스페이스 정책이 외부 자격증명을 거부하는 명확한 거절. 다른 하나는 누적의 실패였다. 아무도 보지 않는 사이에 천천히 차오른 카운터. 전자는 우회로가 있고, 후자는 잠복이 길다. 두 번째가 늘 더 까다롭다.