Skip to Content
Datadog RUM 기반 서비스 품질 개선 (2026-04-30)

Datadog RUM 기반 서비스 품질 개선


배경

Datadog RUM 30일(2026-03-30 ~ 2026-04-30) 데이터를 분석하여, 전체 유니크 세션 약 195K 기준으로 사용하지 않는 기능 식별, 에러 핫스팟 해결, 성능 개선, 사용성 개선을 진행했습니다.

분석에 사용한 Datadog 쿼리

용도쿼리
페이지별 뷰 수aggregate_rum_events@type:view group by @view.url_path
유니크 세션aggregate_rum_eventsCARDINALITY(@session.id) group by @view.url_path
성능 (P50/P90)aggregate_rum_events@type:view @view.loading_time:>0 P50/P90 group by @view.url_path
에러 수aggregate_rum_events@type:error group by @view.url_path
에러 패턴aggregate_rum_events@type:error @view.url_path:/knowledge-space group by @error.message
액션 빈도aggregate_rum_events@type:action group by @action.name

Phase 1: 에러 핫스팟 해결

1-1. touchmove intervention 에러 필터링

문제: 지식공간 에러율 68%, 문서보관함 에러율 19%로 보이지만 89~93%가 브라우저 intervention 경고

"Ignored attempt to cancel a touchmove event with cancelable=false"

원인: 스크롤 중 preventDefault() 호출 시 브라우저가 남기는 passive 경고. 실제 사용자 영향 없음.

수정:

  • packages/logger/provider/client/ClientLogger.tsx — Datadog RUM beforeSend에서 cancelable=falsenavigator.vibrate 에러 필터링
  • packages/ui/utils/components/PreventScrollWhenSlide.tsx — touchmove 이벤트에 passive: false 명시

검증 방법: 배포 후 Datadog RUM에서 /knowledge-space, /document-box 에러 수 확인


1-2. React Hydration Mismatch (React error #418)

문제: 커뮤니티 채팅에서 1,177건/7일, 문서보관함에서 50건/7일

원인 분석:

  1. InputWrapperForIOSisIOS() 체크가 렌더 타임에 실행되어 서버(false) vs iOS 클라이언트(true) HTML 구조 불일치
  2. ChatroomMessageInputisIOS() 조건부로 hidden input 렌더링
  3. NoListingCafeSectionnew Date().toJSON()이 모듈 스코프에서 평가되어 SSR/클라이언트 간 불일치

수정:

  • packages/ui/utils/components/InputWrapperForIOS.tsxisIOS() 체크를 useEffect 후로 지연 (hydration 완료 후 iOS 분기)
  • ChatroomMessageInput/index.tsx — hidden input을 항상 렌더링 (tabIndex={-1}, aria-hidden)
  • NoListingCafeSection.tsxnew Date().toJSON() → 고정 날짜 문자열 '2026-04-01T12:00:00.000Z'

검증 방법: iOS 기기에서 채팅 진입 시 콘솔에 hydration 경고 없음 확인


1-3. Sendbird 연결 에러 안정화

문제 (7일 기준):

  • Command received no ack — 185건 (Sendbird 명령 타임아웃)
  • Connection is required — 34건 (연결 끊긴 상태에서 작업 시도)

근본 원인:

  1. isConnected()currentUser !== null만 체크 → 소켓이 죽어도 연결된 것으로 판단
  2. 자동 재연결 로직 없음
  3. connect() 에러를 console.log로 삼킴 → 연결 실패해도 isConnected=true

수정:

  • packages/chat/libs/classes/chat.ts:
    • isConnected(): connectionState === 'OPEN' 체크 추가
    • connect(): try/catch 제거하여 에러 전파
    • addConnectionHandler() / removeConnectionHandler() 메서드 추가
  • packages/chat-react/contexts/ChatConnectContext.tsx:
    • Sendbird ConnectionHandler 등록 (onReconnectStarted/Succeeded/Failed)
    • onReconnectFailed 시 수동 connectIfNeeded() 재시도
    • connect() 실패 시 catch에서 setIsConnected(false)

검증 방법: 모바일에서 네트워크 끊김 → 재연결 시 채팅 자동 복구 확인


1-4. 비즈니스 로직 에러 처리

문제: QUIZ_ALREADY_SUBMITTED 104건/7일 (지식공간)

원인: handleAnswer에서 mutation 진행 중 중복 클릭 방지 없음

수정:

  • knowledge-space/page.tsxquizSolveMutation.isPending + quiz?.response != null 가드 추가

Phase 2: 성능 개선

2-1. 루트 페이지 API 병렬화

문제: P90 5.9초, P50 2.2초. 순차 API 호출 체인:

getMe() → getMyOnboardingStatus() → getHomeAccess() → getFirstUncertifiedListing()

수정 (apps/app/src/app/page.tsx):

// Before: 순차 호출 (4 round trips) const user = await UserAPI.getMe(); const certificationStatus = await UserAPI.getMyOnboardingStatus(); const homeAccess = await UserAPI.getHomeAccess(); const firstUncertifiedListing = await getFirstUncertifiedListing(); // After: 병렬 호출 (2 round trips) const [user, certificationStatus] = await Promise.all([ UserAPI.getMe(), UserAPI.getMyOnboardingStatus().catch(() => null), ]); const [homeAccess, firstUncertifiedListing] = await Promise.all([ UserAPI.getHomeAccess(), getFirstUncertifiedListing(), ]);

예상 효과: ~300-400ms 절감 (API round trip 2개 절약)


2-2. 채팅 레이아웃 API 병렬화

문제: getChatByUrl이 단독 실행 후 → 6개 API 순차 배치. cafeId 의존성 때문에 2단계 waterfall.

수정 (community/(chat-server-connect)/[chatUrl]/layout.tsx):

// Before: getChatByUrl 단독 → 6개 API const [chat] = await Promise.all([fetchAPIQuery(ChatAPI.getChatByUrl, {chatUrl})]); const cafeId = chat.cafe.id; await Promise.all([...6개 API...]); // After: 비의존 4개를 getChatByUrl과 병렬 → cafeId 의존 2개만 2차 배치 const [chat] = await Promise.all([ fetchAPIQuery(ChatAPI.getChatByUrl, {chatUrl}), fetchAPIQuery(ChatAPI.getChatParticipantsList, {chatUrl}), // cafeId 불필요 fetchAPIQuery(UserAPI.getMyProfile, {chatUrl}), // cafeId 불필요 fetchAPIQuery(ChatAPI.getMyChatByUrl, {chatUrl}), // cafeId 불필요 fetchAPIQuery(UserAPI.getMyChatList), // cafeId 불필요 ]); const cafeId = chat.cafe.id; await Promise.all([ fetchAPIQuery(CafeAPI.getCafeActiveNotice, {cafeId}), fetchAPIQuery(CafeAPI.getUnreadPostsCount, {cafeId}), ]);

예상 효과: ~300ms 절감 (1차 배치에서 4개 API 동시 실행)


2-3. /vote 레거시 서버 리다이렉트

문제: P90 4.7초. 클라이언트 useEffect → router.push로 전체 JS 로딩 후 리다이렉트.

수정 (apps/app/src/app/vote/page.tsx):

// Before: 클라이언트 리다이렉트 'use client'; useEffect(() => { router.push(getRoutePath('home')); }, []); // After: 서버 리다이렉트 import { redirect } from 'next/navigation'; redirect(getRoutePath('home'));

예상 효과: P90 4.7초 → ~0.5초 (클라이언트 JS 로딩 생략)


Phase 3: 사용성 개선

3-1. 서명 UX 개선

문제: “다시 서명하기” 155,844회 vs “다음으로” 171,406회 = 47.6% 재시도율

원인 분석:

  1. hasSignaturestartDrawing 즉시 true → 아주 작은 터치도 서명으로 인정
  2. “다시 서명하기” 버튼이 서명 후에만 표시 → 사용자가 처음에 버튼 존재를 모름
  3. 이름 워터마크가 서명 시작 시 사라짐 → 가이드 없이 서명

수정:

  • _hooks/useSignatureCanvas.ts:
    • strokeLengthRef로 스트로크 길이 추적
    • 최소 30px 이상 그려야 hasSignature=true (실수 터치 방지)
    • hasSignature 설정을 startDrawingstopDrawing으로 이동
  • _components/SignatureCanvas.tsx:
    • “다시 서명하기” 버튼 항상 표시 (disabled={!hasSignature})
    • 이름 워터마크를 서명 중에도 유지 (“여기에 서명해주세요” 텍스트만 숨김)
    • onSignatureChangeuseEffecthasSignature 변경 감지

검증 방법: 배포 후 “다시 서명하기” / “다음으로” 비율 변화 추적


3-2. 채팅↔카페 탭 전환 최적화

문제: 탭 전환 2,530회/일, 매번 34초 full page reload

원인: router.replace()로 전체 라우트 변경 → 서버 RSC 재렌더링 + API 재호출

수정 (CommunityHeader.tsx):

// 다른 탭 데이터를 미리 로드 useEffect(() => { if (currentPage === 'chat') { router.prefetch(cafePath); } else { router.prefetch(chatPath); } }, [chatUrl, currentPage, router]);

예상 효과: Next.js가 다른 탭의 RSC payload를 미리 캐시 → 전환 시 1~2초로 감소

향후 개선 방향: parallel routes (@chat, @cafe) 구조로 전환하면 <500ms 가능


3-3. 세무상담 노출 강화

문제: 유니크 세션 2,102 (전체의 1.1%). 최근 릴리즈된 기능이지만 홈에서 8번째 위치.

수정 (Home.tsx):

Before: HomeDocuments → HomeAdvertisement → HomeQuiz → HomeCommunityList → HomeShortcut After: HomeDocuments → HomeShortcut → HomeAdvertisement → HomeQuiz → HomeCommunityList

예상 효과: 스크롤 없이 바로가기(세무상담 포함) 노출 → 유니크 세션 증가


검증 계획

배포 후 1~2주 뒤 아래 Datadog 쿼리로 before/after 비교:

지표Before (baseline)쿼리
지식공간 에러율68.3%@type:error @view.url_path:/knowledge-space
문서보관함 에러율18.9%@type:error @view.url_path:/document-box
채팅 hydration 에러1,177/주@type:error @error.message:*418* @view.url_path:/community/*/chat
Sendbird no ack185/주@type:error @error.message:*no ack*
루트 P905.86s@type:view @view.url_path:/ P90 @view.loading_time
채팅 P503.55s@type:view @view.url_path:/community/*/chat P50 @view.loading_time
투표 P904.70s@type:view @view.url_path:/vote P90 @view.loading_time
서명 재시도율47.6%@type:action @action.name:*서명* 비율
세무상담 유니크 세션2,102@type:view @view.url_path:/tax-chat CARDINALITY @session.id

변경된 파일 목록

파일Phase변경 내용
packages/logger/provider/client/ClientLogger.tsx1-1RUM beforeSend 에러 필터링
packages/ui/utils/components/PreventScrollWhenSlide.tsx1-1passive 옵션 명시
packages/ui/utils/components/InputWrapperForIOS.tsx1-2isIOS() hydration 후 지연
ChatroomMessageInput/index.tsx1-2hidden input 항상 렌더링
NoListingCafeSection.tsx1-2목업 날짜 고정
packages/chat/libs/classes/chat.ts1-3isConnected 소켓 체크, ConnectionHandler
packages/chat-react/contexts/ChatConnectContext.tsx1-3자동 재연결, 에러 핸들링
knowledge-space/page.tsx1-4퀴즈 중복 제출 방지
apps/app/src/app/page.tsx2-1API 병렬화
community/(chat-server-connect)/[chatUrl]/layout.tsx2-2API 병렬화
apps/app/src/app/vote/page.tsx2-3서버 리다이렉트
vote/[voteId]/_hooks/useSignatureCanvas.ts3-1최소 스트로크 검증
vote/[voteId]/_components/SignatureCanvas.tsx3-1버튼 항상 표시, 워터마크 유지
CommunityHeader.tsx3-2router.prefetch 추가
home/v2/Home.tsx3-3HomeShortcut 위치 상향
rum-baseline-2026-04-30.mdx-개선 전 기준 데이터
Last updated on