‘이제 될거야’ 보다는 수치로 확신할 수 있도록
매일 들여다 보진 않지만, 나는 데이터독을 자주 보는 편이다.
ResizeObserver loop completed with undelivered notifications. 에러가 미친듯이 찍혀서, 리사이즈 옵저버에 requestAnimationFrame 을 감싼 적도 있고,
Apollo Client 4v 으로 올리면서 수많은 Aborted 에러를 보고 useLazyQuery 반환값에 retain() 을 걸기도 했다.
이번 포스팅에서는 그런 단순한 상황이 아닌, 데이터독을 통해 발견하기 쉽지 않았던 잠재적 문제를 처음부터 끝까지 해결한 경험을 공유해보고자 한다.
개발자가 가장 싫어하는 상황은 ‘간헐적’, ‘가끔씩’, ‘재현 경로 없음’ 같은 말이 아닐까.
차트메모가 가끔씩 내용이 사라진다며, 수정 기록을 BE 개발자분들께 요청하는 스레드가 가끔씩 보이긴 했었다.
차트메모는 우리 팀 리더가 4년전에 직접 만든 커스텀 에디터로, 코드 복잡도가 가장 높은 모듈이다. 처음엔 네트워크 문제거나, 에디터의 잠재적인 엣지케이스 일 거라고 생각했다. (심각하게 느끼지 않은 이유는 정말 간헐적으로 들어오는 인입이었고, p_text_editor 라는 모듈을 둘러보며 코드를 파악할 시간이 없었기 때문이다. 물론 핑계다)
그러다 어느 시기 중 백스페이스와 엔터를 제외한 나머지 모든 keydown 이벤트를 무시해버리는 악랄한 크롬 확장프로그램을 쓰는 병원 인입이 있었던 시기라, 반강제로 에디터의 코어 로직을 뜯어본 참이었다.
그 시기에 다시한번 인입이 들어왔다.

병원의 워딩이 좀더 강하기도 했고, 마침 이번달 배포의 태스크는 어느정도 마무리 돼있던 상태라 차트메모를 제대로 확인해 보기로 했다.
그리고 텍스트 에디터는 죄가 없다(잘 만들어져있다) 라는걸 알게된 상태였기에, 뮤테이션을 쏘는 부분에 문제가 있을것 같았다.
차트메모는 저장 버튼이 따로 없는 방식이라, 디바운스 300ms 로 잘 최적화가 되어있는 상태였다.
사용자가 차트메모가 있는 차트 페이지를 300ms 전에 벗어난다? 물론 절대 불가능한건 아니지만, 상상하기가 조금 힘들었다.
그럼에도 가장 신경쓰이는 부분은 300ms 전에 벗어난다면 debounce 의 flush를 하지않는 부분 뿐이라서, 일단은 코드를 먼저 수정 하기보단 실제로 디바운스가 캔슬되는 상황일때 로그를 확인할 수 있게 해뒀다.
// 최초 렌더링시에는 console.warn 을 찍지 않게 하기위한 플래그
const isDebouncePending = useRef<boolean>(false);
// 300ms 로 lodash debounce가 걸려있는 뮤테이션 실행 함수
const debouncedUpdateChartMemo = useMemo(
() =>
debounce(() => {
isDebouncePending.current = false;
// ...
}, 300),
[...],
);
const handleUpdateChartMemo = useCallback(
(blocks: EditableBlockModel[]) => {
isDebouncePending.current = true;
debouncedUpdateChartMemo(blocks);
},
[debouncedUpdateChartMemo],
);
/** debounce cancel */
useEffect(() => {
return () => {
debouncedUpdateChartMemo.cancel();
if (isDebouncePending.current) {
console.warn('[p_chart_memo] multi-content: debounce cancel (메모 저장 전 취소되었습니다)');
isDebouncePending.current = false;
}
};
}, [debouncedUpdateChartMemo]);
정기배포가 얼마 안남은 시점이었어서, 로그를 빠르게 배포하고 prod 배포 후 CS 분들께 상황을 설명 드렸다.
prod 에서 로그 수집될수있게 배포되었습니다.
당장 해결되진 않았지만 틈틈히 지표 생긴게있는지 확인하면서 지켜보겠습니다.
솔직히 말해서 큰 기대를 하진 않았었다.
‘웹을 좀 특이하게 사용하시는 분일까?’ 라는 생각을 가지고 있었는데, 배포 후 캔슬 되는 수치가 생각보다 높았다.
무려 하루 20~30번 정도의 횟수였다.
url이 특이했는데, 내가 예상한 차트 페이지의 url이 아닌 플래너 url 이 대부분이었다.
여기서 알아차렸다. 유저는 생각보다 300ms 안에 차트메모의 debounce를 취소시키는게 쉽다는 사실을.
플래너에서 드로워로 열리는 차트 화면이 있는데, ESC 키로 해당 드로워가 닫힌다.
의사/원장 분들은 주로 (드로워/모달이 아닌) 실제 차트 url 에서 메모를 기록한다.
하지만 다른 의료 종사자분들. 예를 들어 상담 실장님이라던지, 데스크에서 안내를 도와주시는 분들은 보통 플래너에서 차트를 드로워로 열고, 필요시 메모를 기록할 것이다.
이게 차트메모가 유독 많이 취소되는 이유였다.
원인을 알았으니, 해결할 일만 남았다.
lodash의 debounce는 cancel()과 함께 flush()라는 메서드를 제공한다. cancel()이 대기 중인 호출을 버리는 것이라면, flush()는 대기 중인 호출을 즉시 실행하는 것이다.
useEffect(() => {
return () => {
debouncedUpdateChartMemo.flush();
};
}, [debouncedUpdateChartMemo]);컴포넌트가 unmount 되기 직전, 아직 300ms를 채우지 못한 마지막 입력까지 확실히 저장하면 드로워처럼 빠르게 닫힐 수 있는 환경에서도 마지막 입력까지 요청하게 된다.
고치는 김에, 차트메모의 debounce time이 300ms, 진료기록(medical-record)의 debounce time이 500ms로 제각각이었다. 같은 성격의 자동 저장인데 굳이 다를 이유가 없어서, INPUT_DEBOUNCED_TIME이라는 상수로 통합하고 500ms로 맞췄다.
이제 더이상 저장이 취소될 일이 없으므로 500ms 인 편이 훨씬 네트워크 부하도 적을 것이다.

감사하게도 QA 분들이 허락해주셔서 바로 다음 배포에 태울 수 있었고, 이렇게 차트메모가 누락되는 이슈는 해결되었다 :)
배포 후 꽤 시간이 지났는데, 이제 더이상 차트메모 누락에 대한 CS 인입은 없다.
처음엔 “재현 안됩니다” 같은 요소들로 넘겼었겠지만, 그건 “(차트 페이지에서는 웬만하면) 재현 안됩니다” 의 줄임말 이었다.
데이터독 같은 도구는 이 줄임말을 늘리게 해준다.
url 부터, 해당 로그의 빈도 수 등 힌트가 될 요소가 참 많다.
개인적으로 아쉬운건, 이슈를 해결한 시점의 블로그 글인데 데이터독 자료를 백업을 좀 안해뒀어서 이미지를 많이 첨부하지 못했던 점이다.
데이터독의 어트리뷰트, url 등 내가 힌트로 얻은 정보들을 좀더 꼼꼼히 기록했어야했는데, 데이터독 구독 갱신으로 이전 데이터가 날아가버려 아쉽다.
어쨌든 이번 데이터독 활용 사례는 단순 ‘로그가 비정상적으로 많이 쌓이는 부분을 해결’ 한 것이 아니라
능동적인 데이터독의 사용 사례였다.
개발을 하다보면 ‘간헐적’ 이라는 이슈가 참 많다.
감으로 ‘여기가 의심되는데 고쳤으니 아마 될거야’ 라고 생각하면 그건 감으로 끝난다.
데이터독은 이런 상황에서 약간의 시간과 노력을 첨가하면 그 감을 수치라는 확신으로 전환해준다.
모니터링 도구는 버그를 잡기도 하지만, 내가 모르는 세상을 알게 해주는 도구이기도 하다.