2026-07-20 (월) 개발일지
오늘은 크게 두 가지를 정리했다. 하나는 모바일 SDK의 행동 데이터 수집 파이프라인이 멈춰 있는 걸 복구한 일, 다른 하나는 포트폴리오 코딩 시각화 페이지에서 면접관 질문과 입력값을 제대로 보여주기 위해 레이아웃을 뜯어고친 일이다. 두 이슈 모두 원인을 찾는 데 예상보다 시간이 걸렸고, 중간에 방향을 두 번씩 틀어야 했다.
행동 데이터 수집 파이프라인 — /ingest/behavior 410 Gone 과 phantom-parent 개선
며칠 전부터 서버 대시보드에서 특정 세션의 behavior.screen.view 이벤트가 보이지 않는다는 리포트가 들어왔다. 분명 SDK는 정상적으로 초기화되고 세션도 시작되는데, 세션 이후의 행동 데이터가 아예 없는 상태였다. 당장 재현해보니 iOS 시뮬레이터에서는 behavior.session.start가 뜨지 않고 Android에서는 간헐적으로 tap 이벤트가 빠져 있었다.
가장 먼저 의심한 건 네트워크 경로였다. curl로 각 엔드포인트를 찔러보니 충격적인 발견이 나왔다. /ingest/behavior 엔드포인트가 410 Gone을 반환하고 있었다. 이 엔드포인트는 최근 API 정리 과정에서 제거된 듯했는데, SDK 쪽에는 fallback 로직이 전혀 없었다. 모든 behavior 이벤트가 여기로 집중되다 보니 410 한 방에 전체 파이프라인이 막혔던 것이다.
/ingest/behavior410 — 이게 session.start가 안 보인 진짜 원인이었다.
처음에는 단순히 엔드포인트를 복구하는 쪽으로 갈까 고민했다. 하지만 서버팀의 의도를 무시하고 다시 살리면 또 같은 문제가 반복될 게 뻔했다. 그래서 fallback 방향으로 선회했다. BehaviorAdapter.kt와 BehaviorAdapter.swift에 검증 로직을 추가해서, /ingest/behavior가 410이나 404를 반환하면 기존의 /event 엔드포인트로 전환하도록 수정했다.
이와 별도로 phantom-parent 문제도 확인됐다. 백그라운드에서 screen.view가 강제로 export될 때 부모 컨텍스트가 유실되는 현상이었다. Android에서는 ContextManager.kt에서 액티비티 생명주기와 함께 parent를 갱신하는 로직이 늦게 타서 빈 parent가 할당되고 있었다. iOS도 비슷한 구조였지만 증상이 덜 심각했다.
해결한 변경
| 문제 | 수정 파일 | 핵심 |
|---|---|---|
/ingest/behavior 410 |
BehaviorAdapter.kt, BehaviorAdapter.swift |
fallback → /event |
| phantom-parent (Android) | ContextManager.kt, AndroidTapAdapter.kt |
생명주기 콜백에서 parent 명시적 재설정 |
| 콜드스타트 tap 누락 | AndroidMemoryAdapter.kt |
세션 재개 시 tap 버퍼 flush |
검증: iOS와 Android 각각에서 정상 동작을 확인했다. behavior.screen.view가 이전처럼 찍히고 tap 이벤트의 parent도 올바르게 연결됐다. 7개 커밋(P1~P9 처리, 1db84e3)을 dev에 푸시했고 서버팀에 전달했다.
남은 과제: iOS 쪽이 네이티브 성능에 비해 여전히 약간 느리다는 리포트가 남아 있다. 이건 다음 스프린트에서 프로파일링으로 다시 잡을 예정.
포트폴리오 코딩 시각화 — 면접관 질문을 최상단으로 올린 레이아웃 재설계
두 번째 이슈는 20_code_execution_visualizer.html이다. 이 페이지는 코딩 테스트 문제의 코드를 하이라이트하고, 스텝별로 실행하면서 변수를 시각화하는 위젯인데, 문제마다 면접관의 실제 질문 문장과 입력값 예시가 하단에 밀려 있어서 보기 불편하다는 피드백이 들어왔다.
처음엔 단순히 div 순서만 바꾸면 될 거라 생각했다. 그런데 코드 블록 아래에 위치한 source 블럭이 highlight.js와 line 매핑에 의존하고 있어서, 그냥 위로 올리면 하이라이트 인덱스가 어긋나는 문제가 발생했다. 특히 11번 문제는 초보자용 주석이 많아 코드 길이가 길었는데, 그걸 그대로 두면 오디오 생성(edge-tts)에서도 너무 길어져 복습용으로 부적합했다.
방향을 바꿔서, 주석과 빈 줄만 제거한 실행 코드를 별도로 추출하기로 했다. sed로 #으로 시작하는 줄과 빈 줄을 모두 날리고, 로직과 인라인 주석은 그대로 유지했다. 그리고 이 실행 코드를 기존 golden 코드와 비교해 문제별 line 매핑을 자동 생성하는 스크립트를 만들었다. sed 파이프라인과 python3로 비교하는 과정을 거쳐 52문제 전체의 매핑표를 뽑았다.
레이아웃은 다음과 같이 바뀌었다.
이전
[코드 블록 (source)]
[면접관 질문] [입력 예시] ← 하단에 작게
[실행 애니메이션]
이후
[🎙️ 면접관 질문]
[📝 입력 · 출력 예시] ← 상단에 배치
[실행 애니메이션]
[코드 블록 (source)] ← 하단으로 내림
CSS에 .left, .ask, .mean 클래스를 추가하고, QMETA 데이터를 <script> 태그에 삽입한 뒤 load() 함수에서 각 영역을 채우도록 했다. 하이라이트는 기존 line 매핑을 그대로 사용하되, source 블록이 아래로 이동한 만큼 스크롤 위치만 조정했다.
검증: 재배포 후 모든 문제에서 면접관 질문과 입력값이 상단에 정확히 표시됨을 확인했다. 11번 문제는 주석 제거 실행코드로 교체되어 오디오 길이도 3분 이내로 줄었다. line 매핑도 빈 줄이 사라진 덕분에 이전보다 정확해졌다.
남은 과제: 아직 11번 문제를 제외한 나머지 51문제는 원래 코드(주석 포함)를 사용 중인데, 일관성을 위해 전부 실행코드로 통일할지 검토 중이다. 다만 주석이 학습에 도움되는 경우도 있어서 옵션 선택을 사용자에게 맡기는 쪽이 더 나을 것 같다.
오늘의 한줄
파이프라인이 막히면 엔드포인트 응답 코드를 먼저 확인하라 — 410 하나가 며칠의 디버깅을 절약한다.