와탭 모니터링
2025-10-14
24억 건 실시간 로그 모니터링 UX 구현기

안녕하세요 AI 네이티브 옵저버빌리티 플랫폼, 와탭랩스입니다. 와탭랩스는 웹 기반으로 다양한 모니터링 서비스를 제공하고 있습니다. 그중 로그 모니터링은 하루 최대 24억 건의 데이터를 수집하고 조회할 수 있을 만큼 대규모 데이터를 처리합니다.

이번 글에서는 애플리케이션, 서버, 데이터베이스, 쿠버네티스, 네트워크 장비 등에서 수집된 로그를 실시간으로 확인할 수 있는 ‘라이브 테일(Live Tail)’ 화면의 UX를 개선하기 위해, 우리가 정의했던 문제와 그 해결 과정을 공유하려고 합니다.

문제 정의

위 영상을 보셨을 때 어떤 생각이 드셨나요? 몇몇 고객분들이 아래와 같은 문제들을 제기하였고, 저 또한 이 문제에 공감했습니다.

  1. 분석해야 할 로그를 발견했을 때 자동 스크롤을 멈춰서 보고싶어요.
  2. 데이터 갱신이 되고 있는지, 데이터 갱신되는 방향이 어디인지 알기 어려워요.

목표

로그 모니터링 고객은 터미널에서 tail -f 명령어를 입력하여 사용하는 UX에 익숙하여 이를 참고하기로 결정했습니다.

  1. 터미널 콘솔 스크롤 UX 구현
    - 새로운 데이터가 들어오면 아래에 계속 쌓여요.
    - 스크롤 바 위치가 밑바닥에 있고, 새로운 데이터가 들어오면 자동으로 스크롤 바를 맨 아래에 이동시켜요.
    - 사용자가 스크롤을 움직이면 자동 스크롤을 멈추고 스크롤 위치를 보존해요. 이때, 데이터는 지속적으로 최신화해요.
  2. 실시간으로 로그가 들어올 때 스크롤 애니메이션으로 실시간 로그가 들어온다는 UX 구현

터미널 콘솔 스크롤 UX 분석

  1. 스크롤을 맨 아래에 두면 실시간으로 로그가 업데이트되고 있어요.
  2. 스크롤을 움직이면 해당 위치의 데이터를 그대로 보존하면서 볼 수 있어요.

이러한 동작이 가능한 이유는 터미널과 브라우저의 로그 처리 방식의 차이에 있습니다.터미널은 로그를 GC(Garbage Collection) 하지 않고 단순히 로그 파일에 계속 쌓는 구조를 갖고 있습니다.

반면, 와탭의 브라우저 기반 로그 모니터링 서비스는 서버로부터 전달받은 로그 JSON 데이터를 무한정 저장할 경우 OOM(Out of Memory) 이 발생할 수 있기 때문에, 최대 2만 개까지만 로그를 유지하고 그 이상은 자동으로 GC하여 메모리를 관리합니다.

제약 사항

  • 브라우저 OOM을 고려하여 GC가 필요
  • 동적 height virtualized 리스트 고려

구현

요약

사용자가 과거 데이터를 확인하기 위해 스크롤을 위로 올리면 자동 스크롤이 일시 중지되어 로그를 안정적으로 읽을 수 있습니다. 이후 사용자가 스크롤을 최하단으로 내리면, 자동으로 최신 로그 스트리밍이 다시 시작됩니다.

  1. 새로운 데이터 추가 시 자동으로 스크롤이 최하단으로 이동
  2. 사용자 개입 감지: 사용자가 스크롤 조작을 감지해 수동 모드로 전환
  3. 사용자가 최하단까지 스크롤하면 자동 스크롤 모드로 변환

플로우

스크롤 상태

export type ScrollStatus = 'auto' | 'manual';

문제 1: 자동 스크롤, 수동 스크롤 구분 이슈

  • 바닥으로 자동 스크롤했을 때 발생하는 이벤트: scroll
  • 사용자가 scroll을 움직였다는 것을 감지하는 이벤트: scroll

동일한 브라우저 이벤트를 사용하고 있어, 구분이 어려웠습니다.

해결 방안

  • 바닥으로 자동 스크롤했을 때 발생하는 이벤트: scroll
  • 사용자가 scroll을 움직였다는 것을 감지하는 이벤트: mousedown 및 touch(스크롤 바 영역 여부 판단), wheel
const handleMouseDown = (e) => {
  const isScrollbar = e.offsetX > e.currentTarget.clientWidth;
  if (!isScrollbar) {
    return;
  }
  updateScrollStatus('manual');
  stopScrollAnimation();
};


문제 2: 동적 Height Virtualized 리스트의 자동 스크롤 이슈

와탭의 로그 데이터는 하루 최대 24억 건을 수용할 수 있기에 virtualized 기법을 사용하여 화면에 보이는 요소만 렌더링하고 있습니다.

로그 데이터는 데이터마다 컨텐츠의 크기가 다르다는 특성이 있습니다.

일반적인 정적 높이 가상화 리스트에서는 element.scrollTop = element.scrollHeight - element.clientHeight 로 최하단 이동이 가능합니다. 하지만, 동적 높이의 가상화 리스트에서는 DOM 요소들이 동적으로 생성/제거되고 높이가 실시간으로 변경되기 때문에 이 방법이 제대로 작동하지 않습니다.

그리하여 rAF를 활용하여 auto 모드일 때, 지속적으로 바닥으로 자동 스크롤하도록 설계했습니다.

function startContinuousScroll() {
  function continuousScrollLoop() {
    if (scrollMode !== 'auto' || isUserInteracting) {
      return;
    }

    // 매 프레임마다 최신 scrollHeight 계산
    const maxScrollTop = element.scrollHeight - element.clientHeight;
    element.scrollTop = maxScrollTop;

    // 다음 프레임에서 다시 실행
    requestAnimationFrame(() => {
      continuousScrollLoop();
    });
  }

  requestAnimationFrame(continuousScrollLoop);
}

문제 3: GC 알고리즘 설계

실시간 로그 모니터링 시스템에서는 끊임없이 새로운 로그 데이터가 추가됩니다. 하지만 브라우저의 메모리는 무한하지 않기 때문에, 사용자가 현재 보고 있는 로그는 보존하면서도 메모리를 효율적으로 관리하는 가비지 컬렉션(GC) 알고리즘이 필요합니다.

하지만 터미널의 UX를 제공하기 위해서는 실시간으로 로그가 업데이트되면서, 애니메이션이 동작하면서, 자동 스크롤을 멈추었다면 멈춰서 해당 로그를 볼 수 있게 해야합니다.

자동 스크롤을 멈춘 경우: 스크롤 위치가 최하단이 아닌 경우

고민

사용자가 자동 스크롤을 멈추었다면 새로운 로그 데이터가 갱신되면서, 메모리 최적화를 위해 GC를 하면서 보고 있던 데이터가 유지되도록 하는 방법이 고민이었습니다.

기존 방법대로 항상 최신 로그 데이터 2만개를 유지하면 사용자가 자동 스크롤을 멈춘 위치의 화면을 보고 있을 때, 새로운 데이터로 갱신되는 현상이 생깁니다.

설계

  1. 사용자가 움직인 스크롤 위치를 보존하는 방법
    - 사용자가 보고 있는 화면의 viewport 시작 이전의 데이터들은 모두 유지
  2. 최신 데이터를 갱신하는 방법
    - 사용자가 보고 있는 화면의 viewport 끝 이후의 데이터들을 모두 최신 데이터로 교체

알고리즘

  1. 스크롤 멈춘 위치의 start, end index 값을 구하기
  2. start index 이전의 데이터는 모두 유지
  3. end index + N → 2만개가 되도록 N에 해당하는 데이터만 실시간으로 업데이트
  4. 사용자가 스크롤을 바닥으로 이동하면 최신 데이터 2만개로 갱신

예시

  • 전체 데이터: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] (15개)
  • 최대 데이터 개수: 10개
  • 현재 뷰포트: 인덱스 1 - 2
  • 결과: [0, 1, 2, 8, 9, 10, 11, 12, 13, 14] (10개)
  • 제거: [3, 4, 5, 6, 7] (5개)

문제 4: 제한된 데이터 개수로 인해 애니메이션이 발생하지 않는 이슈

새로운 데이터가 추가되어야 즉, 이동할 스크롤이 있어야 애니메이션이 발생합니다. 2만개가 되었을 때, 스크롤 위치도 바닥에 있으면 애니메이션이 동작하지 않습니다.

현재 데이터 개수가 15,000개이면 버퍼 추가 알고리즘을 통해 동적으로 max 데이터 크기가 늘어나면서 최대 2만개까지 데이터를 유지하도록 설계했습니다.

15,000 → 15,500 → … → 20,000 → 15,000 → 15,500 → …

문제 5: 애니메이션 구현

구현 플로우

  1. 시간 기반 진행률: elapsed / duration로 0~1 사이 값 계산
  2. Easing 함수 적용: 진행률을 곡선으로 변환하여 자연스러운 움직임
  3. 위치 보간: 시작위치 + (이동할 거리 × 진행률)로 현재 scrollTop 위치 계산
  4. rAF 반복: 60FPS로 부드러운 프레임 업데이트

애니메이션 종류

export const linear = (startValue: number, endValue: number, t: number): number => {
  return startValue + (endValue - startValue) * t;
};

export const easeInOutQuad = (t: number): number => {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
};
  • Linear: 동일한 속도
  • Ease-in-out: 점진적 가속 후 점진적 감속
  • Ease-in: 점진적 가속감
  • Ease-out: 점진적 감속감

데모

Before

After

다섯 가지 문제들을 해결함으로써,

  • 브라우저 OOM을 고려하여 GC가 필요
  • 동적 height virtualized 리스트 고려

제약 사항을 해결하며, 목표로 했던 사용자 경험을 제공할 수 있었습니다.

추가적으로 하루 최대 24억 건의 실시간 로그 인터랙션을 제공하기 위해서 성능도 고민해야 하고, 복잡한 문제를 해결한만큼 확장성 또한 고민해야 하는데요. 이 경험은 다음 편에서 소개드리겠습니다. 감사합니다.

와탭 모니터링을 무료로 체험해보세요!