백필
고친 사람 github-actions[bot]
백필은 지나간 기간의 데이터를 뒤늦게 채워 넣는 작업입니다. 데이터를 모아 가공하는 작업은 평소에 새로 들어온 몫만 처리합니다. 백필은 비거나 틀린 과거 기간에 같은 처리를 다시 돌려 메웁니다. 데이터베이스에 새 열을 만든 뒤 기존 행에 값을 채우는 일도 같은 이름으로 부릅니다.
쉽고 빠른 이해
백필은 지난 날짜의 결과를 나중에 채우는 일입니다. 매일 밤 어제 하루 치 매출을 집계하는 작업을 오늘 처음 켰다고 합시다. 지난달 매출은 비어 있습니다.
왜 하나 — 매일 도는 작업은 새로 들어온 날짜만 봅니다. 작업을 새로 만들었거나 틀린 계산을 고쳤을 때 지나간 날짜는 아무도 다시 계산해 주지 않습니다.
어떻게 도나:
- 채울 기간을 고릅니다. 예를 들어 지난 30일입니다
- 평소에 돌던 작업을 그 기간의 날짜마다 한 번씩 돌립니다
- 각 실행이 그 날짜의 결과를 새로 채웁니다
무엇이 나빠지나 — 과거를 한꺼번에 다시 읽으니 시간이 걸립니다. 원본 데이터베이스도 그만큼 바빠집니다. 작업이 결과를 덮어쓰지 않고 덧붙이게 짜여 있으면 같은 날짜가 두 벌 쌓입니다.
상세
가계부를 이번 달부터 쓰기 시작했다고 합시다. 지난 석 달 치도 보고 싶으면 서랍 속 영수증을 꺼내 그 달 칸에 하나씩 옮겨 적습니다. 적는 방법은 이번 달과 같습니다. 날짜만 지난달입니다.
파이프라인은 데이터를 읽고 고치고 저장하는 단계를 차례로 이어 놓은 처리입니다. 원본에서 주문 기록을 읽어 날짜별 매출로 묶은 뒤 결과 테이블에 쓰는 처리가 한 예입니다.
백필은 이 파이프라인이 평소에 처리하지 않은 지난 기간을 골라 같은 처리를 다시 돌리는 작업입니다. 일별 매출 집계를 오늘 새로 만들었다면 지난 1년 치 날짜마다 집계를 한 번씩 돌려 결과 테이블을 채웁니다.
영어 backfill 은 파낸 구덩이를 흙으로 다시 메운다는 말입니다. 데이터에서는 비어 있는 과거 기간이 그 구덩이입니다.
정기 실행이 맡는 기간
백필은 파이프라인이 평소 하던 일을 날짜만 바꿔 되풀이합니다. 이 소절은 파이프라인이 평소에 어떻게 도는지부터 봅니다.
데이터 파이프라인은 대개 정해 둔 시각마다 실행됩니다. 매일 새벽에 한 번 돌아 어제 하루 치를 처리하는 식입니다. 이렇게 정해 둔 시각마다 도는 실행을 정기 실행이라고 합니다. 정기 실행 한 번이 맡는 기간은 하루로 정해져 있습니다.
결과 테이블도 날짜로 나눠 두는 경우가 많습니다. 커다란 테이블을 기준 값에 따라 여러 조각으로 나눴을 때 그 조각 하나를 파티션이라고 부릅니다. 날짜로 나눈 테이블이라면 하루 치 결과가 파티션 하나에 들어갑니다. 실행 한 번은 파티션 하나만 쓰고 끝납니다. 그러니 날짜끼리 서로 건드리지 않습니다.
실행 하나를 이루는 단계 하나하나가 태스크입니다. 태스크가 처리할 날짜를 인자로 받으면 같은 태스크를 어느 날짜로든 돌릴 수 있습니다. 백필은 이 성질에 기댑니다.
반대로 태스크 안에서 지금 시각을 읽으면 백필이 망가집니다. 태스크를 06-02(6월 2일)로 돌렸다고 합시다. 그런데 코드가 「어제」를 지금 시각에서 구하면 오늘 기준의 어제를 처리합니다. 처리할 날짜는 언제나 인자로 받아야 합니다.
flowchart TD
S["정기 실행 · 어제 하루 치"] --> T["태스크 · 날짜 하나를 받아 처리"]
B["백필 · 지난 날짜 목록"] --> T
subgraph 결과 테이블
P1["06-01 파티션"]
P2["06-02 파티션"]
P3["06-03 파티션 · 어제"]
end
T -->|정기 실행| P3
T -->|백필| P1
T -->|백필| P2
두 입구가 같은 태스크를 부릅니다. 정기 실행은 어제 파티션 하나를 채웁니다. 백필은 날짜 목록을 넘겨 그보다 앞선 파티션들을 채웁니다.
백필이 필요해지는 때
지난 기간이 비거나 틀리는 사정은 여럿입니다. 이 소절은 흔한 사정을 표로 모읍니다. 그 사정들이 어떻게 한 이름으로 묶이는지도 봅니다.
| 때 | 무엇이 비거나 틀렸나 |
|---|---|
| 파이프라인을 새로 만들었다 | 만든 날 이전의 결과가 하나도 없다 |
| 계산 로직의 버그를 고쳤다 | 고치기 전 날짜에 틀린 값이 남아 있다 |
| 결과에 새 열을 더했다 | 지난 날짜의 결과에는 그 열이 비어 있다 |
| 장애로 며칠 실행이 멈췄다 | 멈춘 동안의 날짜가 빠져 있다 |
| 원본 데이터가 늦게 도착했다 | 그 날짜를 처리할 때 원본이 모자랐다 |
파이프라인을 만들기 전의 날짜와 실행이 멈췄던 날짜는 결과가 아예 없습니다. 백필이 그 날짜의 파티션을 처음으로 채웁니다.
버그를 고쳤거나 원본이 늦게 온 날짜는 결과가 있지만 틀렸습니다. 원본이 늦게 온 날짜는 모자란 원본으로 계산했기 때문에 틀린 것입니다. 백필이 그 날짜의 파티션을 다시 계산해 덮어씁니다.
새 열을 더하기 전의 날짜도 같은 방법으로 채웁니다. 그 날짜의 파티션을 다시 계산하면 새 열까지 값이 들어갑니다.
어느 쪽이든 지난 날짜로 같은 처리를 돌린다는 점은 같습니다. 그래서 모두 백필입니다.
다시 돌려도 한 벌만 남는 태스크
백필은 이미 결과가 있는 날짜를 다시 돌리는 일이 잦습니다. 이 소절은 그때 결과가 두 벌 쌓이지 않으려면 태스크를 어떻게 짜야 하는지 코드로 봅니다. 두 코드에서 total(day) 는 그 날짜의 매출 합계를 구하는 함수입니다.
먼저 결과에 줄을 덧붙이는 태스크입니다.
rows = []
def run(day):
rows.append((day, total(day)))
run("06-02") # 정기 실행
run("06-02") # 백필로 한 번 더
len(rows) # 2
같은 날짜로 두 번 돌자 06-02 의 줄이 둘이 됐습니다. 이 줄들로 그날 매출을 더하면 두 배로 잡힙니다. 한 번 반영될 결과가 두 번 쌓이는 이 고장을 중복 처리라고 부릅니다.
다음은 날짜를 키로 삼아 그 날짜의 값을 덮어쓰는 태스크입니다.
sales = {}
def run(day):
sales[day] = total(day)
run("06-02") # 정기 실행
run("06-02") # 백필로 한 번 더
len(sales) # 1
몇 번을 돌려도 06-02 의 값은 하나입니다. 테이블이라면 그 날짜의 파티션을 비우고 새로 채우는 것이 이 코드에 해당합니다.
같은 일을 여러 번 해도 결과가 한 번 한 것과 같은 성질을 멱등성이라고 부릅니다. 두 번째 태스크는 멱등합니다. 첫 번째 태스크는 멱등하지 않습니다. 멱등하지 않은 태스크로 백필하려면 돌리기 전에 그 기간의 결과를 먼저 지워야 합니다.
여러 날을 한꺼번에 돌리는 방법
백필은 날짜 수만큼 실행이 생깁니다. 1년 치면 365번입니다. 이 소절은 그 많은 실행을 어떤 순서와 속도로 흘려보내는지 봅니다.
날짜끼리 서로 기대지 않으면 여러 날을 동시에 돌릴 수 있습니다. 동시에 많이 돌릴수록 빨리 끝납니다. 대신 원본 데이터베이스가 한꺼번에 많은 읽기를 받습니다.
원본이 사용자 요청을 받는 서비스의 데이터베이스라면 그 읽기가 사용자 요청을 느리게 만듭니다. 그래서 동시에 도는 실행 수에 상한을 둡니다.
앞 날짜의 결과에 기대는 계산은 순서대로 돌려야 합니다. 누적 가입자 수처럼 어제 값에 오늘 늘어난 수를 더하는 집계가 그렇습니다. 06-02 를 고치면 그 뒤 날짜의 누적값도 모두 다시 계산해야 합니다.
태스크를 순서대로 띄우고 실패를 지켜보는 프로그램이 오케스트레이션 도구입니다. 이런 도구에는 흔히 백필 명령이 따로 있습니다. 시작 날짜와 끝 날짜를 주면 날짜마다 실행을 하나씩 만듭니다. 그 실행들을 정해 준 동시 실행 수와 순서에 맞춰 돌립니다.
테이블 열을 채우는 백필
데이터베이스를 다루는 쪽에서도 같은 이름을 씁니다. 이 소절은 테이블에 새 열을 만들었을 때 기존 행을 채우는 백필을 봅니다.
테이블에 새 열을 더하면 이미 있던 행에는 그 열의 값이 없습니다. 새로 들어오는 행은 애플리케이션이 값을 넣습니다. 기존 행은 누군가 따로 채워야 합니다. 이 채우는 작업도 백필입니다.
데이터베이스는 고치는 행에 락을 걸어 다른 쓰기가 끼어들지 못하게 막습니다. 수백만 행을 한 번에 고치면 그 락을 오래 잡습니다. 그동안 같은 행을 쓰려는 다른 요청은 기다립니다. 그러니 행을 작은 묶음으로 나눠 조금씩 고칩니다.
테이블 구조를 바꾸는 작업이 스키마 마이그레이션입니다. 열 백필은 흔히 그 가운데 한 단계로 들어갑니다. 순서는 대개 이렇습니다.
- 값이 비어도 되는 열로 먼저 만듭니다
- 애플리케이션이 새 행에 그 열의 값을 넣기 시작합니다
- 기존 행을 묶음으로 나눠 채웁니다. 이것이 백필입니다
- 다 채운 뒤에 「비어 있으면 안 된다」는 제약을 겁니다
넷째를 먼저 하면 아직 안 채운 기존 행이 그 제약에 걸립니다. 그래서 백필이 제약보다 앞에 섭니다.
백필과 헷갈리는 처리 방식
지난 데이터를 다시 다루는 일은 이름이 여럿입니다. 무엇을 처리하는지와 언제 돌리는지로 셋을 가르면 이렇습니다.
| 이름 | 무엇을 처리하나 | 언제 돌리나 |
|---|---|---|
| 재시도 | 방금 실패한 실행 하나 | 실패 직후에 자동으로 |
| 백필 | 사람이 고른 지난 기간 | 파이프라인을 만들거나 고친 뒤에 |
| 증분 처리 | 지난 실행 뒤에 새로 들어온 데이터 | 정기 실행마다 |
재시도는 실패한 실행을 되살립니다. 백필은 이미 성공한 날짜까지 일부러 다시 돌릴 수 있습니다.
증분 처리는 정기 실행의 방식입니다. 새로 들어온 것만 보니 빠릅니다. 그 대신 지나간 기간은 다시 보지 않습니다. 백필은 증분 처리가 지나쳐 간 기간을 메웁니다.
관련 항목
백필이 과거 날짜로 다시 돌리는 처리
파이프라인 · 데이터 파이프라인 · 태스크 · 워크플로 · DAG · 배치 처리 · ETL · ELT
백필이 채우는 저장 구조
파티션 · 파티셔닝 · 테이블 · 열 · 행 · 데이터 웨어하우스
백필을 안전하게 만드는 성질과 기법
백필이 잘못 돌 때 나는 장애
백필을 띄우고 지켜보는 도구
오케스트레이션 · Apache Airflow · Dagster · 크론
백필과 맞세워지는 처리 방식
재시도 · 재처리 · 증분 처리 · 스트리밍 처리
백필을 부르는 데이터 사정
지연 도착 데이터 · 데이터 품질 · 데이터 계보 · 장애 복구
열 백필이 끼는 스키마 변경
스키마 마이그레이션 · 스키마 진화 · 확장과 축소 · 온라인 스키마 변경 · ALTER TABLE
백필을 다루는 분야와 역할
다른 이름: backfill · backfilling · 소급 적재