문정호

문정호님의 포스트

문정호

문정호

김대리. 너무 GA4 이벤트? 사용하지 마세요.


GA4 이벤트를 코드에 직접 심으면 측정할 게 늘어날 때마다 배포를 해야 합니다.

기능은 다 만들었는데 이벤트 심느라 배포가 밀리고, 그렇게 심어도 마케팅이 보고 싶은 데이터는 빠져 있어서 다음 배포로 밀리는 경우가 있습니다.

슈퍼인턴에서 코드에서 직접 넣던 event를 지우고 GTM을 설정하였습니다. E2E를 활용해 상호작용 가능한 모든 요소를 측정하게 만든 과정을 공유합니다.

1. E2E 셀렉터 재활용

GTM이 클릭을 잡으려면 요소를 가리켜야 하는데, 클래스나 구조로 가리키면 디자인이나 기능이 바뀔 때 이벤트가 끊겨 한참 뒤에 알아차립니다.

E2E에서 버튼이나 입력창 같은 상호작용 요소를 찾아 조작하는데, 측정하고 싶은 것도 정확히 그 요소입니다. 교집합 덕분에 어트리뷰트(data-testid)가 이미 다 붙어 있었고, 새로 만들 규칙도 없었습니다.

Component Click 태그 하나로 흩어져 있던 클릭 측정을 일원화했습니다. 클릭을 전부 이 태그가 받고 element_id로 구분하여, 버튼이 늘어도 별도의 설정이 필요 없습니다. E2E도 같은 어트리뷰트를 쓰니 누락이 생기면 테스트에서 잡아냅니다.

2. dataLayer는 브라우저가 못 보는 것만

요소 클릭만 쌓으면 어떤 버튼이 몇 번 눌렸는지까지만 나옵니다. GA4에서 퍼널을 그리거나 신규·기존 코호트를 비교하려면 누른 사람이 누구인지가 붙어야 합니다. 그 정보는 서버에 있으니 dataLayer로 내려줍니다.

  • 전역 사용자 속성: 식별 정보, 상태값, 코호트, 활동 구간
  • BFF 패턴: 서버가 모아 내려주고 클라이언트는 한 번만 push
  • 주의사항: 값 종류 500개 초과 시 GA4가 (other)로 처리

그래서 가입일은 날짜가 아니라 월로, 지원 건수는 숫자가 아니라 구간으로 넘깁니다.

3. 커버 안 되는 건 커스텀

클릭 트리거는 무엇을 눌렀는지까지만 알려줍니다. 어디까지 진행했는지, 그래서 결과가 무엇인지는 클릭만으론 알 수 없습니다.

진입과 진행 단계는 page_view 경로 규칙으로 잡습니다. 완료는 뷰로 세면 결과 화면을 다시 열어본 사람까지 섞여 완료율이 100%를 넘어가니, 완료된 순간에만 코드가 맞춤 이벤트를 한 번 보냅니다.

슈퍼인턴에선 가입과 로그인은 동일 버튼으로 다른 기능을 수행합니다. 이렇게 클릭으로 알기 어려운 이벤트도 맞춤 이벤트로 보냅니다. OAuth 리다이렉트 구간은 서버에서 돌아 dataLayer 접근이 안 되니, 쿠키로 넘기고 도착 페이지가 읽어 소비합니다.

4. Claude Code로 강제

이미 상호작용 가능한 모든 요소를 테스트 하도록 E2E 테스트 하네스가 적용되어 있습니다. 측정에 필요한 게 개발 흐름에 이미 있었기 때문에 하네스만 추가했습니다.

  • 검사 스크립트: 어트리뷰트가 빠진 상호작용 요소를 전부 찾아냄
  • 커밋 훅: Claude Code가 커밋할 때 돌려서 빠진 게 있으면 차단
  • 예외 주석: 측정 안 할 요소는 주석 한 줄로 제외

GTM은 러닝커브가 있어 처음 세팅하기가 어렵습니다. 또한 개발에서 마케팅의 언어로 데이터를 풀 수 있도록 논의가 선행되어야 합니다. 조직에 GTM 도입이 필요하시다면 편하게 프로필 링크로 DM 주세요.

공채가 활발한 지금, 슈퍼인턴은 사내 MCP부터 GTM까지 데이터 기반 채용 서비스로 발전하고 있습니다. 채용 홍보를 맡기고 싶으시다면 biz@superpasshr.com으로 연락 주세요!

슈퍼인턴

우리 기업에 딱 맞는 주니어 인재 채용

1
0
문정호

문정호

그만 물어봐! 사내 MCP로 "데이터 민주화" 하기


"이번 달 지원자 몇 명이에요?" 데이터를 보려면 늘 개발자인 저를 거쳐야 했습니다.

마케터나 기획자분이 SQL을 쓸 줄 알아도 마찬가지였습니다. 조직 내 암묵지들 때문에 제대로 된 데이터를 뽑기 어려운 경우가 빈번하게 일어났습니다.
그래서 사내 DB를 AI가 직접 조회하는 MCP 서버를 만들었지만, 조직이 실제로 쓰는 MCP가 되기까지는 생각보다 멀었습니다.
MCP 배포 이후 2주동안 그 거리를 좁히면서 배운 3가지를 공유합니다.

1. MCP 구현

MCP SDK로 만들었습니다. 읽기 전용을 두 겹으로 강제했습니다.

  • 앱단: SQL 파서로 단일 SELECT만 통과
  • DB단: 계정 자체가 SELECT 권한만 보유

앱 가드를 우회해도 DB가 거부합니다.
다만 서로 다른 RDBMS라 직접적인 크로스 DB 조인은 불가능합니다. 대신 인터페이스를 통일해서 LLM이 두 번 쿼리해 직접 합치도록 했습니다.
MySQL과 PostgreSQL은 생각보다 많이 다릅니다.
커넥션 풀, 드라이버별 응답 형태, 타임아웃과 오류 체계가 전부 달라서 이걸 하나로 추상화하는 게 실제 작업의 대부분이었습니다.

2. 암묵지의 형식지화

조직과 서비스가 커지면 암묵지가 쌓입니다. AI에게는 이걸 초등학생에게 설명하듯 자세히 알려줘야 합니다.
예를 들어 앱스토어 이벤트용 캠페인과 CRM 캠페인은 데이터 구조가 전혀 다른데 둘 다 campaign이라는 이름을 씁니다.
AI는 이걸 구분할 능력이 없습니다. 그래서 데이터를 요청하면 심심찮게 의도하지 않은 결과가 나옵니다.
문제는 에러가 안 난다는 것입니다. 조용히 틀린 숫자가 나옵니다.

  • 커밋 시점: Claude Code hooks가 "스키마 바꿨는데 가이드도 고쳤냐"를 확인
  • CI 시점: 테스트 가드가 "그 내용이 실제 스키마와 맞냐"를 검증
  • 두 층이 짝입니다. 훅은 고쳤는지만, 테스트는 맞는지를 봅니다

도메인 지식을 통째로 주면 컨텍스트를 다 먹습니다. 그래서 맥락을 크기에 따라 대-중-소 목차의 트리 구조로 나눴습니다.
AI가 목차를 먼저 읽고 필요한 절만 골라 열도록 해서, 판별력은 살리고 컨텍스트는 아끼는 구조입니다.

3. 권한 관리

데이터를 여는 일과 개인정보를 지키는 일은 같이 가야 합니다. 개인정보보호법의 통계 작성, 과학적 연구, 기록 보존 목적에 맞춰 해시를 통한 가명화 처리를 했습니다.

  • 기본적으로 우리 조직 계정만 허용하고, 그중 admin만 전체를 볼 수 있습니다
  • 나머지 구성원은 가명화된 테이블만 조회됩니다
  • DB 계정 자체를 분리해서, 앱단에 SQL 인젝션이 들어와도 가명화된 것만 접근 가능합니다

가명 데이터도 개인정보라 GDPR을 준하기 위해 감사 로그를 남깁니다.
모든 호출을 누가 언제 어떻게 어떤 결과를 받았는지 구조화 로그에 기록해 사후 추적이 가능하도록 했습니다.

이제 개발자가 아니어도 직접 묻습니다. 저를 거치지 않아도 됩니다.
만들고 보니 그동안 흐린 눈 했던 도메인 문서화도 함께 해결됐고, 직접 처리하던 가명화도 자동화되었습니다.

사내 데이터를 AI에게 열어보려는 분들께 권합니다. 도구보다 "우리가 아는 것을 정리하는 일"이 먼저였습니다. 궁금하신 분들은 편하게 링크드인 DM 주세요!

슈퍼인턴

우리 기업에 딱 맞는 주니어 인재 채용

2
0