web-vitals.js, Google 애널리틱스, BigQuery를 사용한 성능 측정

1. 시작하기 전에

실행할 작업

이 Codelab에서 학습할 내용은 다음과 같습니다.

  • Google 애널리틱스 4 속성을 BigQuery에 연결합니다.
  • web-vitals 라이브러리를 웹페이지에 추가합니다.
  • web-vitals 데이터를 준비하여 Google 애널리틱스로 전송합니다.
  • BigQuery에서 코어 웹 바이탈 데이터를 쿼리합니다.
  • Google 데이터 스튜디오에서 대시보드를 빌드하여 코어 웹 바이탈 데이터를 시각화합니다.

필요한 항목

  • GA4 속성이 있는 Google 애널리틱스 계정
  • Google Cloud 계정
  • Chromium 기반 웹브라우저(예: Chrome, Microsoft Edge) Chromium 기반 웹브라우저가 필요한 이유에 관한 자세한 내용은 브라우저 지원을 참고하세요.
  • 원하는 텍스트 편집기(예: Sublime Text, Visual Studio Code)
  • web-vitals 라이브러리의 작동 방식을 확인하기 위해 테스트 페이지를 호스팅할 위치. 로컬 서버를 사용하여 정적 웹페이지를 제공하거나 GitHub에서 테스트 페이지를 호스팅할 수도 있습니다.
  • 애널리틱스 코드를 배포할 수 있는 공개 사이트. 코드를 프로덕션 단계에 적용하면 이 Codelab이 끝났을 때 BigQuery 및 데이터 스튜디오 예시를 더 이해하기 쉽게 만들 수 있습니다.
  • HTML, CSS, 자바스크립트, Chrome DevTools 관련 지식

시작하기 전에

먼저 Google 애널리틱스 4를 BigQuery에 연결하여 코드가 적용되는 즉시 성능 분석을 시작할 수 있도록 합니다.

Google 애널리틱스 고객센터의 단계에 따라 GA4 속성을 BigQuery에 연결하세요.

이제 Google 애널리틱스 속성이 이벤트 데이터를 BigQuery로 내보낼 준비가 되었으므로 사이트에 web-vitals 라이브러리를 통합합니다.

2. 웹페이지에 web-vitals 라이브러리 및 gtag 추가

먼저 web-vitals 라이브러리를 웹페이지에 추가합니다.

  1. web-vitals 라이브러리를 추가할 페이지 템플릿을 엽니다. 이 예에서는 간단한 페이지를 사용하겠습니다.

basic.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Web Vitals Test</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <p><img style="max-width: 360px" src="https://placekitten.com/g/3840/2160" alt="Kitten" /></p>
  <p>Text below image</p>
</body>
</html>
  1. 텍스트 편집기에서 소스 코드를 빈 파일에 붙여넣습니다.
  2. 파일을 로컬에 basic.html로 저장합니다.
  3. 이 모듈 스크립트를 복사하여 닫는 </body> 태그 바로 앞에 붙여넣습니다. 이 스크립트는 콘텐츠 전송 네트워크에서 web-vitals 라이브러리를 로드합니다.

basic.html

<script type="module">
  import {getCLS, getFID, getLCP} from 'https://unpkg.com/web-vitals?module';

  getCLS(console.log);
  getFID(console.log);
  getLCP(console.log);
</script>

결과 코드는 다음과 같습니다.

basic.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Web Vitals Test</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <p><img style="max-width: 360px" src="https://placekitten.com/g/3840/2160" alt="Kitten" /></p>
  <p>Text below image</p>

<script type="module">
  import {getCLS, getFID, getLCP} from 'https://unpkg.com/web-vitals?module';

  getCLS(console.log);
  getFID(console.log);
  getLCP(console.log);
</script>
</body>
</html>
  1. 파일을 저장합니다.

web-vitals 라이브러리를 웹페이지에 추가했습니다.

3. 웹페이지의 코어 웹 바이탈 측정

코어 웹 바이탈은 Chrome 또는 Chromium 브라우저의 web-vitals 라이브러리를 통해 캡처된 실제 사용자 경험 척도입니다. web-vitals를 프로덕션에 출시하면 사용자의 연결 속도, 기기 성능, 사용자와 사이트의 상호작용 방식에 따라 다양한 결과를 확인할 수 있습니다. web-vitals 라이브러리의 기능을 보여주기 위해 느린 연결로 사용자 환경을 시뮬레이션해 보겠습니다.

  1. 저장된 파일을 웹브라우저에서 엽니다.
  2. 웹페이지를 마우스 오른쪽 버튼으로 클릭합니다.
  3. 검사를 클릭하여 Chrome 개발자 도구를 엽니다.

1d60156133044215.png

  1. 콘솔 탭 > 콘솔 설정 b5c716ebfacfd86.png을 클릭합니다.

a310e2b6e03891a1.png

  1. 웹페이지를 새로고침할 때 로그가 유지되도록 하려면 로그 보존 체크박스를 선택합니다.

cdfbcd3315aa45cd.png

  1. 네트워크 탭 > 온라인 > 느린 3G를 클릭하여 느린 네트워크 연결을 시뮬레이션합니다.

b1fab3d167d032f0.png

  1. 콘솔 탭을 클릭합니다.
  2. 웹페이지의 아무 곳이나 클릭하여 최대 콘텐츠 렌더링 시간(LCP) 및 최초 입력 반응 시간(FID)의 측정항목을 인쇄합니다.
  3. 페이지 새로고침 acaaa8c0fdd33b1.png을 클릭하여 레이아웃 변경 횟수(CLS) 측정항목을 인쇄합니다.

e18b530e48108a4.png

  1. 네트워크 탭 > 온라인 > 빠른 3G를 클릭하여 빠른 네트워크 연결을 시뮬레이션합니다.
  2. 콘솔 탭을 클릭합니다.
  3. 웹페이지의 아무 곳이나 클릭하여 LCP 및 FID 측정항목을 다시 인쇄합니다.

e5d5ca555ded9f7a.png

  1. 페이지 새로고침 acaaa8c0fdd33b1.png을 클릭하여 CLS의 측정항목을 다시 인쇄합니다.

e8bde4594a01021b.png

이제 완료됐습니다. 웹페이지의 코어 웹 바이탈을 측정했습니다.

4. web-vitals 데이터 자세히 살펴보기

측정 중인 각 코어 웹 바이탈 이벤트에 관해 반환되는 데이터에서 다양한 정보를 사용해 성능 병목 현상을 디버그할 수 있습니다. 각 web-vitals 이벤트에는 현재 측정항목 값에 기여하는 이벤트에 관한 정보와 함께 entries 배열이 포함됩니다.

CLS entries

getCLS()에 의해 기록된 객체의 entries 속성을 펼치면 LayoutShift 항목 목록이 표시됩니다. 각 LayoutShift에는 레이아웃 변경 점수를 반영하는 value 속성과 변경된 요소를 확인하는 데 사용할 수 있는 sources 배열이 포함되어 있습니다.

355f0ff58e735079.png

이 예에서는 레이아웃 변경이 두 번 발생하여 모두 번 모두 페이지에서 h1 요소가 이동했습니다. currentRect 속성은 요소의 현재 위치를 알려주고 previousRect 요소는 이전 위치를 알려줍니다.

LCP entries

getLCP()에 의해 기록된 객체의 entries 속성을 펼치면 최종 값이 보고되기 전에 최대 콘텐츠 렌더링 시간(LCP) 후보에 해당하는 요소를 알 수 있습니다.

737ebf826005dbe7.png

이 예에서 entries 배열에는 모든 LCP 후보의 목록이 시간순으로 나옵니다. 이 경우는 h1 요소가 먼저 렌더링된 다음 img 요소가 렌더링되었습니다. img가 최대 콘텐츠 렌더링 시간 요소입니다. 보고된 LCP 요소는 항상 배열의 마지막 항목입니다.

FID entries

getFID(),에 의해 기록된 객체의 entries 속성을 펼치면 페이지의 첫 번째 사용자 입력에 해당하는 PerformanceEventTiming 항목이 포함된 배열이 표시됩니다.

a63ef33575c3218d.png

name 속성은 기본 스레드 가용성의 타이머를 트리거한 사용자 입력의 유형을 알려줍니다. web-vitals가 보고하는 valuePerformanceEventTiming 항목의 startTime 속성과 processingStart 속성 간의 지연 시간으로, 마이크로초에서 밀리초로 변환됩니다. 이 경우 측정된 FID는 2밀리초입니다.

5. web-vitals 데이터를 준비하여 Google 애널리틱스 4로 전송

web-vitals 데이터를 Google 애널리틱스 4로 보내려면 GA4에서 수신할 수 있는 형식으로 변환해야 합니다. 중요한 진단 정보를 가져오는 유용한 함수도 추가합니다.

입력 대상 노드를 식별하는 데 도움이 되는 선택기 생성

먼저, DOM의 노드와 노드 자리를 나타내는 문자열을 CSS 선택자와 비슷한 형식으로 생성하는 함수를 스크립트 블록에 추가합니다. 이 함수의 출력을 통해 페이지에서 CWV 값을 담당하는 요소를 확인할 수 있습니다.

diagnostics.html

function getSelector(node, maxLen = 100) {
 let sel = '';
 try {
   while (node && node.nodeType !== 9) {
     const part = node.id ? '#' + node.id : node.nodeName.toLowerCase() + (
       (node.className && node.className.length) ?
       '.' + Array.from(node.classList.values()).join('.') : '');
     if (sel.length + part.length > maxLen - 1) return sel || part;
     sel = sel ? part + '>' + sel : part;
     if (node.id) break;
     node = node.parentNode;
   }
 } catch (err) {
   // Do nothing...
 }
 return sel;
}

LayoutShift 정보 검색

발생하는 모든 레이아웃 변경을 기록하려면 과도한 양의 데이터가 생성될 수 있습니다. 아래의 함수는 가장 큰 LayoutShift 항목과 그 안에 있는 가장 큰 LayoutShiftSource에만 초점을 맞춥니다. 이를 통해 사이트에서 레이아웃 변경의 가장 큰 원인을 기반으로 최적화하는 데 집중할 수 있습니다. 레이아웃 변경의 원인을 파악하고 최소화하는 방법을 찾을 때 보고서에 표시되는 레이아웃 변경 소스가 변경되어 가장 나쁜 영향을 미친 새로운 원인을 보여줍니다.

diagnostics.html

function getLargestLayoutShiftEntry(entries) {
 return entries.reduce((a, b) => a && a.value > b.value ? a : b);
}

function getLargestLayoutShiftSource(sources) {
 return sources.reduce((a, b) => {
   return a.node && a.previousRect.width * a.previousRect.height >
       b.previousRect.width * b.previousRect.height ? a : b;
 });
}
  • getLargestLayoutShiftEntry()는 페이지 뷰의 수명 주기 동안 가장 큰 레이아웃 변경 항목만 반환합니다.
  • getLargestLayoutShiftSource()는 이 항목 내에서 가장 큰 레이아웃 변경 소스만 반환합니다.

FID 발생 시점이 DOMContentLoaded 이전인지 이후인지 확인

DOMContentLoaded 이벤트는 페이지의 HTML이 완전히 로드 및 파싱된 후에 발생합니다. 여기에는 동기식, 지연, 모듈(정적으로 가져온 모든 모듈 포함) 스크립트가 로드될 때까지 대기하는 과정이 포함됩니다. 이 함수는 첫 번째 사용자 입력이 DOMContentLoaded 이전에 발생했다면 true를 반환하고 이후에 발생했다면 false를 반환합니다.

diagnostics.html

function wasFIDBeforeDCL(fidEntry) {
 const navEntry = performance.getEntriesByType('navigation')[0];
 return navEntry && fidEntry.startTime < navEntry.domContentLoadedEventStart;
}

FID 대상 요소 식별

유용할 수 있는 또 다른 디버그 신호는 상호작용된 요소입니다. 요소와의 상호작용 자체는 FID에 기여하지 않지만(FID는 총 이벤트 지연 시간의 지연 부분임) 사용자가 어떤 요소와 상호작용하는지 안다면 FID를 가장 효과적으로 개선할 방법을 판단하는 데 도움이 될 수 있습니다.

첫 번째 입력 이벤트와 연결된 요소를 가져오려면 first-input 항목의 target 속성을 참조합니다.

diagnostics.html

function getFIDDebugTarget(entries) {
  return entries[0].target;
}

FID 입력 이벤트 유형 식별

사용자가 페이지와 어떻게 상호작용하는지 파악하기 위해 FID 측정을 트리거한 이벤트 유형을 캡처하는 것이 유용할 수도 있습니다.

diagnostics.html

function getFIDEventType(entries) {
  return entries[0].name;
}

각 CWV의 디버그 정보 구조화

이 코드를 Google 애널리틱스로 전송하기 전 마지막 단계는 위 함수에서 반환한 정보를 포함하여 항목의 정보를 구조화하는 것입니다.

diagnostics.html

function getDebugInfo(name, entries = []) {
  // In some cases there won't be any entries (e.g. if CLS is 0,
  // or for LCP after a bfcache restore), so we have to check first.
  if (entries.length) {
    if (name === 'LCP') {
      const lastEntry = entries[entries.length - 1];
      return {
        debug_target: getSelector(lastEntry.element),
        event_time: lastEntry.startTime,
      };
    } else if (name === 'FID') {
      const firstEntry = entries[0];
      return {
        debug_target: getSelector(firstEntry.target),
        debug_event: firstEntry.name,
        debug_timing: wasFIDBeforeDCL(firstEntry) ? 'pre_dcl' : 'post_dcl',
        event_time: firstEntry.startTime,
      };
    } else if (name === 'CLS') {
      const largestEntry = getLargestLayoutShiftEntry(entries);
      if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
        const largestSource = getLargestLayoutShiftSource(largestEntry.sources);
        if (largestSource) {
          return {
            debug_target: getSelector(largestSource.node),
            event_time: largestEntry.startTime,
          };
        }
      }
    }
  }
  // Return default/empty params in case there are no entries.
  return {
    debug_target: '(not set)',
  };
}

Google 애널리틱스로 데이터 전송

마지막으로, web-vitals 이벤트에서 매개변수를 가져와 Google 애널리틱스로 전달하는 함수를 만듭니다.

diagnostics.html

function sendToGoogleAnalytics({ name, delta, value, id, entries }) {
  gtag('event', name, {
    // Built-in params:
    value: delta, // Use `delta` so the value can be summed.
    // Custom params:
    metric_id: id, // Needed to aggregate events.
    metric_value: value, // Value for querying in BQ
    metric_delta: delta, // Delta for querying in BQ
    // Send the returned values from getDebugInfo() as custom parameters
      ...getDebugInfo(name, entries)
  });
}

함수를 각 web-vitals 함수에 등록합니다. 그러면 브라우저가 각 이벤트를 측정할 준비가 되었을 때 실행됩니다.

diagnostics.html

getLCP(sendToGoogleAnalytics);
getFID(sendToGoogleAnalytics);
getCLS(sendToGoogleAnalytics);

잘하셨습니다. 이제 web-vitals 이벤트를 Google 애널리틱스로 전송합니다.

6. Google 애널리틱스에서 web-vitals 데이터가 채워지는지 확인

Google 애널리틱스 4 속성이 이벤트를 기록하도록 하려면 다음 단계를 따르세요.

  1. Google 애널리틱스 4 속성을 열고 보고서로 이동합니다.

ab1bf51ba70f3609.png

  1. 실시간을 선택합니다.

65a5b8087b09b2a.png

  1. 테스트 페이지를 몇 번 새로고침하면서 새로고침 사이에 페이지를 클릭하여 FID 이벤트를 트리거해야 합니다.
  2. 실시간 개요 UI에서 이벤트 이름별 이벤트 수 섹션을 찾습니다. LCP, FID, CLS 이벤트가 표시됩니다.

f92b276df1c2f6ce.png

  1. 이벤트 이름 중 하나를 클릭하여 이벤트와 함께 전달된 매개변수를 확인합니다.

8529bd743f121dd9.png

  1. 매개변수 키를 클릭하면 Google 애널리틱스에 수신된 값에 관한 요약을 볼 수 있습니다.

f0cf6a3dd607d533.png

페이지 템플릿 이름이나 이 Codelab 앞부분에서 설명한 FID와 관련된 다른 페이지 이벤트와 같은 다른 데이터를 디버그 정보에 추가하는 것이 좋습니다. getDebugInfo() 함수에서 return 문을 수정하면 됩니다.

테스트 페이지의 데이터가 만족스러우면 새 GA 코드를 사이트의 프로덕션에 배포하고 다음 단계로 진행합니다.

7. BigQuery로 데이터 쿼리

Google 애널리틱스 코드를 며칠 동안 적용한 후에는 BigQuery에서 데이터 쿼리를 시작할 수 있습니다. 먼저 데이터가 BigQuery로 전송되고 있는지 확인합니다.

  1. Google Cloud Console을 열고 화면 상단의 드롭다운 메뉴에서 프로젝트를 선택합니다.
  2. 화면 왼쪽 상단의 탐색 메뉴 3cbb0e5fcc230aef.png를 클릭하고 BigQuery분석 헤더를 찾습니다.
  3. 탐색기 창에서 프로젝트를 펼쳐 Google 애널리틱스 데이터 세트를 확인합니다. 데이터 세트의 이름은 analytics_ 뒤에 Google 애널리틱스 4 속성 ID가 옵니다(예: analytics_229787100)).
  4. 데이터 세트를 펼치면 events_ 테이블이 표시됩니다. 괄호 안의 숫자는 쿼리할 수 있는 일수입니다.

CWV 이벤트만 선택하는 하위 쿼리

CWV 이벤트만 포함된 데이터 세트를 쿼리하려면 최근 28일의 LCP, CLS, FID 이벤트를 선택하는 하위 쿼리로 시작합니다. 특히 동일한 CWV 이벤트를 두 번 이상 집계하지 않도록 metric_id 키를 사용하여 각 web-vitals 이벤트 ID에 관해 마지막으로 보고된 값을 찾습니다.

# Subquery all Web Vitals events from the last 28 days
WITH web_vitals_events AS (
 SELECT event_name as metric_name, * EXCEPT(event_name, is_last_received_value) FROM
 (
   SELECT *
   , IF (ROW_NUMBER() OVER (
     PARTITION BY (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'metric_id')
     ORDER BY (SELECT COALESCE(value.double_value, value.int_value) FROM UNNEST(event_params) WHERE key = 'metric_value') DESC
   ) = 1, true, false) AS is_last_received_value
   # Make sure to update your project ID and GA4 property ID here!
   FROM `YOUR_PROJECT_ID.analytics_YOUR_GA_PROPERTY_ID.events_*`
   WHERE event_name in ('CLS', 'FID', 'LCP') AND
     _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE, INTERVAL 28 DAY)) AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY))
  )
  WHERE is_last_received_value
)

이렇게 하면 이 데이터 세트에 관한 모든 쿼리의 기반이 됩니다. 기본 쿼리가 임시 테이블 web_vitals_events에서 실행됩니다.

GA4 이벤트의 구성 방식

각 Google 애널리틱스 4 이벤트 데이터는 STRUCTevent_params 열에 있습니다. 사이트의 GA4에 전달하는 각 이벤트 매개변수는 키로 표시되며 값은 가능한 각 데이터 유형의 키가 포함된 STRUCT입니다. 위의 예에서 metric_value 키는 int_valuedouble_value를 가질 수 있으므로 COALESCE() 함수가 사용됩니다. 앞서 전달한 debug_target을 가져오려면 debug_target에서 string_value 키를 선택합니다.

...
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = "debug_target") as debug_target
...

성능이 가장 저조한 페이지와 요소 찾기

debug_target은 측정항목 값과 가장 관련성이 높은 페이지 요소에 해당하는 CSS 선택자 문자열입니다.

CLS를 사용할 때 debug_target은 CLS 값에 기여한 최대 레이아웃 변경의 가장 큰 요소를 나타냅니다. 변경된 요소가 없는 경우 debug_target 값은 null입니다.

다음 쿼리는 CLS 기준, 75분위에서 최고부터 최저 수준의 순서로 debug_target별로 묶어 페이지를 나열합니다.

# Main query logic
SELECT
  page_path,
  debug_target,
  APPROX_QUANTILES(metric_value, 100)[OFFSET(75)] AS metric_p75,
  COUNT(1) as page_views
FROM (
  SELECT
    REGEXP_SUBSTR((SELECT value.string_value FROM UNNEST(event_params) WHERE key = "page_location"), r'\.com(\/[^?]*)') AS page_path,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = "debug_target") as debug_target,
    ROUND((SELECT COALESCE(value.double_value, value.int_value) FROM UNNEST(event_params) WHERE key = "metric_value"), 3) AS metric_value,
    *
  FROM web_vitals_events
  WHERE metric_name = 'CLS'
)
GROUP BY 1, 2
# OPTIONAL: You may want to limit your calculations to pages with a
# minimum number of pageviews to reduce noise in your reports.
# HAVING page_views > 50
ORDER BY metric_p75 DESC

1bbbd957b4292ced.png

페이지에서 어느 요소가 변경되는지 파악하면 문제의 근본 원인을 훨씬 더 쉽게 파악하고 해결할 수 있습니다.

여기에 보고된 요소는 페이지를 로컬에서 디버깅할 때 변경되는 것과 동일한 요소가 아닐 수 있으므로 먼저 이 데이터를 캡처하는 것이 중요합니다. 인식하지 못한 문제를 해결하는 것은 매우 어려운 일입니다.

다른 측정항목 디버깅

위의 쿼리는 CLS 측정항목의 결과를 보여주지만, LCP와 FID의 디버그 대상에 관해 보고하는 데도 정확히 동일한 기법을 사용할 수 있습니다. 디버깅할 관련 측정항목으로 WHERE절을 대체하기만 하면 됩니다.

# Replace:
# WHERE metric_name = 'CLS'
# With:
WHERE metric_name = 'LCP'

8. 데이터 스튜디오에서 쿼리 결과 시각화

BigQuery를 사용하면 데이터 스튜디오를 통해 모든 쿼리 결과를 빠르게 시각화할 수 있습니다. 데이터 스튜디오는 무료로 사용할 수 있는 데이터 시각화 및 대시보드 도구입니다. BigQuery UI에서 쿼리를 실행한 후 쿼리 결과를 시각화하려면 데이터 탐색을 클릭하고 데이터 스튜디오로 탐색을 선택합니다.

BigQuery의 데이터 스튜디오로 탐색 옵션

이렇게 하면 탐색 뷰에 BigQuery에서 데이터 스튜디오로 직접 연결되는 링크가 생성됩니다. 이 뷰에서 시각화할 필드를 선택하고, 차트 유형을 선택하고, 필터를 설정하고, 빠른 시각적 분석을 위한 임시 차트를 만들 수 있습니다. 위의 쿼리 결과에서 다음 선 차트를 만들어 시간 경과에 따른 LCP 값 추세를 확인할 수 있습니다.

데이터 스튜디오의 일일 LCP 값 선 차트

BigQuery와 데이터 스튜디오 간의 이 직접 링크를 사용하면 모든 쿼리에서 빠른 차트를 만들고 시각적 분석을 할 수 있습니다. 하지만 추가 분석을 원하는 경우 대화형 대시보드에서 여러 차트를 보며 전체적으로 파악하거나 데이터를 자세히 살펴볼 수 있습니다. 대시보드가 편리하기 때문에 측정항목을 분석할 때마다 쿼리를 작성하고 수동으로 차트를 생성하지 않아도 됩니다.

기본 BigQuery 커넥터를 사용하여 데이터 스튜디오에서 대시보드를 만들 수 있습니다. 이렇게 하려면 datastudio.google.com으로 이동해 새 데이터 소스를 만들고, BigQuery 커넥터를 선택하고, 작업할 데이터 세트를 선택합니다.

데이터 스튜디오에서 BigQuery 기본 커넥터 사용

9. 웹 바이탈 데이터 구체화

위의 설명대로 웹 바이탈 이벤트 데이터의 대시보드를 만들 때 Google 애널리틱스 4 내보내기 데이터 세트를 직접 사용하면 비효율적입니다. GA4 데이터의 구조와 웹 바이탈 측정항목에 필요한 사전 처리로 인해 쿼리의 일부가 여러 번 실행됩니다. 그러면 대시보드 성능과 BigQuery 비용의 두 가지 문제가 발생합니다.

BigQuery 샌드박스 모드를 무료로 사용할 수 있습니다. BigQuery의 무료 사용 등급을 사용하면 매월 처리되는 쿼리 데이터 중 처음 1TB는 무료입니다. 이 게시물에서 다룬 분석 방법의 경우 매우 큰 데이터 세트를 사용하거나 정기적으로 데이터 세트를 많이 쿼리하지 않는다면 매월 무료 한도 내에서 사용할 수 있습니다. 하지만 웹사이트의 트래픽이 많고 신속한 대화형 대시보드를 사용하여 다양한 측정항목을 정기적으로 모니터링하려면 파티션 나누기, 클러스터링, 캐싱과 같은 BigQuery 효율성 기능을 사용하는 동시에 웹 바이탈 데이터를 사전 처리 및 구체화하는 것이 좋습니다.

다음 스크립트는 BigQuery 데이터(소스 테이블)를 사전 처리하고 구체화된 테이블(대상 테이블)을 만듭니다.

# Materialize Web Vitals metrics from GA4 event export data

# Replace target table name
CREATE OR REPLACE TABLE YOUR_PROJECT_ID.analytics_YOUR_GA_PROPERTY_ID.web_vitals_summary
  PARTITION BY DATE(event_timestamp)
AS
SELECT
  ga_session_id,
  IF(
    EXISTS(SELECT 1 FROM UNNEST(events) AS e WHERE e.event_name = 'first_visit'),
    'New user',
    'Returning user') AS user_type,
  IF(
    (SELECT MAX(session_engaged) FROM UNNEST(events)) > 0, 'Engaged', 'Not engaged')
    AS session_engagement,
  evt.* EXCEPT (session_engaged, event_name),
  event_name AS metric_name,
  FORMAT_TIMESTAMP('%Y%m%d', event_timestamp) AS event_date
FROM
  (
    SELECT
      ga_session_id,
      ARRAY_AGG(custom_event) AS events
    FROM
      (
        SELECT
          ga_session_id,
          STRUCT(
            country,
            device_category,
            device_os,
            traffic_medium,
            traffic_name,
            traffic_source,
            page_path,
            debug_target,
            event_timestamp,
            event_name,
            metric_id,
            IF(event_name = 'LCP', metric_value / 1000, metric_value) AS metric_value,
            user_pseudo_id,
            session_engaged,
            session_revenue) AS custom_event
        FROM
          (
            SELECT
              (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id')
                AS ga_session_id,
              (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'metric_id')
                AS metric_id,
              ANY_VALUE(device.category) AS device_category,
              ANY_VALUE(device.operating_system) AS device_os,
              ANY_VALUE(traffic_source.medium) AS traffic_medium,
              ANY_VALUE(traffic_source.name) AS traffic_name,
              ANY_VALUE(traffic_source.source) AS traffic_source,
              ANY_VALUE(
                REGEXP_SUBSTR(
                  (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location'),
                  r'^[^?]+')) AS page_path,
              ANY_VALUE(
                (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'debug_target'))
                AS debug_target,
              ANY_VALUE(user_pseudo_id) AS user_pseudo_id,
              ANY_VALUE(geo.country) AS country,
              ANY_VALUE(event_name) AS event_name,
              SUM(ecommerce.purchase_revenue) AS session_revenue,
              MAX(
                (
                  SELECT
                    COALESCE(
                      value.double_value, value.int_value, CAST(value.string_value AS NUMERIC))
                  FROM UNNEST(event_params)
                  WHERE key = 'session_engaged'
                )) AS session_engaged,
              TIMESTAMP_MICROS(MAX(event_timestamp)) AS event_timestamp,
              MAX(
                (
                  SELECT COALESCE(value.double_value, value.int_value)
                  FROM UNNEST(event_params)
                  WHERE key = 'metric_value'
                )) AS metric_value,
            FROM
              # Replace source table name
              `YOUR_PROJECT_ID.analytics_YOUR_GA_PROPERTY_ID.events_*`
            WHERE
              event_name IN ('LCP', 'FID', 'CLS', 'first_visit', 'purchase')
            GROUP BY
              1, 2
          )
      )
    WHERE
      ga_session_id IS NOT NULL
    GROUP BY ga_session_id
  )
CROSS JOIN UNNEST(events) AS evt
WHERE evt.event_name NOT IN ('first_visit', 'purchase');

구체화된 이 데이터 세트에는 다음과 같은 여러 이점이 있습니다.

  • 데이터 구조가 축약되어 더 쉽게 쿼리할 수 있습니다.
  • 원본 GA4 데이터 세트의 웹 바이탈 이벤트만 유지합니다.
  • 세션 ID, 사용자 유형(신규 고객, 재사용자), 세션 참여 정보를 열에서 직접 확인할 수 있습니다.
  • 테이블은 날짜별로 파티션이 나뉘고 측정항목 이름을 기준으로 클러스터링됩니다. 일반적으로 각 쿼리에서 처리되는 데이터의 양이 줄어듭니다.
  • 이 테이블을 쿼리하는 데 와일드 카드를 사용할 필요가 없으므로 쿼리 결과를 최대 24시간 동안 캐시할 수 있습니다. 따라서 동일한 쿼리를 반복하는 데 드는 비용이 줄어듭니다.
  • BigQuery BI Engine을 사용하는 경우 이 테이블에서 최적화된 SQL 함수 및 연산자를 실행할 수 있습니다.

구체화된 이 테이블을 BigQuery UI 내에서 직접 쿼리하거나 BigQuery 커넥터를 사용하여 데이터 스튜디오에서 테이블을 사용할 수 있습니다.

정기적인 구체화 작업 실행

기간을 지정하지 않고 위의 쿼리를 실행하면 전체 Google 애널리틱스 데이터 세트에서 실행됩니다. 대량의 과거 데이터를 매일 재처리하게 되므로 이렇게 하지 않는 것이 좋습니다. 마지막 날의 데이터만 추가하도록 쿼리를 업데이트할 수 있습니다. 쿼리 시작 부분에 있는 CREATE or REPLACE TABLE 문을 삭제하고, events_intraday_ 테이블과 비교하는 기준을 하위 쿼리의 WHERE 절에 추가하면 됩니다.

FROM
  # Replace source table name
  `YOUR_PROJECT_ID.analytics_YOUR_GA_PROPERTY_ID.events_intraday_*`
WHERE
  event_name IN ('LCP', 'FID', 'CLS', 'first_visit', 'purchase')
  # The _TABLE_SUFFIX replaces the asterisk (*) in the table name
  #
  AND _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY)

이 쿼리는 어제의 데이터만 반환합니다. 그런 다음 BigQuery 콘솔을 사용하여 쿼리가 매일 실행되도록 예약할 수 있습니다.

10. Google 데이터 스튜디오의 데이터 시각화

Google 데이터 스튜디오는 기본적으로 Google BigQuery에서 데이터를 읽을 수 있게 지원합니다. 이제 Google 애널리틱스 4의 web-vitals 데이터가 BigQuery에 채워져 있으므로 데이터 스튜디오 BigQuery 커넥터를 사용하여 구체화된 테이블을 직접 읽을 수 있습니다.

웹 바이탈 커넥터 사용

대시보드를 처음부터 만드는 데 시간이 많이 걸리기 때문에 Google은 템플릿 대시보드를 만드는 패키지 솔루션을 개발했습니다. 먼저 위의 쿼리를 사용하여 웹 바이탈 테이블을 구체화했는지 확인합니다. 그런 다음 goo.gle/web-vitals-connector 링크를 사용하여 데이터 스튜디오의 웹 바이탈 커넥터에 액세스합니다.

일회성 승인을 제공하면 다음과 같은 구성 화면이 표시됩니다.

웹 바이탈 커넥터 승인 화면

구체화된 BigQuery 테이블 ID(예: 대상 테이블)와 BigQuery 결제 프로젝트 ID를 제공합니다. 연결을 클릭하면 데이터 스튜디오에서 새 템플릿 대시보드가 만들어져 데이터와 연결됩니다. 원하는 대로 대시보드를 수정하고 공유할 수 있습니다. 대시보드를 한 번 만들면 서로 다른 데이터 세트에서 여러 대시보드를 만들지 않는 한 커넥터 링크를 다시 방문하지 않아도 됩니다.

대시보드를 탐색할 때 '요약' 탭에서 웹 바이탈 측정항목의 일일 추세와 웹사이트의 일부 사용 정보(예: 사용자, 세션)를 볼 수 있습니다.

'사용자 분석' 탭에서 측정항목을 선택하고 다양한 사용 및 비즈니스 측정항목을 기준으로 측정항목 백분위수는 물론 사용자 수도 세분화하여 확인할 수 있습니다.

'페이지 경로 분석' 탭에서는 웹사이트의 문제 영역을 파악할 수 있습니다. 여기서는 측정항목을 선택하여 개요를 볼 수 있으며 모든 페이지 경로의 분산형 지도(y축은 백분위수 값, x축은 레코드 수)도 확인할 수 있습니다. 분산형 지도를 사용하면 측정항목 값이 예상보다 낮은 페이지를 식별할 수 있습니다. 페이지를 선택한 후 페이지 경로 테이블의 분산형 차트를 통해서나 디버그 대상 테이블을 확인하여 문제 영역을 자세히 살펴볼 수 있습니다.

'수익 분석' 탭은 비즈니스 및 실적 측정항목을 한곳에서 모니터링하는 방법 중 하나입니다. 이 섹션에는 사용자가 구매한 모든 세션이 표시됩니다. 특정 세션 중에 발생한 수익과 사용자 경험을 비교할 수 있습니다.

11. 기타 리소스

이 Codelab을 완료했습니다. 이제 사이트 전반의 코어 웹 바이탈 성능을 매우 세분화하여 추적할 수 있게 되었습니다. 또한 사이트에서 CWV를 높이는 특정 페이지 유형과 요소를 파악하여 최적화에 집중할 수 있습니다.

추가 자료

web.dev에는 코어 웹 바이탈 개선을 위한 전략과 관련된 다양한 자료와 우수사례가 있습니다. 먼저 각 측정항목의 최적화 도움말을 확인하세요.

참조 문서