Google 애널리틱스 4 및 BigQuery로 성능 측정 및 디버그

Web Vitals 데이터를 Google 애널리틱스 4 속성으로 전송하는 방법과 BigQuery 및 Looker Studio에서 분석할 수 있도록 이러한 데이터를 내보내는 방법을 알아보세요.

Google은 개발자가 현장에서 실제 사용자를 대상으로 Core Web Vitals 측정항목에 대해 사이트의 실적을 확인할 수 있는 여러 도구(Search Console, PageSpeed Insights(PSI), Chrome 사용자 환경 보고서(CrUX))를 제공합니다.

이러한 도구는 사이트의 실제 사용자 실적을 대략적으로 확인할 수 있다는 점에서 유용하며, 사용하기 위해 설정할 필요가 전혀 없습니다.

하지만 이러한 도구만 사용하여 사이트 실적을 측정하는 것은 바람직하지 않습니다. 다음과 같은 몇 가지 중요한 이유가 있습니다.

  • CrUX 기반 도구는 월별 또는 이전 28일 동안의 데이터를 보고합니다. 따라서 변경 후에는 오랜 시간이 지난 후에 결과를 볼 수 있습니다.
  • CrUX 기반 도구는 국가, 연결 유형, 기기 카테고리(데스크톱 또는 모바일)와 같이 제한된 수의 측정기준으로만 분류할 수 있습니다. 비즈니스 관련 측정기준(예: 참여도 높은 사용자, 특정 실험 그룹의 사용자 등)을 기준으로 데이터를 분할할 수 없습니다.
  • CrUX 기반 도구는 실적의 내용을 알려줄 수 있지만 이유는 알려주지 않습니다. 분석 도구를 사용하면 문제를 추적하고 디버그하는 데 도움이 되는 추가 데이터를 전송할 수 있습니다.

따라서 모든 사이트 소유자는 기존 분석 도구를 사용하여 Core Web Vitals 측정항목을 모니터링하는 것이 좋습니다. 이 게시물에서는 Google에서 제공하는 무료 도구를 사용하여 이를 실행하는 방법을 설명합니다.

모든 설정을 완료하면 다음과 같은 대시보드를 만들 수 있습니다.

웹 바이탈 커넥터 보고서 스크린샷

웹 바이탈 커넥터 보고서 스크린샷

여기에 설명된 모든 단계를 한눈에 보려면 Google I/O 2021 강연을 확인하세요.

측정

Google 애널리틱스에서는 항상 맞춤 측정항목을 사용하여 실적을 측정할 수 있었지만, Google 애널리틱스 4(GA4)에는 특히 개발자가 주목해야 할 몇 가지 새로운 기능이 있습니다.

Google 애널리틱스 웹 인터페이스에는 강력한 분석 도구가 있지만 이미 알고 있는 쿼리 언어를 사용하여 원시 이벤트 데이터에 액세스하는 것만큼 강력하고 유연한 방법은 없습니다.

Google 애널리틱스 4 및 BigQuery를 사용하여 Core Web Vitals 측정을 시작하려면 다음 세 가지 작업을 수행해야 합니다.

  1. Google 애널리틱스 4 속성BigQuery 프로젝트를 만듭니다.
  2. Google 애널리틱스 속성 구성에서 BigQuery 내보내기를 사용 설정하면 수신하는 모든 데이터가 BigQuery 프로젝트 테이블에 자동으로 채워집니다.
  3. web-vitals JavaScript 라이브러리를 사이트에 추가하여 Core Web Vitals 측정항목을 측정하고 기여 분석 데이터를 포함하여 데이터를 Google 애널리틱스 4로 전송합니다.

분석

설정이 완료되면 BigQuery 인터페이스에 이벤트 데이터가 채워지고 다음과 같이 데이터를 쿼리할 수 있습니다.

SELECT * FROM `my_project_id.analytics_XXXXX.events_*`
WHERE event_name IN ('LCP', 'INP', 'CLS')

다음은 이 쿼리의 결과 미리보기입니다.

BigQuery의 웹 바이탈 이벤트 데이터

웹 바이탈 데이터 쿼리

웹 Vitals 이벤트 데이터를 쿼리하기 전에 데이터가 집계되는 방식을 이해하는 것이 중요합니다.

가장 중요한 점은 경우에 따라 동일한 페이지에서 동일한 측정항목에 대해 여러 이벤트가 수신될 수 있다는 점입니다. 이는 측정항목 값이 변경되고 업데이트된 값이 보고되는 경우 발생할 수 있습니다(CLS에서 흔히 발생).

웹 Vitals 이벤트의 경우 전송된 마지막 값이 항상 가장 정확하므로 분석을 실행하기 전에 이러한 값만 필터링하는 것이 중요합니다. Google 애널리틱스 4로 데이터를 전송하기 위해 web-vitals JavaScript 라이브러리에서 제공하는 코드 스니펫에는 측정항목별 고유 ID 전송이 포함되어 있으므로 다음 쿼리를 사용하여 결과를 각 측정항목 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)

이 게시물에서 참조하는 다른 모든 쿼리는 이 하위 쿼리로 시작합니다.

쿼리 예

다음 몇 섹션에서는 실행할 수 있는 일반적인 웹 Vitals 쿼리의 몇 가지 예를 보여줍니다.

전체 사이트의 75번째 백분위수(p75) 기준 LCP, INP, CLS

# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  metric_name,
  APPROX_QUANTILES(metric_value, 100)[OFFSET(75)] AS p75,
  COUNT(1) as count
FROM (
  SELECT
    metric_name,
    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
)
GROUP BY 1

모든 개별 LCP 값(최대값에서 최소값으로)

# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  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 = 'LCP'
ORDER BY metric_value DESC
# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  page_path,
  APPROX_QUANTILES(metric_value, 100)[OFFSET(75)] AS LCP,
  COUNT(1) as count
FROM (
  SELECT
    REGEXP_SUBSTR((SELECT value.string_value FROM UNNEST(event_params) WHERE key = "page_location"), r'\.com(\/[^?]*)') AS page_path,
    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 = 'LCP'
)
GROUP BY 1
ORDER BY count DESC
LIMIT 10

CLS가 가장 낮은 상위 10개 페이지 (p75)

# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  page_path,
  APPROX_QUANTILES(metric_value, 100)[OFFSET(75)] AS CLS,
  COUNT(1) as count
FROM (
  SELECT
    REGEXP_SUBSTR((SELECT value.string_value FROM UNNEST(event_params) WHERE key = "page_location"), r'\.com(\/[^?]*)') AS page_path,
    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
HAVING count > 50 # Limit to relatively popular pages
ORDER BY CLS DESC
LIMIT 10

디버그

이전 쿼리는 웹 바이탈 측정항목 데이터를 쿼리하는 방법을 보여줍니다. 이 데이터는 현재 실적과 시간이 지남에 따른 동향을 파악하는 데 도움이 됩니다. 하지만 실적이 예상보다 좋지 않지만 이유를 잘 모르겠다면 어떻게 해야 할까요?

조치를 취해 문제를 해결할 수 없다면 점수 가 얼마인지 알면 소용이 없습니다.

현장에서의 디버그 성능에서는 분석 데이터와 함께 추가 디버그 정보를 전송하는 방법을 설명합니다. 해당 게시물에 나와 있는 지침을 따르면 BigQuery에도 디버그 정보가 표시되는 것을 볼 수 있습니다.

쿼리 예

다음 쿼리는 debug_target 이벤트 매개변수를 사용하여 성능 문제의 근본 원인을 파악하는 방법을 보여줍니다.

CLS에 기여하는 주요 요소

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

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

다음 쿼리는 75번째 백분위수에서 CLS를 기준으로 가장 나쁜 페이지부터 가장 좋은 페이지까지, debug_target별로 그룹화하여 나열합니다.

# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  page_path,
  debug_target,
  APPROX_QUANTILES(metric_value, 100)[OFFSET(75)] AS CLS,
  COUNT(1) as count
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
HAVING count > 50 # Limit to relatively popular pages
ORDER BY CLS DESC

CLS에 기여하는 상위 요소의 쿼리 결과

페이지에서 어떤 요소가 바뀌는지 알면 문제의 근본 원인을 훨씬 더 쉽게 식별하고 해결할 수 있습니다.

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

다른 측정항목 디버깅

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

WHERE metric_name = 'INP'
WHERE metric_name = 'LCP'

각 Core Web Vitals 측정항목의 디버그 정보를 수집하고 전송하는 방법에 관한 안내는 현장에서 성능 디버그를 참고하세요.

시각화

쿼리 결과만 보고 유용한 정보를 얻는 것은 쉽지 않습니다. 예를 들어 다음 쿼리는 데이터 세트에서 LCP의 일일 75번째 백분위수 값을 나열합니다.

# 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
    FROM `bigquery_project_id.analytics_XXXXX.events_*`
    WHERE event_name in ('CLS', 'INP', '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
)
# Main query logic
SELECT
  event_date,
  metric_name,
  APPROX_QUANTILES(ROUND(metric_value, 2), 100)[OFFSET(75)] AS p75
FROM
  (
    SELECT
      event_date,
      metric_name,
      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 = 'LCP'
  )
GROUP BY
  1, 2
ORDER BY event_date

이러한 쿼리 결과에서 데이터만 보고 트렌드나 이상치를 파악하기는 어렵습니다.

일일 측정항목 값 쿼리 결과

이러한 경우 데이터를 시각화하면 유용한 정보를 더 빠르게 도출할 수 있습니다.

Looker Studio에서 쿼리 결과 시각화

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

BigQuery의 Looker Studio로 탐색 옵션

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

Looker Studio의 일일 LCP 값 선 차트

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

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

Looker Studio에서 BigQuery 기본 커넥터 사용

Web Vitals 데이터 구체화

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

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

다음 스크립트는 BigQuery 데이터 (소스 테이블)를 사전 처리하고 구체화된 테이블 (대상 테이블)을 만듭니다. 자체 데이터 세트에 이 쿼리를 사용할 때는 소스 테이블의 날짜 범위를 정의하여 처리되는 데이터 양을 줄이는 것이 좋습니다.

# Materialize Web Vitals metrics from GA4 event export data

# Replace target table name
CREATE OR REPLACE TABLE bigquery_project_id.ga4_demo_dev.web_vitals_summary
  PARTITION BY DATE(event_timestamp)
  CLUSTER BY metric_name
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
              `bigquery_project_id.analytics_XXXXX.events_*`
            WHERE
              event_name IN ('LCP', 'INP', '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 엔진을 사용하는 경우 이 테이블에서 최적화된 SQL 함수 및 연산자를 실행할 수 있습니다.

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

웹 바이탈 커넥터 사용

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

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

웹 바이탈 커넥터 승인 화면

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

대시보드를 탐색할 때 요약 탭에서 Web Vitals 측정항목의 일일 추세와 웹사이트의 일부 사용량 정보(예: 사용자 및 세션)를 확인할 수 있습니다.

사용자 분석 탭에서 측정항목을 선택한 다음 측정항목 백분위수와 다양한 사용량 및 비즈니스 측정항목별 사용자 수의 분석을 확인할 수 있습니다.

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

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

고급 사용법

데이터 세트에 익숙해지면 대시보드를 수정하고 자체 차트를 추가하여 더 풍부하고 타겟팅된 분석을 수행할 수 있습니다. 대시보드를 더 유용하게 만들려면 다음 단계를 따르세요.

  • BigQuery에서 예약된 쿼리를 설정하여 업데이트된 데이터를 가져옵니다. 이전에 실행한 구체화 쿼리는 그 순간의 데이터 스냅샷만 가져옵니다. 대시보드를 새 데이터로 계속 업데이트하려면 매일 실행되는 예약된 쿼리를 실행하고 구체화된 테이블에 새 데이터를 추가하면 됩니다.
  • 퍼스트 파티 데이터 (예: CRM)를 조인하여 유용한 비즈니스 정보를 확보합니다. materialized 테이블에서 user_id를 별도의 열로 추가할 수 있습니다. 이렇게 하면 퍼스트 파티 데이터를 조인할 수 있습니다. 퍼스트 파티 데이터가 아직 BigQuery에 없는 경우 데이터를 로드하거나 제휴 데이터 소스를 사용할 수 있습니다.
  • Google 애널리틱스로 전송하는 데이터에서 사이트 또는 앱 버전을 매개변수로 보고하고 이를 구체화된 테이블의 열로 추가합니다. 그런 다음 버전 데이터를 차트에 측정기준으로 추가하여 버전 변경사항이 실적에 미치는 영향을 더 쉽게 확인할 수 있습니다.
  • 직접 쿼리 또는 대시보드를 통해 데이터 세트를 상당히 많이 사용할 것으로 예상되는 경우 유료 버전의 BigQuery BI 엔진을 사용해 볼 수 있습니다.

요약

이 게시물에서는 Google 애널리틱스 4 및 BigQuery를 사용하여 현장에서 수집된 실제 사용자 데이터로 성능을 측정하고 디버그하는 방법을 간단히 설명했습니다. 또한 Looker Studio와 웹 바이탈 커넥터를 사용하여 자동 보고서와 대시보드를 빌드하여 데이터를 최대한 쉽게 시각화하는 방법도 설명했습니다.

이 게시물의 핵심 내용은 다음과 같습니다.

  • 실제 사용자 데이터로 실적을 측정하는 것은 사이트를 이해, 디버그, 최적화하는 데 중요합니다.
  • 성능 측정항목과 비즈니스 측정항목이 동일한 시스템에 있으면 더 깊이 있는 정보를 얻을 수 있습니다. Google 애널리틱스와 BigQuery를 사용하면 이러한 작업이 가능합니다.
  • 원시 Google 애널리틱스 데이터를 BigQuery로 내보내면 이미 알고 있는 쿼리 언어를 사용하여 심층적인 맞춤 분석을 무한대로 할 수 있습니다.
  • Google에는 Looker Studio와 같은 다양한 API 및 시각화 도구가 있어 원하는 방식으로 보고서를 자유롭게 빌드할 수 있습니다.