Skip to content

Commit 738db1a

Browse files
committed
velox spill
1 parent e10ae1e commit 738db1a

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

_posts/2024-12-14-spilling.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
title: "Spilling"
3+
excerpt: "spilling in velox"
4+
categories:
5+
- data
6+
tags:
7+
- data
8+
last_modified_at: 2024-12-14T08:00:00-08:00
9+
---
10+
11+
[https://facebookincubator.github.io/velox/develop/spilling.html](https://facebookincubator.github.io/velox/develop/spilling.html)
12+
를 정리한 글.
13+
14+
# Background
15+
- 스필은 제한된 메모리 상에서도 쿼리가 성공하게 한다.
16+
- 예를 들어 해시 집계 연산자는 중간 집계 상태를 해시 테이블에 저장한다. 모든 입력을 처리한 이후에 결과를 출력한다. 높은 카티널리티 워크로드에서는 해시 테이블이 메모리 제한을 초과한다.
17+
- 스필은 연산자의 상태 일부를 디스크에 쓴다. 연산자가 모든 입력을 받은 후 디스크에 스필한 상태와 메모리의 상태를 읽어서 병합 후 결과를 출력한다.
18+
- 스필은 두 단계로 이루어진다. 스필과 복구
19+
- 스필 : 연산자가 입력을 처리할 때 수행된다. 연산자 상태의 어떤 부분을 스필할지 결정하고 디스크에 어떻게 저장할지 결정한다.
20+
- 복구 : 연산자가 모든 입력을 처리한 이후 수행된다. 디스크에서 스필한 상태와 메모리의 상태를 읽어서 병합 후 결과를 출력한다.
21+
- 다른 연산자는 각기 다른 스필 알고리즘을 사용한다. 이 문서는 해시 집계, Order By, 해시 조인 연산자에 대해 다룬다.
22+
23+
# Spilling Framework
24+
![](https://facebookincubator.github.io/velox/_images/spill-arch.png)
25+
26+
- Velox의 스필 프레임웍은 공통 스필 함수, 데이터 컬렉션, 파티션, 정렬, (역)직렬화, 저장소 읽기/쓰기 등을 제공한다.
27+
- 각 스필 가능한 연산자는 이 함수를 이용해 자신의 스필 알고리즘을 구현한다.
28+
29+
# Spill Objects
30+
스필 프레임웍은 다음 주요 소프트웨어 객체로 구성된다.
31+
32+
## Spiller
33+
- 스필러 객체는 연산자를 위한 스필 함수를 제공하고 연산자가 디스크에 스필 상태를 관리하도록 돕는다.
34+
- 각 연산자마다 하나의 스필러 객체가 생성된다.
35+
- 스필러 객체는 row 컨테이너 객체를 생성자에서 받는다.
36+
- hash probe 연산자 제외. 그것은 입력을 버퍼하지 않고 스필 row를 즉시 디스크에 쓴다.
37+
- row 컨테이너는 행 기반의 인메모리 데이터 저장소다. 메모리가 부족할 때 디스크에 스필할 수 있는 상태를 저장한다.
38+
- 예를 들어, 해시 집계 연산자의 로우 컨테이너는 각 그룹(고유 키 값의 조합)별 하나의 행으로 중간 집계 상태를 저장한다.
39+
- 연산자 스필이 필요할 때 스필러는 로우 컨테이너의 모든 행을 스캔하고 각 로우의 파티션 번호를 계산하고, 스필할 파티션을 결정하고, 파티션을 파일의 리스트로 스필한다.
40+
- 스필러는 디스크에 데이터를 쓰기 전에 정렬할 수 있다.
41+
- 정렬이 허용된다면 스필러는 각 정렬된 수행에 대해 별도의 파일을 생성한다.
42+
- 복구 단계에서 스필러는 스필한 데이터를 읽어서 인메모리 상태로 복구한다. 정렬이 허용된다면 스필러는 병합 정렬 reader를 생성하여 정렬된 데이터를 읽는다.
43+
- 이러한 기능은 해시 집계나 `ORDER BY` 연산자에 사용된다.
44+
45+
스필러는 아래 주요 함수를 구현한다.
46+
- **Spill data partition**
47+
- 스필 시, 디스크에 최소한의 데이터를 쓰고, 나머지는 인메모리에 유지하고 싶을 것이다. IO를 최소화하고, 복구도 빠르게 하기 위함이다.
48+
- 스필러는 로우 컨테이너의 로우들을 파티션으로 나누고 그중 일부만 스필한다.
49+
- 각 파티션은 디스크에 별개의 파일 집합으로 저장된다.
50+
- 해시 집계 연산자는 그룹핑 키를 사용하여 데이터를 파티션으로 나눈다. 동일 그룹의 모든 로우는 동일 파티션으로 함께 스필, 복구된다.
51+
- **Select partitions to spill**
52+
- 스필러는 스필 시 가장 많은 데이터를 가진 파티션을 선택한다.
53+
- 스필 가능한 데이터는 로우 컨테이너 내 로우가 점유한 메모리 바이트를 측정한 것이다.
54+
- 정렬 스필을 사용하는 연산자는 데이터량이 적은 파티션이 이전에 스필되었어도 스필을 피해야 한다.
55+
- 정렬 스필은 각 정렬 수행에 대해 새 파일을 생성하고 파일에 대해 데이터 추가를 허용하지 않기 때문이다.
56+
- 비정렬 스필은 파일에 데이터를 추가할 수 있기 때문에 이런 제약이 없다.
57+
- **Sort data while spilling**
58+
- 정렬 스필을 사용하는 연산자는 스필 파일과 인메모리 데이터를 정렬-병합 알고리즘을 사용해 조합한다.
59+
- 인메모리 데이터 또한 정렬 수행에 따라 정렬되어 있다.
60+
- 스필러는 파티션 컬럼과 연산자가 정의한 비교 플래그의 집합으로 로우를 정렬한다.
61+
- 예를 들어, `Order By` 연산자는 쿼리 플랜 노드가 명시한 것과 동일한 정렬 순서가 보장되어야 한다.
62+
- **Spill data IO**
63+
- 스필러는 저장 시스템과의 상호작용을 관리한다.
64+
- SpillFileList와 SpilFile 객체는 스필 파일의 생성, 쓰기, 읽기, 삭제 등 생명주기를 관리한다.
65+
- 스필 쓰기는 전용 입출력 수행기로 위임한다. 각 스필 파티션 쓰기는 스레드 수행 단위다.
66+
- 스필 읽기는 드라이버 수행기로 수행된다.
67+
- 읽기 쓰기 모두 동기 입출력이다.
68+
69+
## Spill APIs
70+
- **spill with targets**
71+
- 연산자는 스필을 목적으로 하는 로우와 바이트 수를 명시한다.
72+
- 스필러는 목적에 맞는 스필 대상 파티션 수를 선택한다.
73+
- 스필은 내부적으로 수행되고 완료되면 반환한다.
74+
- 스필 처리는 연산자에 의해 통제되지 않는다.
75+
- 다만 어떤 파티션이 스필되고 얼마나 많은 데이터가 스필되었는지 통계 API를 통해 확인할 수 있다.
76+
77+
```c++
78+
void Spiller::spill(uint64_t targetRows, uint64_t targetBytes);
79+
SpillPartitionNumSet Spiller::spilledPartitionSet() const;
80+
Stats Spiller::stats() const;
81+
```
82+
83+
- **spill partitions**
84+
- 연산자는 스필할 파티션을 명시한다. 스필러는 해당 파티션의 모든 로우를 디스크로 스필한다.
85+
- 스필 처리는 연산자에 의해 통제된다.
86+
- 해시 빌드 연산자가 사용한다.
87+
- 스필이 시작되면 연산자 중 하나가 모든 연산자를 수행하도록 선택된다.(그룹 스필)
88+
- 모든 연산자들에서 스필 가능한 통계를 수집하여 스필할 파티션의 수를 선택한다.
89+
90+
```c++
91+
void Spiller::spill(const SpillPartitionNumSet& partitions);
92+
void Spiller::fillSpillRuns(std::vector<SpillableStats>& statsList);
93+
```
94+
95+
- **spill vector**
96+
- 연산자는 로우 벡터를 특정 파티션으로 스필한다.
97+
- 스필러는 직접 로우 벡터를 현재 열려있는 스필 파티션 파일에 추가한다.
98+
- 스필 처리는 연산자에 의해 통제된다.
99+
- 해시 조인 연산자가 사용한다.
100+
- 해시 빌드와 해시 프로브 연산자는 연관 파티션이 스필되었다면 입력 로우를 디스크에 스필한다.
101+
- 해시 빌드 연산자는 파티션이 스필되었다면 그 파티션의 모든 입력 로우는 스필되어야 한다.
102+
- 조인할 파티션의 일부 로우로만 해시 테이블을 빌드할 수 없기 때문.
103+
- 해시 프로브 연산자는 그 자체로는 스필 불가능하다. 다만 해시 빌드에서 연관된 파티션이 스필된 경우 입력 행을 스필해야 합니다.
104+
105+
```c++
106+
voild Spiller::spill(uint32_t partition, const RowVectorPtr& spillVector);
107+
```
108+
109+
## Restore APIs
110+
111+
- **sorted spill restore**
112+
- order by 와 해시 집계 연산자에서 사용
113+
- 1. `Spiller::finishSpill()`을 호출하여 스필의 완료를 마킹한다.
114+
- 2. 스필러는 스필되지 않은 파티션에서 로우를 수집하여 연산자에 반환.
115+
- 3. 연산자는 스필되지 않은 파티션을 처리, 결과를 방출하고 로우 컨테이너의 공간을 해제.
116+
- 4. 스필된 파티션을 하나씩 적재한다.
117+
- 5. 각 스필 파티션은 `SpillPartition::createOrderedReader()`를 호출하여 정렬된 reader를 생성하고 복구한다.
118+
119+
```c++
120+
void Spiller::finishSpill(SpillPartitionSet& partitionSet);
121+
SpillPartition::createOrderedReader();
122+
```
123+
124+
- **unsorted spill restore**
125+
- 해시 빌드, 해시 프로브 연산자에서 사용
126+
- 1. `Spiller::finishSpill()` 호출하여 스필 완료 마킹
127+
- 2. 스필러는 스필된 파티션에서 메타데이터를 수집하여 연산자에 반환.
128+
- 3. 연산자는 스필되지 않은 파티션을 처리, 결과를 방출하고 로우 컨테이너의 공간을 해제.
129+
- 4. 스필된 파티션을 하나씩 적재한다.
130+
- 5. 각 스필 파티션은 `SpillPartition::createReader()`를 호출하여 비정렬 reader를 생성하고 복구한다.
131+
132+
```c++
133+
void Spiller::finishSpill(SpillPartitionSet& partitionSet);
134+
135+
SpillPartition::createReader();
136+
```
137+
138+
## SpillFileList and SpillFile

0 commit comments

Comments
 (0)