measureUserAgentSpecificMemory()로 웹페이지의 총 메모리 사용량을 모니터링합니다.

프로덕션 환경에서 웹페이지의 메모리 사용량을 측정하여 회귀를 감지하는 방법을 알아보세요.

브렌단 케니
브렌든 케니
울란 데겐바에프
울란 데겐바예프

브라우저는 웹페이지의 메모리를 자동으로 관리합니다. 웹페이지에서 객체를 만들 때마다 브라우저는 메모리 청크를 '내부에서' 할당하여 객체를 저장합니다. 메모리는 유한한 리소스이므로 브라우저는 가비지 컬렉션을 수행하여 객체가 더 이상 필요하지 않은 경우를 감지하고 기본 메모리 청크를 해제합니다.

하지만 감지가 완벽하지는 않으며 완벽한 감지는 불가능한 작업이라는 것이 증명되었습니다. 따라서 브라우저는 '객체에 연결할 수 있습니다'라는 개념으로 '객체가 필요합니다'라는 개념을 근사화합니다. 웹페이지가 변수 및 연결 가능한 다른 객체의 필드를 통해 객체에 도달할 수 없으면 브라우저에서 안전하게 객체를 회수할 수 있습니다. 다음 예와 같이 이 두 개념의 차이로 인해 메모리 누수가 발생합니다.

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

여기서 더 큰 배열 b는 더 이상 필요하지 않지만 콜백에서 object.b를 통해 여전히 도달할 수 있으므로 브라우저가 이 배열을 회수하지 않습니다. 따라서 더 큰 배열의 메모리가 누출됩니다.

메모리 누수는 웹에 널리 퍼져 있습니다. 이벤트 리스너 등록을 취소하는 것을 잊거나, 실수로 iframe에서 객체를 캡처하거나, worker를 닫지 않거나, 배열에 객체를 누적하는 등의 방식으로 이벤트 리스너를 도입하기 쉽습니다. 웹페이지에서 메모리 누수가 발생하면 시간이 지남에 따라 메모리 사용량이 증가하며 웹페이지가 느리게 표시되고 사용자에게 너무 팽창됩니다.

이 문제를 해결하는 첫 번째 단계는 측정입니다. 새로운 performance.measureUserAgentSpecificMemory() API를 사용하면 개발자가 프로덕션 단계에서 웹페이지의 메모리 사용량을 측정하여 로컬 테스트를 통과하는 메모리 누수를 감지할 수 있습니다.

performance.measureUserAgentSpecificMemory()는 기존 performance.memory API와 어떻게 다른가요?

기존의 비표준 performance.memory API에 익숙하다면 새 API와 어떻게 다른지 궁금할 수 있습니다. 주요 차이점은 이전 API가 JavaScript 힙 크기를 반환하는 반면 새 API는 웹페이지에서 사용하는 메모리를 추정한다는 점입니다. 이 차이는 Chrome이 여러 웹페이지 (또는 동일한 웹페이지의 여러 인스턴스)와 동일한 힙을 공유할 때 중요합니다. 이러한 경우 이전 API의 결과가 임의로 달라질 수 있습니다. 이전 API는 '힙'과 같은 구현 관련 용어에 정의되어 있으므로 표준화는 불필요합니다.

또 다른 차이점은 새 API가 가비지 컬렉션 중에 메모리 측정을 실행한다는 점입니다. 이렇게 하면 결과의 노이즈가 줄어들지만 결과가 생성될 때까지 다소 시간이 걸릴 수 있습니다. 다른 브라우저에서는 가비지 컬렉션에 의존하지 않고 새 API를 구현할 수도 있습니다.

추천 사용 사례

웹페이지의 메모리 사용량은 이벤트, 사용자 작업, 가비지 컬렉션의 타이밍에 따라 다릅니다. 따라서 메모리 측정 API는 프로덕션의 메모리 사용 데이터를 집계하는 데 목적이 있습니다. 개별 호출의 결과는 유용하지 않습니다. 사용 사례 예:

  • 웹페이지의 새 버전을 출시하는 동안 새로운 메모리 누수를 포착하기 위해 회귀 감지
  • 메모리 영향을 평가하고 메모리 누수를 감지하기 위한 새로운 기능의 A/B 테스트
  • 메모리 사용량과 세션 기간의 상관관계를 분석하여 메모리 누수가 있는지 확인합니다.
  • 메모리 사용량과 사용자 측정항목의 상관관계를 분석하여 메모리 사용량의 전반적인 영향을 파악합니다.

브라우저 호환성

브라우저 지원

  • 89
  • 89
  • x
  • x

소스

현재 이 API는 Chrome 89부터 Chromium 기반 브라우저에서만 지원됩니다. 브라우저는 메모리의 객체를 나타내는 방법과 메모리 사용량을 추정하는 방법이 다르기 때문에 API 결과는 구현에 따라 크게 달라집니다. 적절한 계산 비용이 너무 많이 들거나 실행 불가능한 경우 브라우저는 일부 메모리 영역을 계산에서 제외할 수 있습니다. 따라서 브라우저 간에 결과를 비교할 수 없습니다. 동일한 브라우저의 결과를 비교하는 것만 의미가 있습니다.

performance.measureUserAgentSpecificMemory() 사용

기능 감지

실행 환경이 교차 출처 정보 유출을 방지하기 위한 보안 요구사항을 충족하지 않으면 performance.measureUserAgentSpecificMemory 함수를 사용할 수 없거나 SecurityError와 함께 실패할 수 있습니다. 이 시스템은 COOP+COEP 헤더를 설정하여 웹페이지를 활성화할 수 있는 교차 출처 격리를 사용합니다.

지원은 런타임에 감지할 수 있습니다.

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

로컬 테스트

Chrome은 가비지 컬렉션 중에 메모리를 측정합니다. 즉, API가 결과 프로미스를 즉시 확인하지 않고 대신 다음 가비지 컬렉션을 기다립니다.

API를 호출하면 현재 20초로 설정되어 있지만 더 빨리 발생할 수 있는 제한 시간 후 가비지 컬렉션이 강제 실행됩니다. --enable-blink-features='ForceEagerMeasureMemory' 명령줄 플래그로 Chrome을 시작하면 시간 제한이 0으로 감소하며 로컬 디버깅 및 테스트에 유용합니다.

API는 전체 웹페이지의 메모리 사용량을 샘플링하고 집계 및 분석을 위해 결과를 서버로 전송하는 전역 메모리 모니터를 정의하는 것이 좋습니다. 가장 간단한 방법은 주기적으로(예: M분마다) 샘플링하는 것입니다. 하지만 이렇게 하면 샘플 간에 메모리 피크가 발생할 수 있으므로 데이터에 편향이 발생합니다.

다음 예는 푸아송 프로세스를 사용하여 편향되지 않은 메모리 측정을 실행하는 방법을 보여줍니다. 이 프로세스에서는 모든 시점에 샘플이 발생할 가능성이 동일하게 보장됩니다(데모, 소스).

먼저 무작위 간격으로 setTimeout()를 사용하여 다음 메모리 측정을 예약하는 함수를 정의합니다.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

measurementInterval() 함수는 평균 5분마다 측정값이 한 개가 되도록 무작위 간격을 밀리초 단위로 계산합니다. 이 함수 뒤에 숨겨진 수학에 관심이 있다면 지수 분포를 참조하세요.

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

마지막으로 비동기 performMeasurement() 함수는 API를 호출하고 결과를 기록하고 다음 측정을 예약합니다.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

마지막으로 측정을 시작합니다.

// Start measurements.
scheduleMeasurement();

결과는 다음과 같습니다.

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

총 메모리 사용량 추정치는 bytes 필드에 반환됩니다. 이 값은 구현에 따라 크게 다르며 브라우저 간에 비교할 수 없습니다. 동일한 브라우저의 다른 버전 간에 변경될 수도 있습니다. 이 값에는 현재 프로세스의 모든 iframe, 관련 창, 웹 작업자의 자바스크립트 및 DOM 메모리가 포함됩니다.

breakdown 목록은 사용된 메모리에 관한 추가 정보를 제공합니다. 각 항목은 메모리의 일부를 설명하고 URL로 식별되는 기간, iframe, 작업자 집합에 메모리의 일부를 부여합니다. types 필드는 메모리와 관련된 구현별 메모리 유형을 나열합니다.

모든 목록을 일반적인 방식으로 처리하고 특정 브라우저를 기반으로 가정을 하드코딩하지 않는 것이 중요합니다. 예를 들어 일부 브라우저에서는 빈 breakdown 또는 빈 attribution를 반환할 수 있습니다. 다른 브라우저는 attribution에 여러 항목을 반환할 수 있습니다. 이는 이러한 항목 중 메모리를 소유한 항목을 구별할 수 없음을 나타냅니다.

의견

웹 성능 커뮤니티 그룹 및 Chrome팀은 performance.measureUserAgentSpecificMemory()와 관련된 여러분의 생각과 경험을 공유해 주시기 바랍니다.

API 설계에 대해 알려주세요.

API에 예상대로 작동하지 않는 문제가 있나요? 아니면 아이디어를 구현해야 하는 누락된 속성이 있나요? performance.measureUserAgentSpecificMemory() GitHub 저장소에서 사양 문제를 제출하거나 기존 문제에 의견을 추가합니다.

구현 관련 문제 신고

Chrome 구현에서 버그를 발견하셨나요? 아니면 구현이 사양과 다른가요? new.crbug.com에서 버그를 신고합니다. 가능한 한 많은 세부정보를 포함하고, 버그 재현을 위한 간단한 안내를 제공하고, 구성요소Blink>PerformanceAPIs로 설정하세요. Glitch는 쉽고 빠른 재현을 공유하는 데 효과적입니다.

응원하기

performance.measureUserAgentSpecificMemory() 서비스를 사용할 계획이신가요? 공개 지원은 Chrome팀에서 기능의 우선순위를 정하는 데 도움이 되며 다른 브라우저 공급업체에 이러한 기능을 지원하는 것이 얼마나 중요한지 알려줍니다. @ChromiumDev로 트윗을 보내어 어디서 어떻게 사용하고 있는지 알려주세요.

유용한 링크

감사의 말

Chrome에서 코드 검토를 담당해 주신 도메닉 데니콜라, 요아브 바이스, 마티아스 비넨스, 도미니크 인퓨어, 하네스 페이어, 켄타로 하라, 마이클 리파우츠님께 감사드립니다. 또한 API를 크게 개선할 수 있는 귀중한 사용자 의견을 제공해 주신 Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan, Neil Mckay에게도 감사의 말씀을 전합니다.

히어로 이미지(출처: Harrison Broadbent, Unsplash