며칠치로는 규모를 증명 못 해서 1년치를 만들었고, 축을 돌려 다시 쟀습니다
목차
0. 초 단위라는 착각에서 출발하다
여기까지 오면서 저는 꽤 많은 걸 실측했습니다. 게이트가 반쪽 데이터를 막고 알림이
울고 계약이 빌드를 막고 deadman이 침묵을 잡는 걸 전부 라이브로 돌려 봤습니다. 그런데
그 모든 실측에는 공통점이 하나 있었습니다. 닫힌 dt가 3개뿐이었습니다. 07-05,
07-06, 그리고 진행 중인 07-07. 수십만 행. 그 규모에서는 뭘 재도 전부 초 단위로
끝납니다. dbt 빌드도 몇 초, 게이트도 몇 초.
그러다 문득 이런 생각이 들었습니다. “이걸로 ‘규모에서도 버틴다’고 말할 수 있나?”
없었습니다. 그냥 희망이었습니다. 특히 마트를 다시 들여다보니 마음이
불편했습니다. fct_query_daily는 매일 전체 이력을 다시 계산합니다. 하루 발생량을
그날 파티션의 양 끝 차분으로 구하는데, dbt는 그냥 전체를 table로 다시 굽기 때문입니다.
이력이 3일이라 안 아팠을 뿐이지, 1년이 쌓이면 어떻게 될지 저는 몰랐습니다. 모르는
걸 “빠를 거예요”라고 쓰면 그건 지어낸 수치입니다.
두 번째로 마음에 걸린 건 롤링 윈도우였습니다. 0편에서 제가 던진 질문은 “이번 달 vs
지난달 느려진 쿼리 있어?”였는데, mart_query_regression은 “전체 이력의 첫 활동일 vs
마지막 활동일”을 비교합니다. 이력이 3일일 땐 그럴듯했습니다. 그런데 1년이 쌓이면 그건
“1년 전 대비 지금”이 됩니다. 제가 답하려던 질문이 아닙니다.
세 번째는 화면이었습니다. 파이프라인 상태를 보는 수단이 알림(실패하면 운다)과
heartbeat(성공이 안 남으면 운다)뿐인데, 둘 다 이벤트입니다. “지금 전반적으로 건강한가”를
한눈에 보는 화면이 없었습니다.
1. 먼저 재보고 수치가 요구할 때만 판단한다
여기서 제가 안 하려고 한 게 있습니다. 증분 모델부터 도입하는 것. “전체 재빌드는
느리다더라, 그러니 microbatch를 쓰자”는 감으로 최적화하는 것입니다. 그건 이 프로젝트가
DBTower에서 계승한 원칙(“정직한 필요에서 시작한다”)에 어긋납니다. 문제가 있는지도
모르는 곳을 미리 최적화하면, 복잡도만 늘고 정작 진짜 병목은 놓칩니다.
그래서 순서를 뒤집었습니다. 먼저 1년치를 실제로 만들어 어디가 먼저 무너지는지 재고,
그 수치가 요구할 때만 최적화한다. 만약 규모에서도 전체 재빌드가 여전히 빠르면,
“지금은 증분을 도입하지 않는다”를 그 수치로 정당화하고 넘어갑니다.
2. 1년치는 어떻게 만드나, 격리가 전부다
문제가 하나 있었습니다. 원천 수집기는 07-07에 멈췄고(원천 시계로 이미 지난 날들),
7일 보존이라 과거를 다시 뽑을 수도 없습니다. 그리고 이 프로젝트엔 철칙이 있습니다. 원천을
오염시키지 않는다, 실데이터를 건드리지 않는다.
그래서 합성 데이터는 이렇게 만들었습니다. 닫힌 dt=2026-07-05의 6개 인스턴스 parquet
(149,259행)를 메모리로 읽어, 그걸 365개 날짜 경로로 복제합니다. 서버 부하 0,
원천 PG 무접촉입니다. 이미 MinIO에 있는 parquet를 날짜만 바꿔 다시 쓰기 때문입니다.
쓰는 곳도 실데이터 raw/를 놔두고 **별도 프리픽스 scale/**에 씁니다. 물리적으로
격리했고, 끝나면 프리픽스를 통째로 지워 원상 복구할 수 있게 했습니다.
파일 크기·개수 프로파일은 실제와 똑같습니다. dt당 6파일(인스턴스당 1개), 365dt면
2,190파일, 54,479,535행, 396.6MB. 이게 핵심입니다. “작은 파일 폭증”이라는
실무의 대표적인 고통을 그대로 재현해야 하기 때문입니다.
한 가지만 손댔습니다. 롤링 윈도우를 검증하려면 최근이 직전보다 나빠지는 신호가
있어야 합니다. 그래서 마지막 7일의 total_time_ms에만 쿼리별 악화 계수를
곱했습니다(hash 버킷으로 60%는 안정, 일부는 +50%, 일부는 +150%). 한 파티션 전체에
쿼리별 균일 계수를 곱하니까 하루 양 끝 차분도 같은 계수로 스케일되고, 결국
avg_latency가 그 계수만큼 오릅니다.
3. 규모 수치로 보니 병목은 하나였다
만들어 놓고 하나씩 쟀습니다. 실데이터 3dt와 나란히 놓고 봤습니다.
| 단계 | 실데이터(3dt·12파일) | 규모(365dt·2190파일) | 병목 |
|---|---|---|---|
| S3 list_objects_v2 전체 | 12 오브젝트 | 2,190 오브젝트 · 2,182ms | 글롭 |
| DuckDB 전체 글롭+count | 184ms (508,155행) | 3,471ms (54.5M행) | O(이력) |
| dbt fct 전체 재빌드 | <1s | 407.62s | 1순위 |
| dbt mart 재빌드 | ~0.3s | 0.31s | 아니오 |
| 게이트/verify per-dt 글롭 | 8ms | 8~22ms | 아니오 |
| DuckLake CHECKPOINT(1년 커밋) | 미측정 | 0.47s (366→1파일) | 아니오 |
| Metabase 서빙 카드 응답 | 미측정 | 10~60ms | 아니오 |
| 파일 크기 | 평균 177KB | 평균 177KB | 128MB의 1/741 |
병목은 명백히 하나였습니다. fct 전체 재빌드 407.62초. 나머지는 규모에서도
전부 초 단위였습니다. 제가 “느릴 것 같다”고 막연히 걱정하던 곳들이 실제로는 안
아팠습니다.
- mart 재빌드는 0.31초. 마트는 fct를 훑는 롤링 집계지만, fct가 이미 사전집계라
입력이 작습니다(365dt여도 fct는 195k행). 여기를 최적화하면 복잡도만 늡니다. - 게이트와 verify는 8~22ms. 이건 제가 재보기 전엔 몰랐던 사실인데, 게이트는
dt별 파티션(6파일)만 봅니다. 이력이 3일이든 365일이든 상관없습니다. O(1 파티션).
즉 게이트는 규모에서 아예 안 느려집니다. - CHECKPOINT는 0.47초. 1년치 366커밋이 366개 소파일을 쌓았는데, CHECKPOINT가
그걸 1파일로 컴팩션하는 데 반 초입니다. 6단계에서 붙인 유지보수가 소파일 누적을
값싸게 흡수하고 있었습니다.
그리고 소파일. 파일 평균이 177KB입니다. 실무에서 합의된 타깃이 128MB~1GB/파일인데,
그거의 1/741입니다. 소파일 폭증은 분명히 있습니다. 다만 그 고통이 어디로
나타나느냐가 중요한데, (a) 글롭 리스팅 2.2초와 (b) DuckLake 커밋 누적으로 나타나고,
후자는 CHECKPOINT가 이미 잡고 있었습니다.
4. 407초가 정당화한 증분 전환
407초는 초 단위가 아닙니다. 전체 재빌드를 매일 돌리면 이력이 1년일 때 매일 7분씩
걸린다는 뜻입니다. 이건 수치가 최적화를 요구하는 경우입니다.
다행히 fct는 증분으로 바꾸기 좋은 구조였습니다. 하루 발생량은 그날 파티션 양 끝의
차분이라, dt 단위로 완전히 독립입니다. 다른 날과 섞이지 않습니다. 그러니 새 dt만
계산해 붙이면 결과가 전체 재빌드와 같습니다.
전략 선택에서 한 번 짚고 갈 게 있었습니다. dbt-duckdb 1.10.1은 append, delete+insert,
그리고 DuckDB 버전이 맞으면 merge·microbatch를 지원합니다. 백로그엔 “microbatch”라고
적어 뒀었는데, 실제로 보니 microbatch는 event_time을 요구하고 unique_key로 파티션을
교체하는 방식이 아니었습니다. 우리 grain은 (instance, query, dt)라 **delete+insert +
unique_key**가 더 정확하고 단순합니다. 새 dt는 순수 insert, 같은 dt 재실행(당일
재시도)은 그 dt만 삭제 후 재삽입합니다. 멱등성은 그대로 유지됩니다.
그런데 여기서 함정에 빠졌습니다. 증분 predicate를 이렇게 썼습니다.
{% if is_incremental() %}where dt >= (select max(dt) from {{ this }}){% endif %}증분으로 바꿨는데도 규모에서 2분+ 타임아웃이 났습니다. 이유를 파보니, 스칼라
서브쿼리로는 DuckDB가 hive 파티션 프루닝을 못 합니다. (select max(dt) from this)는
런타임에 평가되는데, 파티션 경로 프루닝은 플랜 타임에 상수가 있어야 걸리기 때문입니다.
그래서 predicate가 행은 걸러도 2,190파일을 다 스캔했습니다. 증분의 이득이 통째로
날아간 것입니다.
해법은 워터마크를 컴파일 타임에 리터럴로 구워 넣는 것이었습니다.
{% if is_incremental() %}{% set max_dt = run_query("select max(dt) from " ~ this).columns[0].values()[0] %}where dt >= '{{ max_dt }}'{% endif %}run_query로 현재 fct의 max dt를 컴파일 타임에 뽑아 상수로 박으니, 파티션 경로
프루닝이 걸려 최신 dt 파일만 읽었습니다.
전후 비교입니다.
| fct 전체 재빌드 | fct 증분(1 dt 추가) | |
|---|---|---|
| 소요(wall) | 407.62s | 4s (실작업 ~1~2s + dbt 기동) |
| 읽는 파일 | 2,190 | 워터마크≥ dt만(~6~12) |
| 결과 | 194,910행/365dt | 195,444행/366dt (+534) |
407.62초 → 4초, 약 100배입니다. 새 dt를 붙여도 fct가 정확히 증가했고, 같은 dt를
다시 돌려도 행수가 안 변합니다(멱등). 실데이터 3dt에서도 full-refresh로 다시 지은 뒤
증분 재실행 시 1,749행 그대로였습니다.
정직하게 남길 함정 하나. 워터마크가 >= max(dt)라, 이미 지나간 과거 dt를 정정하면
일반 dbt run은 그 dt를 안 건드립니다. 과거 dt 정정은 --full-refresh가 필요합니다
(RUNBOOK에 절차를 적었습니다). 그리고 계약(contract)과 증분을 같이 쓰면
on_schema_change를 명시하라고 dbt가 강제합니다. 이것도 배웠습니다.
5. 롤링 윈도우를 “지난달 대비”로 되돌리기
mart_query_regression을 최신 dt 기준 최근 N일 vs 직전 M일(기본 7 vs 30)의
미끄러지는 창으로 재설계했습니다. 이력이 아무리 길어져도 창은 항상 “최근 대 직전”으로
따라 미끄러집니다.
365dt(최근 7일에 악화 주입)에서 돌린 랭킹입니다.
inst query_id rN pN prior_ms recent_ms inc_ms pct 8 c5w7xa73cr06 7 30 0.78 1.87 1.08 138.1 3 0xDC2C76028B 7 30 0.89 1.31 0.42 47.5 4 -73528926589 7 30 0.49 0.73 0.23 47.5 8 0j5qp7xhc2tx 7 30 0.32 0.47 0.15 47.5 ...pct 분포: +47.5% 8건 · +138.1% 6건recent_days_seen=7, prior_days_seen=30 정확히 나오고, 제가 주입한 두 악화 계층이
랭킹으로 분리돼 떴습니다. 그런데 정확히 +50/+150은 아니고 +47.5/+138.1입니다. 주입 경계(마지막 7일)와 롤링 창의 직전 30일 경계가 하루 겹쳐서, 직전 창 평균에
계수가 곱해진 하루가 섞였기 때문입니다. 롤링 평균이 그걸 그대로 반영한 결과입니다. 창을
제가 예쁘게 지어내지 않았다는 증거이기도 합니다.
정직하게 하나 더. 실데이터 3dt에서는 이 마트가 0행입니다. 최근 7일이 3개 dt를 다
삼키고 직전 30일이 비어서 비교할 게 없습니다. 이력이 부족하니 정직하게
비웁니다. “3일치로 지난달을 논할 수 없다”, 이게 맞는 답입니다. 이 랭킹은 이력이
쌓여야 실데이터에서도 채워집니다. 그래서 dbt unit test로 로직만은 못박아 뒀습니다
(최근>직전만, 양 창 관측 필수, 저콜 제외).
6. 세 번째 축이 된 운영 대시보드
마지막으로, 매 런의 메타(dt·게이트 축별 상태·소요·행수·heartbeat)를 DuckLake
pipeline_run_log 테이블로 발행하게 했습니다. 마트와 같은 카탈로그라 Metabase가 이미
붙어 있는 DuckLake 커넥션으로 그대로 읽습니다. 서비스도 커넥션도 추가 0입니다.
실데이터 닫힌 dt로 발행해 봤습니다. 07-05·06·07은 전부 OK, 그리고 수집기가 멈춰서
데이터가 없는 07-08은 completeness와 freshness가 FAIL로 찍혔습니다. 게이트가
실제로 잡는 나쁜 날입니다. 그걸 화면으로 옮긴 게 이겁니다.

- 마지막 성공 dt: 2026-07-07
- 오늘 게이트 상태: 07-08이 FAIL(completeness·freshness), 나머지 축은 OK
- 최근 파이프라인 런: 날짜별 게이트 상태·소요·적재 행수 표(07-08 FAIL, 그 위로 OK 3개)
분석 대시보드(“지난 구간보다 느려진 쿼리 있어?”)와 운영 대시보드(“파이프라인이
건강한가”)를 이원화했습니다. 알림(실패)·heartbeat(성공의 부재)에 더한 세 번째
축입니다. 지금 상태를 한 화면으로 보여줍니다. 8단계에서 이미 알림 payload에 dashboard_url
필드를 심어 뒀으니, 알림을 받으면 이 화면으로 한 클릭입니다.
7. 남은 것을 정직하게
- 과거 dt 정정은
--full-refresh가 필요합니다. 증분 워터마크가>= max(dt)라
최신만 갱신하기 때문입니다. 최신 dt 재실행은 멱등하지만, 지나간 dt를 고치려면 그 모델을
명시적으로 다시 지어야 합니다. - 롤링 윈도우는 이력이 쌓여야 실데이터에서 참입니다. 지금 닫힌 dt가 3개뿐이라
실운영 마트는 0행입니다. 규모(365dt 합성)에서만 랭킹이 나옵니다. 구조는 검증됐고,
이력이 recent+prior만큼 쌓이면 실데이터에서도 채워집니다. - 합성은 파일 규모는 정확하지만 쿼리 다양성은 하루치의 반복입니다. “파티션 수·파일
수·바이트”의 규모는 실제와 똑같이 재현했지만, “고유 쿼리 수 폭증”은 재현하지
않았습니다(그건 원천 다양성의 문제라 복제로는 안 됩니다). - mart는 여전히 전체 재빌드입니다. 다만 사전집계라 규모에서도 0.31초입니다.
fct(407초)와 달리 수치가 증분화를 정당화하지 않습니다. 초 단위인 곳을 증분화하면
복잡도만 늡니다.
이번 편에서 제가 배운 건, “빠를 것 같다”와 “빠르다”는 완전히 다른 문장이라는
거였습니다. 며칠치로는 아무것도 증명 못 합니다. 1년치를 실제로 만들어 재보니 병목은
제 예상과 달리 딱 하나였고(fct 407초), 제가 걱정하던 다른 곳들은 규모에서도 초
단위였습니다. 그래서 fct만 증분으로 바꿨고, 나머지는 “지금은 안 한다”를 수치로
정당화했습니다. 문제 없는 곳을 최적화하지 않는 것도 엔지니어링입니다. 그리고 그 판단의
근거를 실측 수치로 남기는 것, 그게 이 프로젝트가 계속 지키려는 원칙입니다.
8. 재보지 않은 문장이 로드맵에 있었다
셀프호스트 어플라이언스를 준비하며 저는 로드맵에 이런 표를 적었습니다. “N=100이면 소파일
압박 시작, N=300이면 소파일 심각, 무너지는 순서는 소파일 → 추출 → 컴퓨트.” 몇백 대를
관제하는 조직이 이걸 셀프호스트하면 어디가 먼저 아픈지를 정리한 표였습니다.
그런데 그 표에는 각주가 하나 붙어 있었습니다. “지금은 외삽이지 실측이 아니다.”
앞부분의 규모 실측은 시간축(dt)만 늘렸기 때문입니다. 365dt를 만들었지만 인스턴스는
6개 그대로였습니다. 몇백 대 시나리오에서 늘어나는 축은 dt가 아니라 **인스턴스
수(N)**인데, 그 축은 한 번도 재본 적이 없었습니다. 재보지 않은 문장을 표로 만들어
두면, 며칠 지나면 그게 사실처럼 읽힙니다. 앞부분에서 “빠를 것 같다”와 “빠르다”는 다른
문장이라고 써 놓고, 같은 실수를 축만 바꿔서 반복하고 있었던 셈입니다.
9. 총량을 고정하고 축만 돌린다
측정 설계에서 하나를 정했습니다. 총량은 고정하고, 축만 돌린다.
앞부분의 dt축 실측은 2,190파일·54.5M행이었습니다. N축을 잴 때 아무 규모나 만들면
“N축이라서 느린 건지, 그냥 데이터가 많아서 느린 건지”를 분리할 수 없습니다. 그래서
N축 합성을 7dt × 300인스턴스 = 2,100파일·52.2M행으로 맞췄습니다. 파일 수도 행수도
앞부분과 거의 같습니다. 다른 건 축의 모양뿐입니다. dt축은 “dt 365개, dt당 6파일”,
N축은 “dt 7개, dt당 300파일”. 이러면 측정값의 차이가 전부 축의 모양으로 귀속됩니다.
합성 방식은 앞부분의 스크립트에 --instances N 모드를 추가했습니다. 닫힌 dt의 실제
인스턴스 6개를 순환 복제하되, 세 가지를 리매핑합니다.
instance_id→ 1..N으로 갈아끼움query_id→~i{j}suffix: 인스턴스마다 자기 쿼리 모집단을 갖는 실제 배포처럼,
고유쿼리 카디널리티가 N에 비례해 자연 증가합니다(실측: 1dt 기준 N=100에서 9,039개,
N=300에서 26,700개)id→ 인스턴스별 오프셋(전역 유일 유지)
악화 계수는 이번엔 안 심었습니다. 앞부분의 주입은 롤링 랭킹을 검증하려는 것이었고,
이번 측정 대상은 랭킹이 아니라 파이프라인 역학이기 때문입니다. 격리 원칙은
그대로입니다. 별도 프리픽스 scale_n/에만 쓰고, 실데이터 raw/도 앞부분의 scale/도
건드리지 않고, 끝나면 프리픽스째 지웁니다.
10. 실측: 예상 두 개가 틀렸다
N=100(700파일·17.7M행)과 N=300(2,100파일·52.2M행)을 만들어 앞부분과 같은 항목을
쟀습니다.
| 지표 | dt축(365dt×6, 앞부분) | N=100(7dt) | N=300(7dt) |
|---|---|---|---|
| 파일 수 · 총 행수 | 2,190 · 54.5M | 700 · 17.7M | 2,100 · 52.2M |
| 파일 평균 크기 | 177KB | 180KB | 177KB |
| fct full-refresh | 407.62s | 68.08s | 769.21s |
| fct 증분(새 dt 1개) | 4s | 2.98s | 8.03s |
| mart 재빌드 | 0.31s | 0.22s | 0.12s |
| per-dt 글롭(게이트 단위) | 8~22ms | 154ms | 33ms* |
(*ms 단위 글롭 수치는 직전 쿼리가 메타데이터 캐시를 데워놨는지에 따라 흔들립니다.
N=300의 33ms는 바로 앞 전체 글롭이 캐시를 데운 상태입니다. 이 표에서 믿을 만한
신호는 초 단위 항목들입니다.)
첫 번째로 틀린 예상: 소파일이 먼저 아플 것. 안 아팠습니다. 파일 프로파일은
앞부분과 동일한 평균 177KB(실무 타깃 128MB의 1/741)인데, 전체 리스팅도 글롭도 게이트
단위 읽기도 전부 초 단위 이하였습니다. 소파일 폭증은 분명 실재하지만, 이 규모에선
아직 고통으로 나타나지 않습니다. “N=100이면 소파일 압박 시작”이라던 제 표는 최소한
이 스택·이 규모에선 겁이 많았습니다.
두 번째로 틀린 예상: 컴퓨트는 마지막일 것. 정반대였습니다. fct 전체 재빌드가
같은 총량에서 dt축 407.62초 vs N축 769.21초, 1.9배입니다. 데이터를 한 바이트도
더 넣지 않고 축의 모양만 바꿨는데 전체 재빌드가 두 배 아픕니다.
11. 왜 두 배인가: 가설까지만
원인을 파는 데는 신중하려고 합니다. 지금 가진 단서는 이렇습니다. fct는 하루 발생량을
“그날 파티션 안에서 쿼리별 first/last 차분”으로 구합니다. 창 집계가 dt 단위로 도는
구조입니다. dt축에선 dt당 149k행짜리 가벼운 창이 365개였습니다. N축에선 dt당
7.46M행짜리 무거운 창이 7개입니다. 창 하나에 들어오는 행이 50배라, 창 안의
정렬·해시·스필 압력이 dt 하나에 집중됩니다. 총합은 같아도 “잘게 365번”과 “무겁게
7번”은 메모리 거동이 다릅니다.
그럴듯하지만, EXPLAIN 프로파일링으로 확인한 게 아니므로 가설로 남깁니다. 앞부분에서
스칼라 서브쿼리 프루닝 실패를 파냈을 때처럼 플랜을 뜯어보면 확정할 수 있을 텐데,
그건 이 급소가 실제로 아파지는 시점(대량 backfill이 필요해지는 날)의 몫으로
미룹니다. 지금 단계에서 지어내지 않는 것이 더 중요합니다.
12. 그리고 예상대로였던 것: 증분은 건재하다
이 실측의 결론은 숫자 하나로 모입니다. N=300에서 새 dt 하나(300파일·7.5M행)의
증분 처리: 8.03초.
앞부분에서 만든 증분 구조(delete+insert + 컴파일 타임 워터마크 리터럴)가 N축에서도
그대로 유효했습니다. 워터마크 프루닝이 최신 dt 파일만 읽으니, N이 6이든 300이든
“오늘 것만 계산한다”는 성질이 보존됩니다. N=100의 2.98초에서 N=300의 8.03초로,
대략 N에 선형입니다. 몇백 대 규모에서도 매일 도는 경로는 안 무너집니다.
mart도 마찬가지입니다. 어느 축에서도 0.1~0.3초. 입력이 사전집계(fct)라 축의 모양과
무관합니다. 앞부분에서 “문제 없는 곳을 최적화하지 않는다”고 남겨둔 판단이 N축에서도
유효하다는 재확인입니다.
13. 함의: 운영 가이드의 1순위가 바뀐다
이 실측 전에 제 로드맵의 몇백 대 대응은 “컴팩션 노브와 추출 병렬도를 노출하라”였고,
full-refresh 청크 분할은 대응 목록의 한 줄이었습니다. 실측이 우선순위를 뒤집었습니다.
- 증분은 그대로 둔다. 건드릴 이유가 없음을 수치가 보여줬습니다.
- backfill·과거 정정이 진짜 위험 지점이다. 증분 워터마크는 과거 dt 정정 시
--full-refresh를 요구하는데(앞부분의 정직한 함정), 그 full-refresh가 N축에서 두 배
아픕니다. N=300에서 12분 50초였고, 이력이 1년이면 이 숫자는 dt 수에 비례해
커집니다. 몇백 대 어플라이언스의 운영 가이드 1순위는 **“full-refresh를 통째로
돌리지 말고 dt 청크로 쪼개라”**가 됩니다. - 소파일·글롭·게이트는 당분간 지켜보기만 합니다. 아직 수치가 최적화를 요구하지
않습니다.
14. 남은 것을 정직하게
- 추출 축은 못 쟀습니다. offload가 원천 PG를 인스턴스별로 조회하는 비용은 원천에
N개 인스턴스가 실재해야 잴 수 있는데, 이 합성은 parquet 쪽만 만듭니다. “추출이 배치
창을 넘기는 시점”은 실원천이 커질 때 재는 것으로 남깁니다. - 769초의 원인은 가설입니다. dt당 행수 집중이 창 집계를 짓누른다는 설명은
그럴듯하지만 플랜으로 확인하지 않았습니다. EXPLAIN 프로파일링은 이 경로가 실제로
아파질 때의 몫입니다. - ms 단위 글롭 수치는 캐시 온도에 오염됩니다. 측정 순서가 결과에 섞여 들어서,
표에서 그 줄은 참고치로만 남겼습니다. 초 단위 신호만 결론에 썼습니다. - 카디널리티는 N에 비례하게 만들었지만, 쿼리 내용의 다양성은 여전히 복제입니다.
suffix로 고유쿼리 수는 늘렸어도 쿼리 텍스트 분포는 하루치의 반복입니다. 앞부분과 같은
한계입니다.
이번 편에서 배운 건, 외삽에 각주를 달아두는 것만으로는 부족하고 결국 재봐야 한다는
것이었습니다. “소파일 먼저, 컴퓨트 마지막”이라는 제 표는 그럴듯했지만 두 군데가
틀렸고, 총량을 고정하고 축만 돌리는 설계 덕분에 그 틀림이 깨끗하게 드러났습니다.
증분이 건재하다는 확인과 full-refresh가 급소라는 발견은 같은 실측의 양면입니다.
하나는 안심하고 두는 근거가 됐고, 다른 하나는 운영 가이드 맨 앞으로 올라갔습니다. 재보기 전엔
둘 다 몰랐습니다.
댓글
댓글 수정/삭제는 GitHub Discussions에서 가능합니다.