Skip to content
isdnetworks
Go back

세 집계가 같은 구간을 다시 셌다

일간과 주간과 월간 통계를 각각 만들었는데 셋이 각자 원본을 훑고 있었다. 원본이 커지면서 세 배치가 같은 구간을 반복해 읽는 것이 문제가 됐다.

Table of contents

Open Table of contents

구간이 겹쳐 있었다

각 배치의 조회 조건을 나란히 놨다.

일간  reg_date >= 오늘 00:00
주간  reg_date >= 7일 전
월간  reg_date >= 30일 전

어제 자료는 세 배치가 다 읽고 오늘 자료도 셋 다 읽는다. 30일치가 400만 행인데 월간 배치가 매번 그 400만 행을 전부 훑고 있었다.

겹치지 않게 나눴다

주간은 일간 결과를 쓰고 월간은 주간 결과를 쓰게 했다.

일간   원본 → stat_daily      (오늘 것만)
주간   stat_daily → stat_weekly   (7행)
월간   stat_daily → stat_monthly  (30행)

주간이 원본을 안 보고 stat_daily 의 7행만 본다. 월간도 stat_daily 30행을 보는데 주간을 거치면 주 경계가 월 경계와 안 맞아서 일간에서 바로 갔다.

읽는 양이 400만 행에서 30행으로 줄면서 월간 배치가 도는 시간도 그만큼 짧아졌다.

합칠 수 있는 값과 없는 값

다만 모든 값이 이렇게 합쳐지는 것은 아니었다.

합칠 수 있나
건수SUM
합계SUM
최소·최대MIN·MAX
평균개수로 가중해야 함
순 방문자 수불가

AVG 는 단순 평균이 안 되고 일별 건수가 다르면 틀린다.

SELECT SUM(sum_v) / SUM(cnt) AS avg_v FROM stat_daily WHERE ...;

합계와 개수를 저장해 두고 나중에 나눈다. 순 방문자 수는 아예 안 되는데 어제 온 사람이 오늘도 오면 이틀 합이 두 배가 아니다. 이것은 원본을 봐야 해서 따로 뒀다.

다시 계산할 수 있게 만들었다

stat_daily 가 틀리면 주간과 월간도 틀리므로 다시 계산하는 방법이 필요했다.

def rebuild_daily(d):
    cur.execute("DELETE FROM stat_daily WHERE stat_date = %s", (d,))
    cur.execute("""INSERT INTO stat_daily ... WHERE DATE(reg_date) = %s""", (d,))

def rebuild_range(start, end):
    d = start
    while d <= end:
        rebuild_daily(d)
        d += timedelta(days=1)
    rebuild_weekly(start, end)
    rebuild_monthly(start, end)

날짜 범위를 주면 그 구간을 다시 만든다. DELETE 하고 INSERT 하는 방식이라 여러 번 돌려도 결과가 같다.

늦게 들어온 자료도 있었다. 장치가 통신이 끊겼다가 나중에 몰아서 보낸다.

rebuild_daily(today - timedelta(days=1))
rebuild_daily(today)

오늘 집계할 때 어제 것도 다시 계산하게 했다. 이틀치만 다시 하면 대부분 잡히고 더 늦게 오는 것은 드물어서 주간 집계 때 일주일치를 다시 계산했다.

검증 — 실행 순서와 검산

셋을 같은 시각에 돌리면 겹친다.

02:00  일간
02:10  주간   (일간이 끝난 뒤)
02:20  월간   (일간이 끝난 뒤)

주간과 월간은 stat_daily 를 쓰므로 그 뒤여야 한다. 10분 간격을 준 것은 일간이 늦어질 수 있어서인데 더 확실하게 하려면 앞 배치의 완료를 확인하게 한다.

def wait_for_daily(d, timeout=600):
    waited = 0
    while waited < timeout:
        cur.execute("SELECT COUNT(*) FROM stat_daily WHERE stat_date=%s", (d,))
        if cur.fetchone()[0] > 0:
            return True
        time.sleep(10); waited += 10
    return False

상한을 두고 기다리고 안 끝나면 실패로 끝낸다.

일간 합과 월간 값이 맞는지도 확인했다.

SELECT
  (SELECT SUM(cnt) FROM stat_daily WHERE stat_date BETWEEN '2016-09-01' AND '2016-09-30') AS from_daily,
  (SELECT cnt FROM stat_monthly WHERE stat_month = '2016-09') AS monthly;

두 값이 같아야 하는데 처음에 달랐다. 월 경계에서 하루가 빠져 있었고 BETWEEN 의 끝 조건이 시각까지 비교돼서 마지막 날 자료가 빠진 것이었다.

단계를 나누면 검산할 수 있는 지점이 생긴다. 한 번에 계산하면 맞는지 확인할 방법이 없다.

정리


Share this post on:

Previous Post
데이터시트를 처음 읽을 때
Next Post
해 본 적 없는 일의 기간을 잡았다