이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 데이터 검증 SQL은 pipeline 결과에 어떤 자동 질문을 던지는가?
- 좋은 quality rule은 SQL, threshold, owner, severity, 실행 주기, 실패 조치를 어떻게 함께 가진다?
- source-target 검증 SQL을 작성할 때 grain, timezone, null, 중복을 어떻게 고려해야 하는가?
- rule 실행 결과를 어떻게 저장해야 알림과 회고로 이어질 수 있는가?
개요
데이터 검증 SQL은 pipeline 결과가 믿을 수 있는지 확인하는 자동 질문이다.
SQL 자체보다 rule 계약이 중요하다.
어떤 dataset에 대해, 어떤 기준으로, 얼마나 자주, 실패하면 누구에게, 어떤 심각도로 알릴지 정해야 한다.
검증 SQL은 pipeline 코드와 독립적으로 실행될 수 있어야 한다.
pipeline이 성공했다고 rule까지 성공한 것은 아니기 때문이다.
Rule 구성 요소
좋은 rule은 다음 정보를 가진다.
rule_id: order_mart_amount_accuracy
dataset: mart_order_daily
dimension: accuracy
schedule: after daily_order_mart DAG
owner: data-platform
severity: critical
threshold: abs(amount_diff) <= 10000
action: block report and open incidentSQL만 있으면 운영자가 실패 의미를 다시 해석해야 한다.
metadata가 있어야 알림과 runbook으로 연결된다.
Freshness SQL
SELECT
CASE
WHEN max(event_time) >= now() - interval '30 minutes' THEN 'PASS'
ELSE 'FAIL'
END AS status,
max(event_time) AS latest_event_time
FROM mart_order_events;freshness rule은 timestamp column의 의미를 확인해야 한다.
event 발생 시각인지, 적재 시각인지, mart 갱신 시각인지에 따라 해석이 달라진다.
Completeness SQL
WITH source AS (
SELECT count(*) AS cnt
FROM orders
WHERE created_at >= :from AND created_at < :to
),
target AS (
SELECT count(*) AS cnt
FROM mart_orders
WHERE order_date >= :from_date AND order_date < :to_date
)
SELECT source.cnt, target.cnt, source.cnt - target.cnt AS diff
FROM source CROSS JOIN target;completeness rule은 source와 target grain이 같을 때만 단순 count 비교가 가능하다.
grain이 다르면 distinct key coverage를 본다.
SELECT count(DISTINCT order_id)
FROM mart_order_items
WHERE order_date = :dt;Accuracy SQL
WITH source AS (
SELECT sum(paid_amount) AS amount
FROM orders
WHERE order_date = :dt
),
target AS (
SELECT sum(paid_amount) AS amount
FROM mart_order_daily
WHERE order_date = :dt
)
SELECT source.amount, target.amount, source.amount - target.amount AS diff
FROM source CROSS JOIN target;accuracy rule은 business 정의와 연결된다.
취소 주문 포함 여부, 환불 차감 시점, timezone 기준이 문서화되어야 한다.
Validity SQL
SELECT count(*) AS invalid_count
FROM payments
WHERE amount < 0
OR status NOT IN ('READY', 'PAID', 'CANCELED')
OR paid_at IS NULL AND status = 'PAID';validity rule은 schema와 domain enum 변경에 민감하다.
백엔드가 새 status를 추가하면 rule도 함께 바뀌어야 한다.
Rule 실행 결과 저장
rule 결과는 table에 남긴다.
CREATE TABLE quality_rule_runs (
run_id varchar(80) PRIMARY KEY,
rule_id varchar(120) NOT NULL,
dataset varchar(120) NOT NULL,
dimension varchar(40) NOT NULL,
status varchar(20) NOT NULL,
measured_value numeric,
threshold_text text NOT NULL,
checked_at timestamp NOT NULL DEFAULT now()
);결과를 저장해야 추세, 회고, 알림 중복 억제가 가능하다.
로그만 남기면 나중에 “언제부터 깨졌는가”를 찾기 어렵다.
Pipeline과 분리
검증 SQL은 pipeline 코드 내부 assert로만 두면 안 된다.
별도 task나 job으로 실행 가능해야 한다.
Airflow에서는 load 이후 validation task로 둔다.
Spring Batch에서는 job step으로 두더라도 rule 결과 table에 남긴다.
검증 실패는 report publish를 막거나 incident로 연결되어야 한다.
운영 지표
- rule failure count by dimension
- consecutive failure count
- rule runtime
- measured value vs threshold
- datasets without rules
- muted rule count
rule이 너무 느리면 pipeline을 막을 수 있다.
무거운 SQL은 summary table이나 sampling 전략을 검토한다.
개인 프로젝트 기준
개인 프로젝트에서는 세 개 rule이면 충분하다.
freshness rule 하나, completeness count rule 하나, accuracy sum rule 하나를 만든다.
각 rule 결과를 table에 저장한다.
하나를 실패시키고 알림 또는 실패 상태가 남는지 확인한다.
위험 신호!
- 검증 SQL만 있고 threshold와 owner가 없다.
- source와 target grain이 다른데 count를 비교한다.
- rule 실패가 pipeline log에만 남는다.
- 품질 실패인데 report publish가 계속된다.
- 백엔드 enum 변경과 validity rule 변경이 연결되어 있지 않다.
확인 질문
확인 질문
- 이 rule은 어떤 dimension을 검증하는가?
- freshness, completeness, accuracy, validity, consistency 중 하나로 답해야 한다.
- 실패하면 어떤 action이 실행되는가?
- block, alert, warn, incident, backfill 중 하나가 명확해야 한다.
- rule 결과는 어디에 저장되어 추세와 회고에 쓰이는가?
- quality_rule_runs 같은 실행 이력 table이 필요하다.