TreasureData에서 효과적인 중간 테이블 활용 예

4103 단어 TreasureData
TreasureData에서는 time 컬럼을 이용해 SQL로 취득하는 데이터 기간을 지정하는 것으로, 불필요한 데이터 I/O를 막아, 효율적으로 처리를 실시할 수가 있습니다.
그러나, 전 기간의 데이터를 읽는 집계를 많이 실시하는 경우, 데이터의 I/O에 많은 시간이 걸려 버리는 등에 의해, 자원을 유효하게 활용할 수 없다고 하는 위험성이 있습니다. 따라서 모든 데이터 읽기가 필요한 집계의 공통 처리 결과를 사전에 중간 테이블로 다른 테이블에 저장해두면 리소스를 효과적으로 활용할 수 있게 됩니다.

중간 테이블의 사용 예



예를 들어 모든 사용자마다 사이트에 대한 첫 번째 액세스 시간과 마지막 액세스 시간을 찾는 경우가 있습니다.
서비스 수가 늘어남에 따라 2012년에 마지막으로 방문한 사용자와 2017년에 마지막으로 방문한 사용자 등 다양한 사용자가 나옵니다. 단순히 위의 요구 사항을 요구하기 위해 쿼리를 작성하면 2012년부터 2017년의 데이터에 대해 처음과 마지막 액세스 시간을 요구하는 쿼리를 작성하게 됩니다. 달에 수십억 PV 있는 사이트의 액세스 로그에 대해, 이러한 쿼리를 수십 종류나 hourly로 던져져 버린 날에는 아무리 자원이 있어도 괴로운 기분이 되네요.

중간 테이블을 만들 때까지의 흐름


  • 초기 데이터 작성을 위해 중간 테이블 작성 쿼리를 실행 대상 테이블의 전날까지의 데이터에 대해서 최초로 실시하고, 다른 테이블에 결과를 써
  • 데이터 업데이트를위한 일정 쿼리를 등록하고 대상 테이블과 중간 테이블을 기반으로 주기적으로 중간 테이블에 데이터를 삽입합니다.
  • 집계 쿼리를 수행하고 중간 테이블에서 집계합니다.



    1. 첫 번째 집계 쿼리를 실행하는 명령



    first_access_time: user_id가 처음 액세스한 시간
    last_access_time: user_id가 마지막으로 액세스한 시간
    sample_db: 작업용 DB
    access: 원래 테이블
    snapshot_access: 중간 테이블

    TD의 Hive 쿼리를 예로 든다. 아래에서는 2017년 3월 1일 시점에서 snapshot을 작성하고 있으며, 그 날의 모든 유저의 최초로 액세스한 날과 마지막으로 액세스한 날이 저장되어 있습니다.
    INSERT INTO TABLE snapshot_access
    SELECT * FROM (
    SELECT
      TD_TIME_PARSE('2017-03-01','JST') AS time,
      user_id,
      TD_FIRST(time, time) AS first_access_time,
      TD_LAST(time, time) AS last_access_time
      FROM access
      WHERE TD_TIME_RANGE(time, null, '2017-03-01', 'JST')
      GROUP BY user̲id
    ) all
    

    2. 중간 테이블 작성을 위해 매일 1시에 실행되는 스케줄 쿼리



    스케줄 쿼리로 다음을 등록합니다. (지금이라면 TD Workflow(digdag) 등을 사용해도 좋네요.)
    서브쿼리의 첫째는 access 테이블의 전일분 액세스가 있던 유저의 최초의 액세스일과 마지막 성공일의 집계를 실시합니다.
    또 다른 하위 쿼리가 포인트로 작성된 snapshot_access에서 며칠 분 모은 첫 번째 액세스 날짜와 마지막 액세스 날짜의 로그를 추출하고 위와 UNION ALL하여 다시 모든 사용자의 첫 번째 및 마지막 액세스 날짜를 요청합니다.
    이것을 하면 snapshot_access의 1일분은 반드시 전체 유저 ID분의 로그 밖에 쌓이지 않기 때문에, 매번 전 기간을 집계하지 않아도 최초와 마지막 액세스일을 추출할 수 있게 됩니다.
    -- aggregate data from (aggregated historical aggregated data) + (aggregated todays data)
    INSERT INTO TABLE snapshot_access
    SELECT
      TD_SCHEDULED_TIME() AS time,
      user̲id,
      TD_FIRST(first_access_time, first_access_time) AS first_access_time,
      TD_LAST(last_access_time, last_access_time) AS last_access_time
    FROM (
      -- aggregates todays data
      SELECT
        user_id,
        TD_FIRST(time, time) AS first_access_time,
        TD_LAST(time, time) AS last_access_time
        FROM access
      WHERE TD_TIME_RANGE(time, TD_TIME_ADD(TD_SCHEDULED_TIME(), '-1d', 'JST'), TD_SCHEDULED_TIME(), 'JST')
      GROUP BY user_id
    
      UNION ALL
    
      -- aggregate historically aggregated data
      SELECT
        user_id,
        TD_FIRST(first_access_time, time) AS first_access_time,
        TD_LAST(last_access_time, time) AS last_access_time
      FROM snapshot_access
      WHERE TD_TIME_RANGE(time, TD_TIME_ADD(TD_SCHEDULED_TIME(), '-5d', 'JST'), TD_SCHEDULED_TIME(), 'JST')
      GROUP BY user_id
    ) historical_data_plus_todays_data
    GROUP BY user_id
    

    3. 집계 쿼리 예



    마지막으로 snapshot_access를 사용하는 경우 아래와 같은 쿼리를 작성합니다.
    매일 신규 고유 사용자 수를 계산하는 예 :
    SELECT
    first_access_time AS day,
    COUNT(1) AS uu
    FROM
    (
      SELECT
        user_id,
        TD_TIME_FORMAT( TD_FIRST(first_access_time, first_access_time), 'yyyy‒MM‒dd', 'JST') AS
        first_access_day
      FROM snapshot_access
      WHERE TD_TIME_RANGE(time, TD_TIME_ADD(TD_SCHEDULED_TIME(), '-1d', 'JST'), NULL, 'JST')
      GROUP BY user_id
    ) first_access_table
    WHERE
      first_access_time = TD_TIME_FORMAT(TD_SCHEDULED_TIME(), 'yyyy‒MM‒dd', 'JST')
    GROUP BY first_access_time
    

    요약



    중간 테이블을 사용하면 효율적으로 처리할 수 있게 된다. 라는 이야기였습니다.
    한편으로 역기로 해결하는 방법도 있습니다만, 워크플로우 시스템이 편리하게 사용할 수 있게 되어 있기 때문에, 효율적인 처리 플로우로 할 수 있는 곳은 그러한 것이 좋네요.
  • 좋은 웹페이지 즐겨찾기