Published on

Chrome INP 급락? Long Task 5분 진단법

Authors

서버 지표는 멀쩡한데, Chrome UX Report나 Search Console에서 INP가 갑자기 급락하는 경우가 있습니다. 이런 상황의 1순위 원인은 거의 항상 Main thread 를 길게 점유하는 Long Task 입니다. 클릭이나 키 입력 같은 상호작용 이벤트가 들어와도 메인 스레드가 바쁘면 브라우저는 입력을 처리하지 못하고, 그 지연이 INP로 그대로 반영됩니다.

이 글은 "5분 안에" 원인을 특정하는 데 초점을 맞춥니다. 즉, 정교한 실험 설계보다도 DevTools로 빠르게 범인을 찍고, 바로 적용 가능한 개선 방향을 뽑아내는 흐름입니다.

INP와 Long Task를 1분만에 정리

  • INP 는 페이지 수명 동안 발생한 상호작용들 중 "가장 나쁜 지연"을 대표값으로 잡아 사용자 경험을 평가합니다.
  • Long Task 는 메인 스레드에서 대략 50ms 이상 연속 실행되는 작업을 의미합니다.
  • Long Task가 많거나 길면
    • 입력 이벤트가 큐에 쌓임
    • 핸들러 실행이 늦어짐
    • 렌더링 커밋도 늦어짐
    • 결과적으로 INP가 악화됩니다

INP가 급락했다는 건, 최근 배포나 트래픽 변화 이후 "메인 스레드가 더 자주, 더 오래 막히는" 일이 생겼다는 뜻일 가능성이 큽니다.

5분 진단 플로우: DevTools로 범인 찾기

아래 순서대로 하면, 대부분의 케이스에서 5분 내에 "어느 코드가" Long Task를 만들었는지까지 좁힐 수 있습니다.

1) DevTools Performance로 재현 시나리오를 고정

  1. Chrome DevTools 열기
  2. Performance
  3. Reload 또는 Record 후, INP가 나빠지는 상호작용을 딱 1회 수행
    • 예: 검색 버튼 클릭 1회
    • 예: 필터 토글 1회
  4. 녹화 종료

팁: 상호작용을 여러 번 하면 분석이 복잡해집니다. "한 번만" 수행하는 게 핵심입니다.

2) Main thread에서 Long Task를 먼저 찾기

Performance 결과에서 다음을 확인합니다.

  • Main 트랙에서 길게 이어진 바
  • 상단의 Summary 또는 Bottom-up 에서 Scripting 비중이 큰지

Long Task가 보이면 그 구간을 드래그로 확대하고, 해당 구간의 호출 스택을 확인합니다.

3) Bottom-up으로 "가장 비싼 함수"를 바로 찍기

Bottom-up 뷰는 시간을 많이 먹은 함수부터 보여줍니다.

  • Self time 이 큰 함수: 그 함수 자체가 무거움
  • Total time 이 큰 함수: 하위 호출까지 포함해 무거움

여기서 파일명과 라인으로 점프해 "무거운 루프", "과도한 JSON 파싱", "정렬", "레이아웃 스래싱" 같은 패턴을 찾습니다.

4) Event Log로 입력 지연 구간을 확인

Performance에서 Interactions 또는 이벤트 관련 트랙이 보이면, 클릭 이벤트가 발생한 시점과 실제 핸들러 실행 시점 사이가 벌어져 있는지 확인합니다.

  • 클릭은 발생했는데
  • 메인 스레드가 이미 다른 작업으로 막혀서
  • 핸들러가 뒤늦게 실행

이 패턴이면 INP 악화와 직결입니다.

5) Rendering 문제인지 Scripting 문제인지 구분

Long Task가 항상 JS 때문은 아닙니다.

  • Scripting 이 크면: JS 실행, 번들 초기화, 프레임워크 렌더, 파서 작업
  • Rendering 또는 Painting 이 크면: 스타일 계산, 레이아웃, 페인트 과다

구분이 되면 해결책이 달라집니다.

가장 흔한 Long Task 원인 6가지와 즉시 확인법

1) 초기 로드에 과도한 동기 실행

대표 예시는 다음입니다.

  • 초기 상태 계산을 한 번에 수행
  • 큰 배열 정렬, 필터링, 그룹핑
  • 대용량 JSON.parse 를 메인 스레드에서 즉시 실행

확인법:

  • Performance에서 Reload 직후 Scripting 이 길게 이어짐

대응:

  • 초기 계산을 쪼개거나, 유휴 시간에 실행
  • 필요한 순간까지 지연 로딩

2) 프레임워크 리렌더 폭발

React, Vue, Svelte 등에서 상태 업데이트가 연쇄적으로 발생하면 Long Task가 생깁니다.

확인법:

  • Performance에서 특정 상호작용 이후 Function Call 이 길게 이어지고, 컴포넌트 렌더 관련 함수가 다수 등장

대응:

  • 불필요한 상태 변경 줄이기
  • 메모이제이션
  • 큰 리스트는 가상 스크롤

3) 레이아웃 스래싱

offsetHeight 같은 레이아웃 값을 읽고, 곧바로 스타일을 쓰는 작업을 반복하면 레이아웃이 강제로 여러 번 발생합니다.

확인법:

  • Performance에 Recalculate Style, Layout 이 반복적으로 등장

대응:

  • 읽기와 쓰기를 분리
  • 배치 업데이트
  • requestAnimationFrame 활용

4) 입력 이벤트 핸들러가 무거움

click 이나 input 이벤트 핸들러에서 무거운 일을 하면 INP가 바로 악화됩니다.

확인법:

  • 이벤트 핸들러가 포함된 Long Task

대응:

  • 핸들러에서는 최소 작업만
  • 무거운 작업은 분리해서 비동기로

5) 서드파티 스크립트

태그 매니저, A/B 테스트, 광고, 분석 스크립트가 Long Task를 만들 수 있습니다.

확인법:

  • 콜스택에 외부 도메인 스크립트가 보임

대응:

  • 로딩 조건 조정
  • 지연 로딩
  • 필요 없는 태그 제거

6) 번들 크기 증가로 파싱과 실행 비용 증가

번들이 커지면 파싱, 컴파일, 실행 시간이 늘어 Long Task가 발생합니다.

확인법:

  • Evaluate Script 또는 Compile Script 비중 증가

대응:

  • 코드 스플리팅
  • 라우트 단위 지연 로딩
  • 폴리필 최소화

5분 안에 적용 가능한 "응급 처치" 코드 패턴

아래 패턴들은 근본 해결 전에도 INP를 즉시 완화하는 데 도움이 됩니다.

1) 무거운 작업을 쪼개서 이벤트 루프에 양보하기

setTimeout 을 이용해 작업을 청크로 나눕니다.

function chunkedWork<T>(items: T[], chunkSize: number, work: (x: T) => void) {
  let i = 0;

  function runChunk() {
    const end = Math.min(i + chunkSize, items.length);
    for (; i < end; i++) work(items[i]);

    if (i < items.length) {
      setTimeout(runChunk, 0);
    }
  }

  runChunk();
}

주의: setTimeout 은 타이밍이 거칠지만, "메인 스레드 독점"을 끊는 데는 빠르게 효과가 납니다.

2) requestAnimationFrame 으로 렌더링 타이밍에 맞추기

DOM 업데이트를 프레임 경계로 모아 레이아웃/페인트 폭발을 줄입니다.

let scheduled = false;

function scheduleDomUpdate(updateFn) {
  if (scheduled) return;
  scheduled = true;

  requestAnimationFrame(() => {
    scheduled = false;
    updateFn();
  });
}

3) 사용자 입력 이벤트에서 "즉시 반응" 먼저 만들기

클릭 직후 UI 피드백을 먼저 주고, 무거운 작업은 뒤로 미룹니다.

button.addEventListener('click', () => {
  button.disabled = true;
  button.textContent = '처리 중...';

  setTimeout(() => {
    // 무거운 작업은 여기서 수행
    doHeavyWork();

    button.disabled = false;
    button.textContent = '완료';
  }, 0);
});

이 패턴은 INP 관점에서 "입력에 대한 즉각적인 반응"을 확보하는 데 유리합니다.

DevTools로 Long Task를 더 정확히 해부하는 팁

Performance 설정에서 스로틀링을 걸어 재현률 올리기

로컬 머신이 너무 빠르면 문제가 안 보일 수 있습니다.

  • Performance 또는 Network 에서 CPU 스로틀링 적용
  • 실제 사용자 환경에 가까운 조건에서 재측정

Long Task 구간의 콜스택에서 "내 코드"와 "외부 코드"를 분리

  • 내 코드가 시작되는 첫 프레임을 찾고
  • 그 함수가 왜 호출되었는지 상위 호출을 확인

서드파티가 원인이라면, "내 코드에서 서드파티를 호출하는 지점"이 결국 제어 포인트입니다.

페이지 전역 이벤트 리스너 점검

scroll, mousemove, touchmove 같은 이벤트에 무거운 핸들러가 걸려 있으면 상호작용이 전반적으로 나빠집니다.

  • 불필요한 리스너 제거
  • 패시브 리스너 고려

코드에서는 다음처럼 패시브 옵션을 명시할 수 있습니다.

window.addEventListener('touchmove', onTouchMove, { passive: true });

재발 방지: 배포 후 INP 급락을 막는 체크리스트

  1. 신규 기능이 추가한 계산량이 있는가
  2. 리스트 렌더링이 커졌는가
  3. 서드파티 태그가 늘었는가
  4. 번들 크기가 증가했는가
  5. 입력 이벤트 핸들러가 무거워졌는가
  6. 스타일/레이아웃 변경이 잦아졌는가

그리고 가능하면 Long Task 를 런타임에서 수집해, "어느 페이지에서", "어느 상호작용에서" 터지는지 관측하는 체계를 갖추는 게 좋습니다.

Long Task 수집용 최소 스니펫: PerformanceObserver

브라우저에서 Long Task를 관측해 콘솔에 남기는 최소 코드입니다.

(function observeLongTasks() {
  if (!('PerformanceObserver' in window)) return;

  try {
    const po = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        // entry.duration 이 대략 50ms 이상이면 Long Task로 잡히는 경우가 많습니다.
        console.log('[LongTask]', {
          name: entry.name,
          duration: entry.duration,
          startTime: entry.startTime,
        });
      }
    });

    po.observe({ entryTypes: ['longtask'] });
  } catch (e) {
    // 일부 환경에서는 longtask entryTypes 미지원일 수 있습니다.
  }
})();

운영에서 그대로 콘솔 출력만 하면 의미가 없으니, 실제로는 샘플링해서 로그 수집 또는 RUM으로 보내는 형태로 발전시킵니다.

원인이 "서버" 같아 보여도, INP는 대개 프론트에서 터진다

INP 급락 시 서버를 먼저 의심하는 경우가 많지만, INP는 기본적으로 브라우저 메인 스레드 지연의 영향이 큽니다. 물론 API 지연이 길어지면 사용자가 클릭을 반복해 상호작용 패턴이 나빠질 수는 있지만, INP 자체는 "입력 처리와 렌더링"의 문제로 수렴하는 경우가 많습니다.

다만 백엔드 지연이 겹치면 체감은 더 나빠지므로, 서버 타임아웃이나 콜드 스타트도 함께 점검하는 게 좋습니다. 관련해서는 GCP Cloud Run 503·Cold Start 원인과 튜닝 글도 같이 참고하면 전체 병목을 더 빨리 분리할 수 있습니다.

마무리: 5분 진단의 핵심은 "한 번의 상호작용"과 "Bottom-up"이다

  • 상호작용을 한 번만 재현하고
  • Performance에서 Long Task 구간을 잡은 뒤
  • Bottom-up 에서 가장 비싼 함수를 찾으면
  • INP 급락의 원인을 빠르게 특정할 수 있습니다

그 다음은 원인 유형에 따라

  • 작업 쪼개기
  • 렌더링 최적화
  • 레이아웃 스래싱 제거
  • 서드파티 로딩 전략 수정
  • 번들 분할

로 이어가면 됩니다.

INP는 "가끔 느린" 것이 아니라 "가장 나쁜 한 번"이 발목을 잡는 지표입니다. 그러니 Long Task를 없애는 것만으로도 체감과 지표가 동시에 개선되는 경우가 많습니다.