실행 형식

실행 형식 (Execution Format)

Vector는 실행 중에 메모리 내 데이터를 저장하는 데 쓰는 컨테이너 형식이에요. DataChunk는 Vector들의 모음으로, 예를 들어 PhysicalProjection 연산자에서 열 목록을 나타내는 데 사용돼요.

출처: 문서

본문

데이터 흐름 (Data Flow)

DuckDB는 벡터화된 쿼리 실행 모델을 사용해요. DuckDB의 모든 연산자는 고정 크기의 Vector에서 동작하도록 최적화돼 있어요.

이 고정 크기는 코드에서 흔히 STANDARD_VECTOR_SIZE로 불려요. 기본 STANDARD_VECTOR_SIZE는 2048 튜플이에요.

Vector 형식 (Vector Format)

Vector는 논리적으로 단일 타입의 데이터를 담는 배열을 나타내요. DuckDB는 서로 다른 vector 형식을 지원하는데, 이를 통해 시스템이 같은 논리적 데이터를 다른 물리적 표현으로 저장할 수 있어요. 이러면 더 압축된 표현이 가능해지고, 시스템 전반에서 압축된 실행이 가능할 수 있어요. 아래는 지원되는 vector 형식 목록이에요.

평면 벡터 (Flat Vectors)

Flat vector는 물리적으로 연속된 배열로 저장돼요. 이것이 표준 무압축 vector 형식이에요. Flat vector의 경우 논리적 표현과 물리적 표현이 동일해요.

Flat Vector example

상수 벡터 (Constant Vectors)

Constant vector는 물리적으로 단일 상수 값으로 저장돼요.

Constant Vector example

Constant vector는 데이터 요소가 반복될 때 유용해요. 예를 들어 함수 호출에서 상수 표현식의 결과를 나타낼 때, constant vector는 값을 한 번만 저장하면 돼요.

SELECT lst || 'duckdb'
FROM range(1000) tbl(lst);

duckdb는 문자열 리터럴이라 리터럴의 값이 모든 행에서 동일해요. flat vector에서는 리터럴 'duckdb'를 행마다 한 번씩 복제해야 해요. constant vector는 리터럴을 한 번만 저장하면 되지요.

Constant vector는 스토리지가 상수 압축(constant compression)에서 압축을 풀 때도 방출돼요.

사전 벡터 (Dictionary Vectors)

Dictionary vector는 물리적으로 자식 벡터와, 그 자식 벡터로의 인덱스를 담은 선택 벡터로 저장돼요.

Dictionary Vector example

Dictionary vector는 스토리지가 사전 압축(dictionary compression)에서 압축을 풀 때 방출돼요.

상수 벡터처럼 dictionary vector도 스토리지에서 방출돼요. 사전 압축된 열 세그먼트를 역직렬화할 때 데이터를 쿼리 실행 중에도 압축된 채로 유지할 수 있도록 dictionary vector에 저장해요.

시퀀스 벡터 (Sequence Vectors)

Sequence vector는 물리적으로 오프셋과 증가값으로 저장돼요.

Sequence Vector example

Sequence vector는 증가하는 시퀀스를 효율적으로 저장하는 데 유용해요. 일반적으로 행 식별자(row identifier)에 대해 방출돼요.

통합 벡터 형식 (Unified Vector Format)

이런 다른 vector 형식들의 속성은 최적화에 좋아요. 예를 들어 함수의 모든 파라미터가 상수인 시나리오를 상상해 보면, 결과를 한 번만 계산하고 constant vector를 방출할 수 있죠. 하지만 모든 함수에 대해 모든 vector 타입 조합용 특수 코드를 작성하는 것은 조합 폭발 때문에 비현실적이에요.

대신 타입과 무관하게 vector를 일반적으로 쓰고 싶을 때마다 UnifiedVectorFormat을 사용할 수 있어요. 이 형식은 본질적으로 Vector 내용에 대한 일반 뷰 역할을 해요. 모든 타입의 Vector가 이 형식으로 변환될 수 있어요.

복합 타입 (Complex Types)

문자열 벡터 (String Vectors)

문자열을 효율적으로 저장하기 위해 string_t 클래스를 사용해요.

struct string_t {
    union {
        struct {
            uint32_t length;
            char prefix[4];
            char *ptr;
        } pointer;
        struct {
            uint32_t length;
            char inlined[12];
        } inlined;
    } value;
};

짧은 문자열(<= 12 bytes)은 구조체 안에 인라인되고, 더 큰 문자열은 보조 문자열 버퍼에 있는 데이터 포인터로 저장돼요. length는 strlen을 호출하고 null 포인터를 계속 확인할 필요가 없도록 함수 전반에 걸쳐 사용돼요. prefix는 조기 종료(early out)로 비교에 사용돼요. prefix가 일치하지 않으면 문자열이 같지 않음을 알 수 있고 포인터를 따라갈 필요가 없어요.

리스트 벡터 (List Vectors)

List vector는 일련의 list entry와 함께 자식 Vector로 저장돼요. 자식 Vector에는 리스트에 존재하는 값들이 들어 있고, list entry는 각 개별 리스트가 어떻게 구성되는지 지정해요.

struct list_entry_t {
    idx_t offset;
    idx_t length;
};

offset은 자식 Vector의 시작 행을 가리키고, length는 이 행의 리스트 크기를 추적해요.

List vector는 재귀적으로 저장될 수 있어요. 중첩 list vector의 경우 list vector의 자식이 다시 list vector예요.

예를 들어 BIGINT[][] 타입 Vector의 모의 표현을 보면:

{
   "type": "list",
   "data": "list_entry_t",
   "child": {
      "type": "list",
      "data": "list_entry_t",
      "child": {
         "type": "bigint",
         "data": "int64_t"
      }
   }
}

구조체 벡터 (Struct Vectors)

Struct vector는 자식 벡터의 목록을 저장해요. 자식 벡터의 수와 타입은 구조체의 스키마로 정의돼요.

맵 벡터 (Map Vectors)

내부적으로 map vector는 LIST[STRUCT(key KEY_TYPE, value VALUE_TYPE)]로 저장돼요.

유니온 벡터 (Union Vectors)

내부적으로 UNIONSTRUCT와 같은 구조를 사용해요. 첫 번째 "자식"은 항상 UNION의 Tag Vector가 차지하며, 각 행에 UNION의 어떤 타입이 적용되는지 기록해요.

더 알아보기 (Learn more)