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

웹 바이탈 데이터를 Google 애널리틱스 4 속성으로 전송하고 BigQuery 및 Looker Studio에서 분석을 위해 데이터를 내보내는 방법을 알아보세요.

Google은 Search Console, PageSpeed Insights (PSI), Chrome 사용자 환경 보고서(CrUX)와 같은 다양한 도구를 제공하므로 개발자는 현장에서 실제 사용자의 코어 웹 바이탈 측정항목과 비교하여 사이트의 성능을 확인할 수 있습니다.

이러한 도구는 사이트의 실제 사용자 실적을 대략적으로 파악할 수 있으므로 매우 유용하며, 별도의 설정 작업 없이 사용을 시작할 수 있습니다.

그러나 사이트 성능을 측정하는 데 이러한 도구만 사용해서는 안 되는 몇 가지 중요한 이유가 있습니다.

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

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

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

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

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

여기에 설명된 모든 단계를 시각적으로 확인하려면 Google I/O '21 강연을 확인하세요.

측정

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

Google 애널리틱스 웹 인터페이스에는 강력한 분석 도구가 있지만 이미 알고 있는 쿼리 언어를 사용하여 원시 이벤트 데이터 액세스의 성능과 유연성을 능가하기가 어렵습니다.

Google 애널리틱스 4 및 BigQuery를 사용하여 코어 웹 바이탈 측정을 시작하려면 다음 세 가지 작업을 실행해야 합니다.

  1. Google 애널리틱스 4 속성BigQuery 프로젝트를 만듭니다.
  2. 수신하는 모든 데이터가 BigQuery 프로젝트 테이블에 자동으로 채워지도록 Google 애널리틱스 속성 구성에서 BigQuery Export를 사용 설정하세요.
  3. web-vitals JavaScript 라이브러리를 사이트에 추가하면 코어 웹 바이탈 측정항목을 측정하고 Google 애널리틱스 4로 데이터를 전송할 수 있습니다.

분석

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

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

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

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

웹 바이탈 데이터 쿼리

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

가장 중요한 점은 같은 페이지에서 같은 측정항목에 대해 여러 이벤트가 수신되는 경우도 있다는 것입니다. 이 문제는 측정항목 값이 변경되고 업데이트된 값이 보고되는 경우에 발생할 수 있습니다 (CLS에서 흔히 발생하는 현상).

웹 바이탈 이벤트의 경우 마지막으로 전송된 값이 항상 가장 정확하므로 분석을 수행하기 전에 해당 값만 필터링하는 것이 중요합니다. 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', '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
)

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

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

쿼리 예

전체 사이트의 75% 백분위수 (p75)의 LCP, FID, 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', '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
)
# 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', '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
)
# 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', '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
)
# 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개 페이지 (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', '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
)
# 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입니다.

다음 쿼리는 CLS 기준 가장 낮은 페이지부터 가장 높은 페이지 순으로 75번째 백분위수로 페이지 목록을 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', '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
)
# 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 및 FID의 디버그 대상에 관해 보고하는 데 정확히 동일한 기법을 사용할 수 있습니다. 다음과 같이 where 절을 관련 측정항목으로 바꾸기만 하면 됩니다.

WHERE metric_name = 'CLS'
WHERE metric_name = 'LCP'

각 코어 웹 바이탈 측정항목의 디버그 정보를 수집하고 전송하는 방법에 관한 안내는 필드의 디버그 성능을 참고하세요.

시각화

쿼리 결과만 보고 통찰력을 얻는 것은 쉬운 일이 아닙니다. 예를 들어 다음 쿼리는 데이터 세트에서 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', '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
)
# 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 네이티브 커넥터 사용

웹 바이탈 데이터 구체화

위에서 설명한 대로 웹 바이탈 이벤트 데이터의 대시보드를 만들 때 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', '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, 사용자 유형 (신규 및 재사용자), 세션 참여 정보를 열에서 직접 확인할 수 있습니다.
  • 테이블은 날짜별로 partitioned이 나뉘고 측정항목 이름을 기준으로 클러스터링됩니다. 이렇게 하면 일반적으로 각 쿼리에서 처리되는 데이터 양이 줄어듭니다.
  • 이 테이블을 쿼리할 때 와일드 카드를 사용할 필요가 없으므로 쿼리 결과가 최대 24시간 동안 캐시될 수 있습니다. 이렇게 하면 동일한 쿼리를 반복하는 데 드는 비용이 줄어듭니다.
  • BigQuery BI 엔진을 사용하는 경우 이 테이블에서 최적화된 SQL 함수 및 연산자를 실행할 수 있습니다.

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

웹 바이탈 커넥터 사용

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

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

웹 바이탈 커넥터 승인 화면

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

대시보드를 탐색하면서 요약 탭에서 웹 바이탈 측정항목의 일일 추세와 사용자, 세션 등 웹사이트의 사용 정보를 확인할 수 있습니다.

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

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

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

고급 사용법

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

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

요약

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

이 게시물의 몇 가지 핵심 요점:

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