▣ 배열과 구조체 기초

  • 배열을 간단히 설명하고, Array, Unnest를 함께 사용하면 쿼리, 함수 및 데이터 타입을 빠르게 실험할 수 있다.

■ 배열과 구조체 기초

  • 다음 쿼리로 문자열의 SPLIT 함수가 어떻게 작동하는지 확인한다.
SELECT
  city, SPLIT(city,' ') AS parts
FROM (
  SELECT
    *
  FROM
    UNNEST([ 'Seattle WA', 'New York', 'Singapore'
  ]) AS city
)

  • SQL 쿼리에 배열을 하드코딩할 수 있으므로 쿼리를 작성할 때 적절한 데이터셋을 찾거나 시간이 오래 걸리는 쿼리를 실행할 필요 없이 배열과 데이터 타입을 사용해 필요한 쿼리를 작성할 수 있다.

  • 여러 데이터를 빠르게 실험하는 다른 방법은 UNION ALL로 단일 행을 반환하는 SELECT 문을 결합하는 방법이다.
  • 이렇게 하면 대여 횟수가 2,000회 미만인 두 행의 데이터를 가진 작은 인라인 데이터셋을 만들 수 있다.
WITH
  example AS(
  SELECT
    'Sat' AS day, 1451 AS numrides, 1018 AS oneways
  UNION ALL SELECT 'Sun', 2376, 936
  UNION ALL SELECT 'Mon', 1476, 736
)
SELECT * FROM example
WHERE numrides < 2000


■ ARRAY_AGG로 배열 만들기

  • 성별 및 연도별 대여 횟수를 찾아보자
SELECT
  gender,
  EXTRACT (YEAR FROM starttime) AS year,
  COUNT(*) AS numtrips
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  gender != 'unknown' AND starttime IS NOT NULL
GROUP BY
  gender, year
HAVING
  year > 2016

SELECT 절을 보면 컬럼 이름 앞에 쉼표가 있는데, 그 이유는 무엇일까? 책이 처음 출간되었던 시점(2019년10월)의 빅쿼리 표준 SQL은 후행 쉼표를 지원하지 않았으므로, 쉼표를 다음 줄로 이동하면 행을 쉽게 재정렬할 수 있고, 특정 줄에 주석을 달아도 다음 예제와 같이 주석 처리된 줄 위 또는 아래의 줄에서 문법 오류가 발생하지 않으므로 쿼리가 여전히 잘 작동한다.

SELECT
 gender
, EXTRACT (YEAR FROM starttime) AS year
FROM etc

현재 빅쿼리는 후행 쉼표를 지원하므로, 굳이 선행 쉼표를 사용할 필요는 없다.

  • 여러 해에 걸쳐 성별과 관련된 대여 횟수를 시계열로 얻으려면 어떻게 해야 할까.
  • 대여 횟수의 배열을 만들어야 한다. ARRAY 타입을 사용해 해당 배열을 SQL로 표시하고 ARRAY_AGG 함수로 배열을 생성하면 된다.
SELECT
  gender,
  ARRAY_AGG(numtrips order by year) AS numtrips
FROM(
    SELECT 
     gender,
     EXTRACT (YEAR FROM starttime) AS year,
     COUNT(1) AS numtrips
     FROM
     `bigquery-public-data`.new_york_citibike.citibike_trips
     WHERE
      gender != 'unknown' AND starttime IS NOT NULL
     GROUP BY
      gender, year
    HAVING
      year > 2016
)
GROUP BY gender

  • 일반적으로 성별로 그룹화할 때는 AVG(numtrips) 함수로 같은 구룹의 단일 스칼라 값을 계산해 모든 연도의 평균 대여 횟수를 찾는다. 그런데 ARRAY_AGG를 사용하면 개별 값을 수집해 순서가 있는 리스트 또는 ARRAY에 넣을 수 있다.
  • ARRAY 타입은 다음과 같은 JSON 배열도 입력 데이터로 사용할 수 있다,
[
  {
    "gender": "male",
    "numtrips": [
      "9306602",
      "3955871"
    ]
  },
  {
    "gender": "female",
    "numtrips": [
      "3236735",
      "1260893"
    ]
  }
]
  • 이런 JSON 파일을 수집해서 테이블을 생성하면 numtrips 열이 ARRAY 유형인 테이블이 생성된다. 배열은 NULL이 아닌 요소의 순서 리스트다. 예를 들어 ARRAY <INT64>는 정수의 배열이다.

 

기술적으로 배열은 NULL 값을 가질 수 있지만 그 배열을 테이블에 저장할 수는 없다. 예를 들어 다음 쿼리는 결과를 보관하는 임시 테이블에 배열 [1, NULL, 2]를 저장하려고 하기 때문에 작동하지 않을 것이다.

WITH example AS(
    SELECT true AS is_vowel, 'a' AS letter, 1 AS position
    UNION ALL SELECT false, 'b', 2
    UNION ALL SELECT false, 'c', 3
)
SELECT ARRAY_AGG( IF(position = 2, NULL, position )) AS
positions FROM example

그러나 다음 쿼리는 NULL 값을 포함한 배열이 임시 테이블로 저장되지 않으므로 정상적으로 작동한다.

WITH example AS(
    SELECT true AS is_vowel, 'a' AS letter, 1 AS position
    UNION ALL SELECT false, 'b', 2
    UNION ALL SELECT false, 'c', 3
)
SELECT ARRAY_LENGTH( ARRAY_AGG( IF(position = 2, NULL, position )) FROM example

■ 구조체의 배열

  • 구조체는 순서를 갖는 필드의 그룹이다. 필드에는 원하는 이름을 지정할 수 있는데, 가독성을 위해서라도 이름을 지정하는 것이 좋다.
SELECT
[
    STRUCT('male' AS gender, [9306602,3955871] AS numtrips),
    STRUCT('female' AS gender, [3236735,1260893] AS numtrips)
] AS bikerides


■ 튜플

  • STRUCT 키워드와 필드 이름을 생략하면 튜플tuple 또는 익명 구조체anonymous struct가 생성된다. 빅쿼리는 쿼리 결과에서 이름이 지정되지 않은 컬럼 및 구조체 필드에 임의의 이름을 할당한다.
SELECT
[
    ('male', [9306602,3955871]),
    ('female', [3236735,1260893])
]

  • 컬럼 이름의 별칭을 생략하면 이후 쿼리는 읽기도 어렵고 유지보수도 어렵게 된다. 일회성의 실험용 쿼리를 작성하는 것이 아니라면 반드시 컬럼 이름을 사용한다.

배열 활용하기

  • 배열을 생성했다면 배열의 길이를 찾을 수도 있고 배열 내의 개별 항목을 탐색할 수도 있다.
SELECT
    ARRAY_LENGTH(bikerides) AS num_items,
    bikerides[OFFSET(0)].gender AS first_gender
FROM
(SELECT 
[
    STRUCT ('male' AS gender, [9306602,3955871] AS numtrips),
    STRUCT ('female'AS gender, [3236735,1260893] AS numtrips)
] AS bikerides)

  • 오프셋(OFFSET)은 0부터 시작하므로 OFFSET(0)은 배열의 첫 번째 값을 제공한다.

 배열 풀기

SELECT 
[
    STRUCT ('male' AS gender, [9306602,3955871] AS numtrips),
    STRUCT ('female'AS gender, [3236735,1260893] AS numtrips)
]

  • 이 쿼리에서 SELECT 절은 배열을 포함하는 행 1개만을 반환하므로, 두 성별 데이터가 같은 행에 반환된다.

  • UNNEST는 배열의 요소를 행으로 반환하는 함수로, 결과 배열을 풀면(UNNEST 하면) 배열의 각 항목에 해당하는 행을 가져올 수 있다.
SELECT * FROM UNNEST(
[
    STRUCT ('male' AS gender, [9306602,3955871] AS numtrips),
    STRUCT ('female'AS gender, [3236735,1260893] AS numtrips)
])


  • UNNEST는 from_item이므로 SELECT 절에서 사용할 수 있다. 또한 배열의 일부만 선택할 수도 있다. 예를 들어 다음 쿼리는 numtrips 컬럼의 값만 얻는다.
SELECT numtrips FROM UNNEST(
[
    STRUCT ('male' AS gender, [9306602,3955871] AS numtrips),
    STRUCT ('female'AS gender, [3236735,1260893] AS numtrips)
])

 

 집계

  • 앞서 살펴본 예제에서는 테이블의 모든 행으로부터 tripduration 컬럼의 값을 60으로 나눠 초를 분으로 변환했다. 하지만 집계 함수를 이용하면 모든 행의 값을 집계해 결과 집합에 하나의 행만 나타나도록 할 수 있다.

■ GROUP BY로 집계하기

  • 다음 쿼리는 남성 사용자의 평균 대여 시간을 찾는다.
SELECT
  AVG(tripduration/60) AS avg_trip_duration
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  gender = 'male'

  • 이 결과를 보면 뉴욕에서 남성 사용자가 평균적으로 자전거를 대여하는 시간이 약 13.4분임을 알 수 있다. 그러나 데이터셋은 지속적으로 갱신되므로 이 쿼리를 실행하는 시점의 결과는 다를 수 있다.

  • 여성 사용자의 경우, 위 쿼리로 2번 실행해도 되지만, 그러면 데이터셋에 2번 접근하면서 그만큼 낭비하게 된다. 이때는 WHERE절 대신 GROUP BY 절을 사용하면 된다.
  • 결과와 같이, 집계 함수는 ORDER BY 절에 지정한 그룹별로 적용된다. 이때 SELECT 표현식에 그룹을 구분하는 컬럼 (성별)의 값과 집계(AVG) 값을 표함하는 것도 가능하다. 실제 데이터셋에는 성별을 나타내는 값이 male, female, unknown 3가지라는 점을 유의할 것.
SELECT
  gender,
  AVG(tripduration/60) AS avg_trip_duration
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  tripduration IS NOT NULL
GROUP BY
  gender
ORDER BY
  avg_trip_duration


■ COUNT로 레코드 수 세기

  • 위 쿼리에서 계산한 평균값에 몇 건의 대여 기록이 포함되어 잇는지 궁금하다면 COUNT( ) 함수를 사용하면 된다.
SELECT
  gender,
  COUNT(*) AS rides,
  AVG(tripduration/60) AS avg_trip_duration
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  tripduration IS NOT NULL
GROUP BY
  gender
ORDER BY
  avg_trip_duration


■ HAVING으로 그룹화된 항목 필터링하기

  • HAVING 절을 사용하면 그룹화 연산 이후에 필터링을 할 수 있다. 평균 14분 이상 대여한 성별을 알고 싶다면 다음 쿼리를 사용한다.
  • WHERE 절에서 성별 또는 대여 기간을 필터링할 수 있지만, 평균 기간은 그룹화한 후 계산되므로 WHERE 절에서 평균 기간으로 필터링할 수 없다.
SELECT
  gender,
  AVG(tripduration/60) AS avg_trip_duration
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  tripduration IS NOT NULL
GROUP BY
  gender
HAVING
  avg_trip_duration > 14
ORDER BY
  avg_trip_duration


■ DISTINCT로 고윳값 찾기

  • 데이터셋의 성별 컬럼에 어떤 값이 저장되어 있는지 호막인하고 싶으면 GROUP BY를 사용할 수도 있지만 컬럼의 고윳값을 추출하고 싶으면 간단하게 SELECT DISTINCT를 사용한다.
SELECT DISTINCT 
  gender
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips


SELECT
  bikeid,
  tripduration,
  gender
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  gender = ""
LIMIT
  100

  • 쿼리 결과를 확인하면 성별 값이 누락되거나 저품질 데이터가 존재하는 것같다.
  • WHERE 절에서 NULL 값을 필터링할 때 NULL 값에 대한 비교 연산 (=, !=, <, >)은 NULL 값을 반환하므로 WHERE 조건으로 적절하지 않다. 대신 IS NULL 또는 IS NOT NULL을 사용하면 된다.

  • 성별의 고윳값을 구하기 위해 DISTINCT를 사용했던 쿼리를 다시 확인해 보면, DISTINCT는 성별 컬럼뿐만 아니라 SELECT 절에서 지정한 컬럼 전체에 적용된다.
SELECT DISTINCT
  gender,
  usertype
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  gender != ''

  • 쿼리의 결과는 6행이 반환된다. 즉 데이터셋에 존재하는 고유한 성별과 고유한 사용자 유형을 토대로 가능한 모든 조합에 해당하는 결과를 얻게 된다.

▣ 간단한 쿼리

  • 빅쿼리는 SQL:2011과 호환되는 SQL 언어를 지원하는데, 명세가 모호하거나 부족하면 빅쿼리는 기존 SQL 엔진에서 설정한 규칙을 따른다. 머신러닝같이 명세가 전혀 없는 영역에서는 빅쿼리가 자체 구문과 의미를 정의한다.

 쿼리 필수 요소

  • 빅쿼리는 구조화된 데이터와 준구조화된 데이터(JSON 객체 등)을 위한 영구 스토리지를 제공하는 우수한 데이터 웨어하우스이다. 영구 스토리지가 지원하는 기본적인 4가지 CRUD 작업은 다음과 같다.
    • Create
      • 새 레코드를 추가한다. SQL INSERT 문을 통한 로드 작업 또는 스트리밍 삽입 API를 통해 구현한다.
    • Read
      • 레코드를 검색한다. SQL SELECT 문과 벌크 read API로 구현한다.
    • Update
      • 기존 레코드를 수정한다. 빅쿼리의 데이터 조작 언어DML(Data Manipulation Language)의 일부인 SQL Update 및 Merge 문으로 구현된다.
    • Delete
      • 기존 레코드를 제거한다. DML 작업 중 하나인 SQL DELETE로 구현한다.

SELECT로 행 검색하기

  • Select 문을 사용하면 테이블에서 지정된 열의 값을 검색할 수 있다.
SELECT
  gender,
  tripduration
FROM
  `bigquery-public-data.new_york_citibike.citibike_trips`
LIMIT
  5

쿼리 실행 결과


  • LIMIT 제약 조건을 사용하면 쿼리 엔진이 처리할 데이터 양이 아니라 표시되는 데이터 양만 제한한다. 일반적으로는 쿼리에서 처리되는 데이터의 양에 따라 요금이 청구되므로 쿼리에서 읽는 컬럼이 많을수록 청구되는 요금이 많아진다. 처리되는 행의 수는 일반적으로 쿼리가 읽는 테이블의 전체 크기이며, 이츨 최적화하는 방법이 있다. 
  • bigquery-public-data.new_york_citibike.citibike_trips
    • 프로젝트 ID : bigquery-public-data
    • 데이터셋의 이름 : new_york_citibike
    • 테이블의 이름 : citibike_trips
  • 프로젝트, 데이터셋 및 테이블은 마침표( . )로 구분한다.
  • 예제의 경우, 프로젝트 이름(bigquery-public-data)의 하이픈이 뺼셈으로 해석되기 때문에 이스케이프 문자인 백틱( ` )을 사용해야 한다. 간단해 보이지만 이렇게 하면 테이블 이름(citibike_trips)을 별칭으로 사용할 수 없다는 단점이 있다. 따라서 프로젝트 이름만 백틱으로 둘러싸고 데이터셋과 테이블의 이름에는 하이픈을 사용하지 않는 습관을 기르는 것이 좋다.

■ AS로 컬럼 이름에 별칭 지정하기

  • AS 키워드를 이용하면 컬럼에 별칭을 지정할 수 있다.
    SELECT
      gender,
      tripduration AS rental_duration
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    LIMIT
      5​

쿼리 실행 결과


■ WHERE로 필터링하기

  • 대여 시간이 10분(600초) 미만인 대여 건을 찾으려면, Where 절을 사용해 Select가 반환한 결과를 필터링하면 된다.
SELECT
  gender,
  tripduration
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  tripduration < 600
LIMIT
  5

쿼리 실행 결과


  • Where 절을 Boolean expression을 사용할 수 있다. 예를 들어 대여한 사람이 여성이고 대여 기간이 5분에서 10분 사이인 대여 건만 찾으려면 다음 쿼리를 사용하면 된다.
    SELECT
      gender,
      tripduration
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    WHERE
      tripduration >= 300 AND tripduration < 600 AND gender = 'female'
    LIMIT
      5​

쿼리 실행 결과


  • Where 절의 표현식에는 OR 키워드와 NOT 키워드도 사용할 수 있다. 예를 들어 여성이 아닌 사용자 (남성 사용자 및 성별을 알 수 없는 사용자)를 찾으려면 Where절을 다음과 같이 작성한다.
    SELECT
      gender,
      tripduration
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    WHERE
      tripduration >= 300 AND tripduration < 600 AND NOT gender = 'female'
    LIMIT
      5​

쿼리 실행 결과


  • 괄호를 사용해 연산 순서를 제어할 수 있다. 다음 쿼리는 남성 사용자와 대여 시간이 10분 미만인 여성 사용자를 검색한다.
    SELECT
      gender,
      tripduration
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    WHERE
      (tripduration < 600 AND gender = 'female') OR gender = 'male'
    LIMIT
      5​

쿼리 실행 결과


  • WHERE 절은 FROM 절에 지정한 테이블의 컬럼을 참조한다. 따라서 WHERE 절에서는 SELECT 절에서 지정한 별칭을 참조할 수 없다. 즉 10분 미만의 대여를 찾을 때 다음과 같이 쿼리를 작성할 수 없다.
    SELECT
      gender,
      tripduration/60 AS minutes
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    WHERE
      minutes < 10 --WHERE에 별칭을 참조할 수 없다.
    LIMIT
      5​
  • 대신 WHERE 절에서 다시 한 번 변환을 실행해야 한다.
    SELECT
      gender,
      tripduration/60 AS minutes
    FROM
      `bigquery-public-data`.new_york_citibike.citibike_trips
    WHERE
      tripduration/60 < 10
    LIMIT
      5​

쿼리 실행 결과


■ SELECT *, EXCEPT, REPLACE

  • 비용 절감과 성능 향상을 위해서 원하는 컬럼만 선택하는 것이 좋다. 그러나 테이블에 있는 모든 컬럼을 선택해야 한다면 SELECT * 를 사용하면 된다.
SELECT
  *
FROM
  `bigquery-public-data`.new_york_citibike.citibike_stations
WHERE
  name LIKE '%Riverside%'

SELECT * 사용 쿼리


  • 이 예제의 WHERE 절에서는 LIKE 연산을 사용해 이름에 Riverside가 들어간 모든 대여소를 검색하고 있다. 일부 컬럼을 제외한 나머지 컬럼을 모두 선택하려면 SELECT EXCEPT를 사용한다.
  • 이 쿼리는 2개의 컬럼 (short_name 및 last_reported)만 제외할 뿐, 이전 쿼리와 동일한 결과를 반환한다.
SELECT
  * EXCEPT(short_name,last_reported)
FROM
  `bigquery-public-data`.new_york_citibike.citibike_stations
WHERE
  name LIKE '%Riverside%'

LIKE 연산 사용 쿼리


  • 모든 컬럼을 선택하면서 그중 한 컬럼의 값을 변환하고 싶다면 SELECT REPLACE를 사용한다. 예를 들어 대여 가능한 자전거 수에 5를 더하고 싶다면 다음과 같이 쿼리를 작성한다.
SELECT
  * REPLACE(num_bikes_available + 5 AS num_bikes_available)
FROM
  `bigquery-public-data`.new_york_citibike.citibike_stations

■ WITH를 사용한 서브 쿼리

  • 서브 쿼리를 사용하면 반복을 줄이고 별칭을 계속 사용할 수 있다.
  • 바깥쪽의 SELECT는 괄호로 묶은 안쪽의 서브쿼리의 결과를 참조한다. 안쪽 쿼리가 컬럼 별칭을 사용하고 있으므로 바깥 쿼리의 WHERE 절에서도 이 컬럼 별칭을 그대로 사용할 수 있다.
SELECT
  *
FROM (
  SELECT
    gender,
    tripduration / 60 AS minutes
  FROM
    `bigquery-public-data`.new_york_citibike.citibike_trips )
WHERE
  minutes < 10
LIMIT
  5

WITH를 사용한 서브 쿼리


  • 괄호가 있는 쿼리는 읽기가 어렵기 때문에 서브 쿼리의 부분을 WITH 절로 감싸는 방법이 더 좋다.
  • 빅쿼리에서 WITH 절은 이름이 있는 서브쿼리처럼 작동하며 임시 테이블을 생성하지 않는다. all_trips와 같이 FROM 절에서 사용할 수 있는 객체를 빅쿼리에서는 'from_item'이라 부른다. from_item은 테이블은 아니지만 테이블처럼 값을 선택할 수 있는 모든 데이터베이스 객체를 이르는 용어다.
WITH
  all_trips AS(
  SELECT
    gender,
    tripduration/60 AS minutes
  FROM
    `bigquery-public-data`.new_york_citibike.citibike_trips )
SELECT
  *
FROM
  all_trips
WHERE
  minutes < 10
LIMIT
  5

with절로 감싸는 쿼리 결과


■ ORDER BY로 정렬하기

  • ORDER BY 절로 쿼리 결과에 행이 반환되는 순서를 제어할 수 있다.
  • 기본적으로 출력 결과의 행은 정렬되지 않는다. 정렬에 사용할 컬럼을 지정하면 기본적으로 오름차순으로 정렬된다. 행을 내림차순으로 정렬하고 반환되는 행의 수를 5개로 제한하면 데이터셋에서 여성 사용자 중 대여 시간이 가장 긴 5개의 행이 반환된다.
  • ORDER BY 절은 SELECT 절이 실행된 이후에 실행되므로 별칭을 사용할 수 있다.
SELECT
  gender,
  tripduration/60 AS minutes
FROM
  `bigquery-public-data`.new_york_citibike.citibike_trips
WHERE
  gender = 'female'
ORDER BY
  minutes DESC
LIMIT
  5

ORDER BY로 정렬하기

데이터 처리 아키텍처

  • 구글 빅쿼리는 쿼리 엔진이 내장된 서버리스(serverless) 서비스로, 확장성이 높은 데이터 웨어 하우스이다. 내장된 쿼리 엔진은 수 테라바이트의 데이터에 대한 SQL 쿼리를 단 몇 초만에 처리할 뿐 아니라, 수 페타바이트의 데이터는 불과 몇 분이면 처리한다. 게다가 인프라를 별도로 관리하지 않고도 인덱스를 만들거나 재구성하지 않아도 성능을 얻을 수 있다.

관계형 데이터베이스 관리 시스템

  • MySQL, PstgreSQL 같은 OLTP (Online Transaction Processing) 데이터베이스의 주요 장점 중 하나는 SQL (Structured Query Language)를 지원한다는 점이다. 이 장점 덕분에 자바나 파이썬 같은 high-level 언어를 사용하지 않고 데이터 베이스 서버에서 쿼리만 실행하면 된다.
  • OLTP 데이터베이스의 장점 : SQL은 단순히 데이터베이스 컬럼에서 raw 데이터를 가져오는 것뿐만 아니라 그 이상의 작업도 가능하다. 파임스탬프 값을 파싱해서 연도와 월을 추출하며 집계, 일부 필터링, 그룹화 및 정렬을 수행할 수 있다. SQL을 사용해 얻을 수 있는 가장 주된 장점은 원하는 데이터만 명시하면 데이터베이스 소프트웨어가 쿼리를 실행하는 최적의 방법을 스스로 찾아낸다는 점이다.
  • OLTP 데이터베이스의 단점 : OLTP 데이터베이스는 데이터 기록과 동시에 읽을 수 있도록 데이터 일관성을 확보하는 데 최적화되어 있다. 이것이 가능한 이유는 데이터 무결성을 유지하기 위해 데이터를 매우 조심스럽게 잠그기 때문이다. 따라서 OLTP 데이터 베이스는 전체 데이터셋을 훑어야 하는 애드혹 쿼리 (필요에 따라 즉석에서 작성해 실행하는 쿼리) 실행에는 적합하지 않다.

맵리듀스 프레임워크

  • OLTP 데이터베이스는 전체 데이터셋의 순회가 필요한 애드혹 쿼리에 적합하지 않아서 순회가 필요한 특수 목적의 분석은 자바나 파이썬 같은 고수준 언어로 수행하였다. 2003년 Jeff Dean, Sanjay Ghemawt는 특수 목적의 연산을 두 단계로 추상화해서 복잡도를 낮췄다. 여기서 두 단계란 키/값 쌍을 처리해 중간 키/값 쌍을 생성하는 map 함수, 동일한 중간 키와 연관된 모든 중간 값을 병합하는 reduce 함수다. MapReduce라고 알려진 이 패러다임은 이후 아파치 하둡 (Apache Hadoop)의 발전을 이끌었다.

빅쿼리 : 서버리스, 분산 SQL 엔진

  • 빅쿼리는 서버리스 서비스이므로 인프라를 관리할 필요 없이 쿼리를 실행할 수 있다. 그리고 전체 데이터셋에 대한 집계를 수 초에서 수 분내에 처리하는 분석을 수행할 수 있다.

     ▶ 예제 1-1 뉴욕의 자전거 데이터셋에서 연도별 월별 편도 대여 수를 구하는 쿼리

SELECT EXTRACT(YEAR FROM starttime) AS year,
    EXTRACT(MONTH FROM starttime) AS month,
    COUNT(starttime) AS number_one_way
FROM
    `bigquery-public-data.new_york_citibike.citibike_trips`
WHERE
    start_station_name != end_station_name
GROUP BY year, month
ORDER BY year ASC, month ASC 

 

    ▶ 대여된 장비는 자전거로, 이 쿼리는 데이터셋에서 뉴욕의 매월 자전거 편도 대여 수를 합산한다. 이 쿼리로 2013년 7월 뉴욕시에서 자전거 편도 대여가 815,324번 있었음을 알 수 있다.


■ 빅쿼리로 작업하기

  • 빅쿼리는 어느 정도 중앙 집중화가 되어 있고 보편성을 가지는 데이터 웨어하우스다. 앞에서 본 쿼리는 단일 데이터셋에 적용하였는데, 빅쿼리의 장점은 다른 소스의 데이터셋을 조인하거나 빅쿼리 외부에 저장된 데이터에 쿼리할 때 빛을 발한다.

■ 여러 데이터셋에서 통찰력 도출하기

  • 뉴욕시의 자전거 대여 데이터와 미국 국립해양대기청의 날씨 데이터와 결합해 비 오는 날에 자전거 대여가 적은지 알아보자.

    ▶ 예제 1-3 비오는 날에 자전거 대여가 적은지 확인하기 위한 쿼리

WITH bicycle_rentals AS (
  SELECT
    COUNT(starttime) as num_trips, 
    EXTRACT(DATE from starttime) as trip_date
  FROM `bigquery-public-data.new_york_citibike.citibike_trips`
  GROUP BY trip_date
),

rainy_days AS
(
SELECT
  date,
  (MAX(prcp) > 5) AS rainy
FROM (
  SELECT
    wx.date AS date,   
    IF (wx.element = 'PRCP', wx.value/10, NULL) AS prcp
  FROM
   `bigquery-public-data.ghcn_d.ghcnd_2016` AS wx
  WHERE  
    wx.id = 'USW00094728'
)
GROUP BY 
  date
)

SELECT
  ROUND(AVG(bk.num_trips)) AS num_trips,
  wx.rainy
FROM bicycle_rentals AS bk
JOIN rainy_days AS wx
ON wx.date = bk.trip_date
GROUP BY wx.rainy

 

    ▶ 쿼리를 실행하면 뉴욕에서는 비가 올 때 자전거 이용률이 20% 정도 떨어진다는 사실을 알 수 있다.


■ ETL, EL, ELT

  • 데이터 웨어하우스를 사용하는 전통적인 방법은 ETL(추출, 변환, 로드) 프로세스로 시작한다. 이 프로세스는 소스 위치에서 raw 데이터를 추출하고 변환한 후 데이터 웨어하우스로 로드한다. 빅쿼리는 ETL을 매력적인 방법으로 만드는 매우 효율적인 컬럼 스토리지 포맷을 가지고 있다. 아파치 밤(Apache Beam) 또는 아파치 스파크로 만든 그림 1-2에서 볼 수 있듯, raw 데이터에서 필요한 만큼 추출하고 변환해 필요한 정리나 집계를 수행한 후 빅쿼리에 로드한다.

그림1-2 빅쿼리를 사용한 ETL 참조 아키텍처는 클라우드 데이터플로우에서 실행되는 아파치 빔 파이프라인을 사용해 스트리밍 및 배치 데이터를 하나의 코드로 모두 처리할 수 있다.

  • 아파치 빔이나 아파치 스파크로 ETL 파이프라인을 구축하는 것이 일반적이지만, 빅쿼리 하나로도 구축이 가능하다. 빅쿼리에서는 컴퓨팅과 스토리지가 분리되어 있어서 구글 클라우드 스토리지에 현재 저장된 CSV(또는 JSON, 아브로) 파일에 빅쿼리 SQL 쿼리를 실행할 수 있으며, 이 기능을 통합 쿼리라고 한다. 이 통합 쿼리를 사용해서 구글 클라우드 스토리지에 저장된 데이터로부터 SQL 쿼리로 데이터를 추출하고 해당 데이터를 변환한 다음, 그 결과를 빅쿼리 테이블로 저장할 수 있다.
  • 변환이 필요하지 않으면 빅쿼리는 CSV, JSON 또는 아브로 같은 네이티브 스토리지에 직접 접근할 수 있다. 즉 EL(추측 및 로드) 워크플로우도 가능하다. 데이터를 데이터 웨어하우스로 로드하는 이유는 네이티브 스토리지에 저장할 때 쿼리 성능이 가장 효율적이기 때문이다.
  • 가능하다면 EL 워크플로우를 설계하고, 변환이 필요한 경우에만 ETL 워크플로우로 넘어가는 것이 좋다. 변환이 SQL으로만 구현하기 어렵거나 파이프라인에 데이터를 빅쿼리로 스트리밍해야 한다면 아파치 빔 파이프라인을 만들어 클라우드 데이터플로우를 사용하는 서버리스 방식으로 실행한다. 빔/데이터플로우는 프로그래밍 코드이므로 이것으로 ETL 파이프라인을 구현하면 파이프라인을 지속적 통합(Continuous Integration) 및 단위 테스트 시스템과 통합이 잘 된다는 장점이 있다.
  • ETL. EL 워크플로우 외에도 빅쿼리는 ELT 워크플로우를 구현할 수 있다. ELT는 원시 데이터를 그대로 추출해 로드한 다음 빅쿼리 뷰로 해당 데이터를 즉시 변환하는 방법이다. 예를 들면 특정 시각을 현지 시각으로 수정해야 할지 결정하고자 행마다 값을 탐색해야 하는 경우가 이에 해당한다. ELT 워크 플로우는 프로토타이핑에 유용하다.
워크플로우 구조 사용해야 하는 경우
EL 구글 클라우드 스토리지의 파일에서 데이터를 추출한다.
빅쿼리의 기본 스토리지에 불러온다.
클라우드 컴포저, 클라우드 Function 또는 예약된 쿼리에서 실행한다.
히스토리 데이터를 배치 단위로 불러오는 경우.
정기적으로 로그 파일을 불러오는 경우 (: 하루에 한 번)
ETL /(Pub/Sup), 구글 클라우드 스토리지, 클라우드 스패너(Spanner), 클라우드 SQL 등에서 데이터를 추출한다.
클라우드 데이터플로우를 데이터를 변환한다.
데이터플로우 파이프라인을 통해 빅쿼리 테이블에 저장한다.
원시 데이터를 빅쿼리에 불러오기 전에 품질 관리, 변환 또는 보강해야 하는 경우.
데이터 불러오기가 지속적으로 필요한 경우.
CI/CD 시스템과 통합하고 모든 구성 요소에서 장치 테스트를 수행하려는 경우.
ELT 구글 클라우드 스토리지의 파일에서 데이터를 추출한다.
빅쿼리에서 원시 형식으로 데이터를 저장한다.
빅쿼리 뷰로 데이터를 즉시 변환한다.
데이터를 사용하려면 어떤 종류의 변환이 필요한지 아직 모르는 실험 데이터셋.
변환이 SQL로 표현될 수 있는 모든 프로덕션 데이터셋.

■ 강력한 분석

  • 데이터 웨어하우스는 저장된 데이터로 작업할 수 있는 분석의 종류가 많다는 장점이 있다. 빅쿼리 웹 UI에서 Search in Data Studio 버튼을 클릭하면 그림 1-3처럼 자전거 편도 대여의 월별 차이를 빠르게 시각화할 수 있다.
  • 빅쿼리는 배열 및 복잡한 조인을 포함한 SQL:2011의 모든 기능을 지원한다. 특히 배열 지원으로 중첩되거나 반복되는 필드를 평평하게 할 필요 없이 계층 데이터(JSON 레코드 등)를 빅쿼리에 저장할 수 있다.

그림 1-3 월별 편도 대여에 대한 데이터 스튜디오 시각화

  • 표준 SQL에 대한 또 다른 빅쿼리 확장 기능은 머신러닝 모델 생성과 배치 예측 작업을 지원한다. 요점은 빅쿼리에서 데이터를 내보내지 않고도 모델을 학습하고 예측할 수 있는 점이다.
  • 데이터 웨어하우스는 다양한 유형의 데이터를 저장할 수 있다. 빅쿼리는 숫자 및 텍스트 컬럼뿐만 아니라 지리 공간 데이터와 계층 데이터 등 다양한 유형의 데이터를 저장할 수 있다.
  • 빅쿼리는 배치 데이터와 스트리밍 데이터 수집 모두 지원한다. REST API를 통해 빅쿼리로 직접 데이터를 스트리밍할 수 있다.

관리의 단순함

  • 빅쿼리의 디자인 고려 사항 중 하나는 사용자가 인프라에 신경 쓰는 것보다 데이터로부터 얻을 수 있는 통찰력에 집중하도록 유도하는 것이다. 빅쿼리의 스토리지는 완전한 관리형이므로 데이터를 적재할 때 스토리지의 종류나 속도 및 비용의 트레이드오프를 생각할 필요가 없다. 
  • SQL 쿼리는 데이터셋의 모든 컬럼을 필터링하며, 빅쿼리는 필요한 쿼리 계획 및 최적화를 처리한다.
  • 쿼리는 자동으로 수천 대의 머신으로 확장되고 병렬로 실행된다. 이 대규모 병렬화를 활성화하고자 따로 작업을 수행하지 않아도 된다. 머신 자체가 작업의 여러 단계를 처리하도록 투명하게 프로비저닝되어 어떤 방식으로든 해당 머신을 설정할 필요가 없다.
  • 인프라스트럭처를 직접 구축하지 않아도 되므로 보안을 신경 써야 하는 번거로움도 줄어든다. 빅쿼리의 데이터는 저장 및 전송 시 자동으로 암호화된다. 빅쿼리를 사용하면 데이터베이스 관리 작업이 최소화되어서 분석가들이 데이터에서 통찰력을 얻는 데 집중할 수 있는 시간을 확보할 수 있다.

+ Recent posts