인강 수강생이 언제 떠나는지, 진짜로 알 수 있을까? — beforeunload vs Heartbeat 비교
취준 공부하다가 유튜브나 인강 틀어놓고 딴 생각하다 보면 어느새 영상이 끝나있는 경험, 다들 있으시죠?
저는 요즘 데이터 엔지니어 취준 공부를 하면서 “그럼 플랫폼 입장에서는 내가 언제 영상을 이탈했는지 어떻게 알지?”라는 궁금증이 생겼어요. 단순히 영상을 끝까지 봤냐 안 봤냐가 아니라, 정확히 어느 시점에 이탈했는지를 데이터로 잡는 게 생각보다 훨씬 까다로운 문제더라고요.
오늘은 그 고민을 파고들면서 공부한 내용을 정리해볼게요. 비전공자 눈높이로 최대한 쉽게 풀어볼 테니 같이 따라와 주세요.
왜 이탈 시점을 정확히 알아야 할까요?
인강 플랫폼 입장에서 생각해봐요.
수강생이 강의 중간에 이탈했다면, 어디서 이탈했냐에 따라 의미가 완전히 달라져요. 10분짜리 강의에서 2분에 나간 사람이랑 9분에 나간 사람은 전혀 다른 케이스거든요. 전자는 콘텐츠 자체에 흥미를 잃은 거고, 후자는 거의 다 본 거니까요.
이 데이터가 쌓이면 “어느 구간에서 수강생들이 많이 이탈하는가”를 분석할 수 있고, 그게 곧 강의 품질 개선이나 커리큘럼 개편의 근거가 돼요. 데이터가 없으면 그냥 감으로 운영하는 거고, 데이터가 있으면 근거 있는 의사결정이 가능해지죠.
→ 이탈 시점 데이터는 콘텐츠 개선의 핵심 지표예요.
가장 먼저 떠오르는 방법: beforeunload + sendBeacon
처음에 저도 이 방법부터 알게 됐어요.
beforeunload는 브라우저에서 탭을 닫거나 페이지를 벗어날 때 발생하는 이벤트예요. 쉽게 말하면 “창 닫기 버튼 누르는 순간 브라우저가 마지막으로 한 번 뭔가를 할 수 있는 기회”라고 보면 돼요. 여기에 sendBeacon이라는 API를 붙이면, 페이지가 사라지기 직전에 서버로 “나 지금 여기서 이탈해요”라는 신호를 쏠 수 있어요.
구현도 비교적 단순하고, 추가적인 서버 부하도 없어서 얼핏 보면 완벽한 해결책처럼 느껴지죠.
근데 문제가 있어요.
→ beforeunload는 ‘정상 종료’에만 작동해요.
beforeunload의 한계: 강제 종료는 못 잡아요
여기서 현실적인 문제가 등장해요.
실제 사용자 환경은 생각보다 훨씬 복잡해요. 노트북 배터리가 갑자기 방전되거나, 와이파이가 끊기거나, 크롬이 갑자기 뻗거나 — 이런 상황에서는 브라우저가 beforeunload 이벤트를 발생시킬 여유조차 없어요. 그냥 그대로 꺼져버리는 거거든요.
저도 솔직히 처음엔 “그게 얼마나 되겠어?”라고 생각했는데, 모바일 환경까지 포함하면 네트워크 단절이나 강제 종료 비율이 무시 못 할 수준이더라고요. 특히 인강 같은 경우 수강 시간이 길기 때문에 그사이 환경이 바뀔 가능성이 더 높아요.
결과적으로 beforeunload에만 의존하면, 이런 케이스에서 이탈 데이터가 아예 유실돼버려요. 서버 입장에서는 그 수강생이 영상을 끝까지 본 건지, 중간에 나간 건지 알 방법이 없는 거예요.
→ 네트워크 단절·강제 종료 상황에서는 beforeunload가 무력해져요.
그래서 등장하는 해결책: Heartbeat 방식
Heartbeat, 이름 그대로 “심장 박동”처럼 주기적으로 신호를 보내는 방식이에요.
영상이 재생되는 동안 8~10초마다 서버에 “나 아직 여기 있어요, 지금 영상 몇 초 지점 보고 있어요”라는 신호를 자동으로 쏘는 거예요. 이걸 video_progress 이벤트라고 부르기도 해요.
이 방식의 핵심은 서버가 마지막으로 받은 신호를 기준으로 이탈 시점을 추정한다는 거예요. 예를 들어 마지막 Heartbeat가 강의 4분 32초 지점에서 왔고, 그 이후로 신호가 없다면? 그 사람은 4분 32초~4분 42초 사이 어딘가에서 나간 거라고 볼 수 있는 거죠.
브라우저가 어떻게 종료되든, 네트워크가 끊기든 간에 마지막으로 살아있던 시점은 남아있어요. 이게 beforeunload와의 결정적인 차이예요.
→ Heartbeat는 ‘살아있음을 증명하는 신호’를 주기적으로 남겨서 이탈 시점을 역추적해요.
Heartbeat의 Trade-off: 공짜는 없어요
근데 해보니 이게 완벽한 방법도 아니더라고요. 현실적인 trade-off가 두 가지 있어요.
첫 번째는 데이터 볼륨 문제예요.
beforeunload는 이탈할 때 딱 한 번 데이터를 쏘는 반면, Heartbeat는 8초마다 계속 쏴요. 10분짜리 강의 하나를 보면 약 75번 이벤트가 발생하는 셈이에요. 수강생이 1만 명이면? 단순 계산으로 75만 개의 이벤트가 쌓이죠. 이게 하루, 한 달 쌓이면 파이프라인과 스토리지에 상당한 부담이 돼요.
두 번째는 Seek 어뷰징 문제예요.
영상의 탐색바를 빠르게 앞뒤로 왔다 갔다 하면(이걸 Seek라고 해요), 짧은 시간 안에 여러 구간에서 Heartbeat가 발생해요. 이걸 그대로 두면 “이 수강생이 강의를 성실히 수강했다”는 데이터가 왜곡될 수 있어요. 실제로는 3분만 봤는데 데이터상으로는 10분 전체를 본 것처럼 보일 수 있거든요.
이 두 가지 문제는 파이프라인 단에서 처리해야 해요. 예를 들어 Seek 이벤트를 필터링하거나, 동일 세션 내 중복 구간 이벤트를 정제하는 로직을 데이터 수집 파이프라인에 추가하는 방식이에요.
→ Heartbeat는 강력하지만, 데이터 볼륨 관리와 어뷰징 방지 로직이 반드시 따라와야 해요.
그래서 둘을 어떻게 써야 할까요?
솔직히 말하면 두 방법은 경쟁 관계가 아니에요.
실무에서는 Heartbeat를 기본으로 쓰되, beforeunload를 보조로 함께 쓰는 방식이 현실적인 선택이에요. Heartbeat로 주기적인 이탈 추정 데이터를 쌓고, 정상 종료 시에는 beforeunload로 정확한 이탈 시점을 한 번 더 찍어주는 거예요. 두 데이터를 비교하면 데이터 품질도 훨씬 높아지고요.
비전공자 취준생인 저 입장에서 이 개념이 흥미로웠던 건, 단순히 “영상 몇 초까지 봤냐”를 기록하는 문제가 아니라 데이터 유실을 막는 시스템 설계의 문제라는 걸 알게 됐기 때문이에요.
세션 타임아웃 관리도 결국 같은 맥락이에요. 서버가 “이 사용자가 아직 활성 상태인가”를 판단하는 기준을 어떻게 설계하느냐의 문제거든요. Heartbeat가 일정 시간 동안 들어오지 않으면 세션이 종료된 것으로 처리하는 방식도 이 연장선에 있어요.
→ beforeunload는 정상 종료용 마지막 신호, Heartbeat는 비정상 종료를 대비한 생존 신호예요. 둘은 함께 써야 제 역할을 해요.
마무리하며
처음에 이 주제 접했을 때 “이탈 시점 측정이 뭐가 어렵겠어”라고 생각했는데, 파고들수록 고려해야 할 게 꽤 많더라고요.
네트워크 환경, 데이터 볼륨, 어뷰징 가능성까지 — 하나의 이벤트 설계 안에 이 모든 게 들어가 있어요. 데이터 엔지니어가 단순히 데이터를 모으는 사람이 아니라, 데이터가 정확하게 수집될 수 있는 구조를 설계하는 사람이라는 걸 다시 한번 실감했어요.
여러분도 인강 듣다가 창 꺼버린 적 있으시죠? 그 순간에도 플랫폼 뒤에서는 꽤 복잡한 일이 일어나고 있을 거예요. 이제 그 맥락이 조금은 보이지 않나요?
오늘도 읽어주셔서 감사해요.