티스토리 뷰
[트러블슈팅] Silver를 일배치 → 15분 near-real-time으로 당기기
코딩하는 제리코 2026. 6. 23. 20:271. 왜 더 자주 돌리려 하나
지금 Silver는 일배치(하루 1번)다.
근데 대시보드 운영자는 더 신선한 데이터를 보고 싶어한다. 어제 숫자만 보이면 답답하니까. (내가 그랬다)
그래서 Silver를 15분 (또는 더 잘게)으로 당기는 작업을 했다.
참고: Bronze는 이미 60초 스트리밍이라 raw는 실시간에 가깝다. 신선도 병목은 Silver/Gold 배치라서 거기를 당기는 거다.
2. 빈도를 올리면 "스케줄만" 바꾸는게 끝이 아니었다.
`@daily → */15`로 한줄 바꾸면 될줄 알았지만, 3개의 레버가 같이 움직이는 거라 아니었다.
| 레버 | 일배치 | 15분 |
| 스케줄 | @daily | `*/15 * * * *` |
| 읽는 범위 | dt >= 오늘 - 7일 | 최근 N시간만(incremental) ← 안 바꾸면 매시간 7일 다시 읽어 낭비 |
| 쓰기 모드 | COW | COW 유지 (스케일 커질 시 MOR 고려) |
| compaction | - | 이미 인라인 self-throttle(지난 작업) |
| expire | 7일 보존 | 2일로 단축 (스냅샷 폭증) |
3. 결정 ① - incremental 읽기 (ingested_at 기준)
매 실행 7일 재독은 낭비다.
그래서 `--lookback-hours`를 추가해서 Bronze를 `ingested_at`(적재시각) 최근 N 시간만 읽게 했다.
if lookback_hours is not None:
df = df.where(F.col("ingested_at") >= F.expr(f"current_timestamp() - INTERVAL {N} HOURS"))
else:
df = df.where(F.col("dt") >= ...) # 기존 sliding 유지
lookback 관련 보충 설명
`--lookback-hours 3` = "지금 기준 최근 3시간 안에 적재된 것만 읽어라"
그니까 얼마나 거슬러 읽을지의 범위인것이다.
왜쓰냐? 자주 돌릴 때 매번 전체를 다시 읽는 낭비를 막기위해서 사용한다.
만약 lookback 없이 15분마다 7일치를 읽으면 아래와 같은 상황이 발생하게 된다.
12:00 run → 7일치(280만) 처리
12:15 run → 또 7일치(280만) 처리 ← 방금 한 거 또 함 (낭비) 하루 96번 = 같은 280만을 96번 재처리
그니까 이미 처리한 옛날 데이터를 매번 다시 MERGE하느라 느리고 낭비인거다. 자주 돌리는 의미가 희석된다.
'그래서 지난 사이 새로 들어온 것만' = 최근N시간 적재분만 읽는다. 각 run이 작아져서 빨라지고 15분 안에 여유있게 끝난다.
그래서 왜 lookback을 스케줄 간격(15분)보다 크게(3h)?
딱 15분으로 하면 run이 한 번 밀리거나 데이터가 좀 늦게 도착했을 때 그 사이 적재분을 놓친다(틈 생김).
그래서 간격보다 넉넉히(3h) 잡아 매 run이 이전 걸 좀 겹쳐 읽게 한다.
그니까 아래와 같은 상황이 있을 수 있다는 것이다.
12:00 run → 09:00~12:00 읽음 ┐ 09:15~12:00은 둘 다 읽음(겹침)
12:15 run → 09:15~12:15 읽음 ┘ → run 밀려도/데이터 늦어도 안 놓침
겹쳐 읽으면 중복 아닌가 싶겠지만 MERGE가 멱등하게 되기 때문에 이미 처이된건 no-op(처리 안함)
그래서 겹쳐도 결과가 똑같다.
4. 결정 ② - 쓰기는 COW 유지
=> 우선은 COW로 개발하였지만,, 사실 많은 데이터가 있는 것을 가정하기 때문에 MOR 로 전환을 해야한다..
다만 우선 아직 개발하지 못해서 문서로만 남겨둘 예정이다.
5. 결정 ③ - expire 보존 단축 (고빈도 부작용)
15분 x (MERGE + compaction)이면 스냅샷이 하루 수백 개 쌓인다.
그래서 GC DAG의 expire 보존을 7일 → 2일로 줄였다.
(time-travel 창도 2일로 줄지만, 백필은 raw 재처리라 무관)
과거를 다루는 두 가지 방법
과거로 돌아가는 걸 2일로 줄였으면, 옛날 데이터는 못고치는거 아닌가 싶었다.
근데 알고 보니 과거 데이터를 다루는 방법에 대한 정확한 이해가 없어 헷갈린거였다.
| 방법 | 개념 | 무엇에 의존 | 한계 |
| time-travel / rollback | 테이블을 과거 버전으로 보거나 되돌리기 |
스냅샷(버전 기록) | 보존기간만큼만 (지금은 2일) |
| backfill | Bronze raw를 다시 읽어 Silver를 새로 만들기 |
Bronze raw (영구 보존) | 무제한 (raw가 살아있으니 3개월 전도 OK) |
위에 보면 아래와 같은 문장이 있다.
(time-travel 창도 2일로 줄지만, 백필은 raw 재처리라 무관)
"time-travel 창도 2일로 줄지만"
- expire 보존을 7일 → 2일로 줄였으니, 그냅샷이 2일치만 남는다.
- 스냅샷이 time-travel의 재료니까 → "2일 전까지만"과거 조회, 롤백 가능 (7일전은 이제 못봄)
"백필은 raw 재처리라 무관"
- 근데 3개월전 데이터를 고쳐야 할 때 우리가 쓰는건 time-travel이 아니다.
- 백필 = Bronze raw (3개월 전 것도 영구 보존됨)를 다시 읽어 → 고친 Silver 로직으로 → 다시 만든다.
- → 스냅샷이 2일치만 있어도, Bronze raw가 살아 있으니 3개월 전이든 재생성 가능
6. 덤 - DAG 이름을 고쳤다 (`silver_processed` → `medallion`)
작업하다 보니 이름이 실제 DAG 구조와 안 맞았다.
`silver_processed`라는 DAG가 사실은 silver + gold + 양쪽 compaction = 메달리온 변환 전체를 돌고있었다.
medallion DAG:
silver_merge >> gold_aggregate >> compact_gold
silver_merge >> compact_silver
gold를 분리할까 고민했는데 굳이 라는 생각이 들었다.
gold는 silver 직후 같은 DAG에서 유기적으로 바로 갱신된다.
대시보드가 Gold를 읽으니 Gold도 15분 신선해야 맞다(분리 하면 cross-DAG 의존성만 복잡해진다)
그래서 한 DAG유지하고 이름만 `medallion`으로 정정했다.
'Projects > 광고 플랫폼 Lakehouse 실전 설계 with Iceberg' 카테고리의 다른 글
| 광고 이벤트 레이크하우스 구축기 — 파이프라인 단위 테스트 + CI 붙이기 (0) | 2026.06.24 |
|---|---|
| 광고 이벤트 레이크하우스 구축기 — 헬스체크 (쿼리부터 Slack 경보까지) (0) | 2026.06.24 |
| [트러블 슈팅] Iceberg Maintenance -- 현재 구현 & 개선 고민 (0) | 2026.06.23 |
| 광고 이벤트 레이크하우스 구축기 (6-1) — Silver 레이어: 설계와 전체 틀 (0) | 2026.06.20 |
| [개념] Glue Catalog는 왜 필요한가 - 내 Bronze 레이어가 카탈로그와 소통하는 전 과정 (0) | 2026.06.16 |
- Total
- Today
- Yesterday
- de
- RDD
- Data engineering
- lakehouse
- 테스트로직
- task
- Consumer DAG
- AWS
- DAG
- DataSet
- lake house
- airflow
- spark
- Prodcuder DAG
- Spark structured streaming
- kafka
- Data Pipeline
- Glue
- iceberg
- s3
- Data Engineerring
- Daynamic Task
- 데이터 파이프라인
- AWS Glue Catalog
- docker
- 메타코드부트캠프
- Data Dngineering
- Unity Catalog
- 데이터파이프라인
- Databricks
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |