테이블의 변경분을 쿼리하기 (Change Queries in Snowflake, SIGMOD 2023)
두 시점 사이에 테이블이나 뷰가 어떻게 바뀌었는지를 SQL 로 묻는 CHANGES 쿼리와, 그 변경분을 정확히 한 번씩 소비하게 해 주는 STREAM 객체의 의미와 구현을 설명한 논문입니다. 이후 Dynamic Tables 의 증분 갱신이 이 위에 지어졌습니다.
출처
| 항목 | 내용 |
|---|---|
| 원제 | What's the Difference? Incremental Processing with Change Queries in Snowflake |
| 저자 | Tyler Akidau, Paul Barbier, Istvan Cseri, Fabian Hueske, Tyler Jones, Sasha Lionheart 외 6명 (Daniel Mills, Dzmitry Pauliukevich, Lukas Probst, Niklas Semmler, Dan Sotolongo, Boyuan Zhang). 모두 Snowflake |
| 실린 곳 | Proceedings of the ACM on Management of Data (PACMMOD) 1권 2호, Article 196, 27쪽. SIGMOD 2023 논문 |
| 발표 | 2023년 6월 |
| DOI | 10.1145/3589776 |
| 원문 | ACM Digital Library |
| 이용 조건 | 저작권은 저자 보유, ACM 출판. Crossref 메타데이터에는 CC BY-NC-ND 4.0 이 등록되어 있고, 확인한 PDF 사본의 표기는 개인, 수업용 비영리 복사를 허용하는 문구입니다 |
참고 이 페이지는 원문 번역이 아니라 한국어 해설이에요. 정확한 표현과 수치는 원문을 확인하세요.
한눈에
- 표준 SQL 은 변경 내용을 테이블에 반영(MERGE 등)할 수는 있어도, "이 테이블이 지난번 이후 어떻게 바뀌었나" 를 물을 방법이 없습니다.
- 논문은 두 시점 사이의 변경분을 돌려주는
CHANGES절과, 소비한 지점을 기억해 다음에 이어 읽게 해 주는STREAM객체를 소개합니다. 3년 동안 운영한 경험을 바탕으로 합니다. - 구현의 핵심은 "쿼리 미분" 입니다. 쿼리의 변경분을 연산자별 규칙으로 바꾸어 테이블 스캔까지 내려보내고, 테이블 스캔은 추가되고 제거된 마이크로 파티션만 읽습니다.
- 운영 데이터에서 변경분 쿼리의 90.8% 는 추가된 파티션만 읽는 가장 싼 계획으로 실행되었습니다(표 2).
왜 나왔나
스트림 처리의 핵심은 증분 처리, 즉 바뀐 것만 다시 계산하는 일입니다. 저자들은 이벤트 큐, 변경 알림, 증분 뷰 유지(IVM), 유연한 CDC 와 ETL 네 가지 사용 사례를 듭니다. 그런데 SQL 로는 이런 작업을 표현하기 어렵습니다. 사용자는 타임스탬프 열로 직접 변경분을 걸러 내고, 어디까지 처리했는지 따로 기록하며, 늦게 커밋된 데이터를 놓치지 않으려 애써야 했습니다. 저자들은 테이블을 시간에 따라 변하는 관계(TVR)로 보고, 그 변화 자체를 SQL 의 대상으로 만들고자 합니다.
핵심 아이디어
CHANGES 쿼리의 의미 (2.1절)
CHANGES 절은 AT 으로 시작 시점, END 로 끝 시점을 받습니다. 시점은 쿼리 ID, 시각, 현재로부터의 간격, 또는 STREAM 객체로 지정합니다. 결과에는 원래 열 외에 변경 종류($ACTION), 갱신 여부($ISUPDATE), 행 식별자($ROW_ID) 메타데이터 열이 붙습니다. 변경분의 형태는 두 가지입니다.
- APPEND_ONLY: 삽입된 행만 돌려줍니다. 삭제나 TRUNCATE 는 무시합니다. 로그성 데이터 처리에 알맞고 싸게 계산됩니다.
- DEFAULT (최소 델타): 두 시점을 비교해 순수한 INSERT, UPDATE, DELETE 만 남깁니다. 넣었다가 지운 행처럼 결과가 0인 변경은 사라지고, 열 하나만 바뀌어도 행 전체를 돌려줍니다. 모든 중간 단계를 기록하는 redo 로그와는 다르며, 감사 용도의 redo 로그는 향후 과제로 남깁니다.
뷰 위의 변경분 (2.2절)
CHANGES 는 뷰에도 쓸 수 있습니다. 뷰가 걸러 내는 열만 바뀌었다면 출력이 없고, 필터 조건을 넘나든 행은 UPDATE 대신 순수한 INSERT 나 DELETE 로 바뀝니다. 조인 뷰에서 차원 테이블 행 하나를 지우면, 그 행과 짝지어졌던 모든 결과 행에 DELETE 가 나옵니다. 스트림 처리 엔진의 "스트림과 정적 테이블 조인" 과 다른 점입니다.
STREAM 객체 (2.3절)
STREAM 은 스키마 안의 객체로, 원천 테이블에서 어디까지 소비했는지를 나타내는 경계(frontier)를 기억합니다. STREAM 을 DML 안에서 읽으면 그 트랜잭션이 커밋될 때 경계가 앞으로 이동합니다. 그래서 변경분을 정확히 한 번 처리하는 파이프라인을 SQL 문 하나로 만들 수 있습니다. 같은 트랜잭션 안에서는 몇 번 읽어도 같은 결과가 나옵니다. 처음 읽을 때 현재 테이블 전체를 함께 돌려주는 SHOW_INITIAL_ROWS 옵션도 있습니다. STREAM 이 보관 기간을 넘겨 쓸모없어지지 않도록 원천 테이블의 보관 기간을 필요한 만큼 자동으로 늘립니다.
구현: 메타데이터와 쿼리 미분 (3절)
테이블은 불변 마이크로 파티션의 모음이고, 테이블 버전마다 파티션 목록을 FoundationDB 에 기록합니다. 두 버전의 목록을 비교하면 추가된 파티션과 제거된 파티션을 알 수 있습니다.
- 쿼리 미분 (3.2절): 미적분 기호를 빌려 쿼리 Q 의 구간 변경분을 ΔQ 로 쓰고, 필터, 투영, 내부 조인, 그룹 집계, 세미 조인마다 미분 규칙을 정의합니다. 규칙을 반복 적용해 Δ 를 테이블 스캔까지 밀어 내리면, 결과는 옵티마이저가 평범하게 다룰 수 있는 계획이 됩니다.
- 변경 추적 열 (3.2.2절): 행마다 숨은 열을 두어, 행이 처음 삽입된 위치를 행의 정체성으로 삼습니다. UPDATE 나 DELETE 가 파티션을 다시 쓰는 copy-on-write 때도 이 값을 보존하므로, 다시 쓰인 파티션 안에서 실제로 바뀐 행만 골라낼 수 있습니다. INSERT 에는 추가 비용이 없습니다.
- 계획 모양 (3.2.3절): copy-on-write 때문에 같은 행이 "제거, 추가" 쌍으로 중복 등장하므로 최소 델타를 만들려면 상쇄하는 정리 단계가 필요합니다. 추가만 있으면 ADDED_ONLY, 제거만 있으면 REMOVED_ONLY 계획을 써서 이 비싼 단계를 건너뜁니다.
- STREAM 트랜잭션 (3.3절): 테이블마다 버전이 독립적으로 커밋되므로, 경계는 시각 하나가 아니라 원천 테이블별 버전 묶음으로 기록합니다.
실험과 결과
- 사용 현황 (4.1절): 활성 STREAM 의 48% 가 APPEND_ONLY, 52% 가 최소 델타였습니다. 표 1에서 APPEND_ONLY STREAM 을 읽는 문장은 INSERT 54.5%, MERGE 29.4%, 최소 델타 STREAM 은 MERGE 63.2% 가 가장 많았습니다. 그림 3은 STREAM 을 읽는 간격이 5분, 10분, 1시간, 1일 배수에 몰려, 주기적인 TASK 로 읽는 경우가 많음을 보여 줍니다.
- 계획 분포 (표 2): 최소 델타 쿼리 가운데 ADDED_ONLY 90.8%, MINIMIZE 7.1%, 초기 행 2.0%, REMOVED_ONLY 0.1% 였습니다.
- 비용 (표 3): 같은 컴퓨트에서 정규화한 실행 시간은 읽는 파티션이 100개 정도까지는 선형보다 완만하게 늘다가, 1000개 이상에서 눈에 띄게 늘었습니다. 제거된 파티션이 0개일 때와 1개일 때 사이에는 9배에서 16배 차이가 났는데, 제거 파티션이 하나라도 끼면 ADDED_ONLY 대신 MINIMIZE 계획을 써야 하기 때문입니다.
- 실행 시간 (표 4): 운영 쿼리의 47.8% 가 0.5초 안에, 87.5% 가 10초 안에, 99.7% 가 10분 안에 끝났습니다. 조인과 DML 을 포함한 시간입니다.
지금 Snowflake 에서는
CHANGES절과 STREAM 은 같은 이름의 제품 기능입니다. 제품에서는 메타데이터 열 이름이METADATA$ACTION,METADATA$ISUPDATE,METADATA$ROW_ID이고, STREAM 에는APPEND_ONLY,SHOW_INITIAL_ROWS옵션이 있습니다.- 논문이 6절에서 예고한 Dynamic Tables 는 2024년 4월 정식 출시되었고, 증분 갱신이 이 논문의 쿼리 미분 프레임워크를 그대로 확장합니다. Dynamic Tables 논문.
CREATE STREAM orders_s ON TABLE orders; -- 경계를 기억하는 STREAM
INSERT INTO orders_clean
SELECT id, amount FROM orders_s
WHERE METADATA$ACTION = 'INSERT'; -- 커밋되면 경계가 이동함께 읽기
- Streams 와 Tasks: 이 사이트의 기능 문서
- Dynamic Tables 와 지연 뷰 의미론 (SIGMOD 2025)
- 불변 파일과 MVCC 의 바탕: SIGMOD 2016 논문
- Snowflake 공식 문서: Introduction to Streams