스토리지 버전과 형식

스토리지 버전과 형식

DuckDB가 데이터베이스 파일을 어떤 형식으로 저장하고, 버전 간에 어떻게 호환되는지 살펴볼게요. 스토리지 형식은 DuckDB 파일을 읽고 쓰는 방식의 핵심이랍니다.

출처: 문서

본문

호환성

하위 호환성

*하위 호환성(Backward compatibility)*은 더 새로운 DuckDB 버전이 더 오래된 DuckDB 버전이 만든 스토리지 파일을 읽을 수 있는 능력을 말해요. 버전 0.10은 스토리지 형식에서 하위 호환성을 지원하는 첫 DuckDB 릴리스예요. DuckDB v0.10은 이전 DuckDB 버전인 v0.9가 만든 파일을 읽고 연산할 수 있습니다.

미래의 DuckDB 버전에 대해서도 우리의 목표는 이 릴리스부터 이후에 출시된 어떤 DuckDB 버전이든 이전 버전들이 만든 파일을 읽을 수 있도록 하는 것이에요. 파일 형식이 완전히 하위 호환되도록 보장하고 싶어요. 이렇게 하면 DuckDB 파일에 저장된 데이터를 보관해 두고, 파일이 어느 버전으로 작성됐는지 걱정하거나 버전 간 파일을 변환할 필요 없이 읽을 수 있다는 것이 보장됩니다.

상위 호환성

*상위 호환성(Forward compatibility)*은 더 오래된 DuckDB 버전이 더 새로운 DuckDB 버전이 만든 스토리지 파일을 읽을 수 있는 능력을 말해요. DuckDB v0.9는 DuckDB v0.10과 부분적으로 상위 호환됩니다. DuckDB v0.10이 만든 일부 파일은 v0.9로 읽을 수 있어요.

상위 호환성은 최선 노력(best effort) 기준으로 제공돼요. 스토리지 형식의 안정성은 중요하지만, 앞으로도 스토리지 형식에 하고 싶은 개선과 혁신이 많이 남아 있어요. 그래서 상위 호환성은 때때로 (부분적으로) 깨질 수 있어요.

스토리지 형식 간 이동 방법

DuckDB를 업데이트하고 오래된 데이터베이스 파일을 열면, 비호환 스토리지 형식에 대한 오류 메시지가 이 페이지를 가리키며 나타날 수 있어요. 데이터베이스를 새 형식으로 옮기려면 오래된 버전과 새 버전의 DuckDB 실행 파일만 있으면 됩니다.

오래된 DuckDB로 데이터베이스 파일을 열고 EXPORT DATABASE 'tmp' SQL 문을 실행하세요. 이렇게 하면 현재 사용 중인 데이터베이스의 전체 상태를 tmp 폴더 안에 저장할 수 있어요. tmp 폴더의 내용은 덮어써지므로 비어 있거나 아직 존재하지 않는 위치를 고르세요. 그다음 새 DuckDB를 시작하고 IMPORT DATABASE 'tmp'를 실행해(앞서 채운 폴더를 가리키며) 데이터베이스를 로드하면, 이를 DuckDB가 가리키는 파일로 저장할 수 있어요.

이를 위한 Bash 스크립트(파일 이름과 실행 파일 위치에 맞게 조정)는 다음과 같아요.

/older/duckdb mydata.old.db -c "EXPORT DATABASE 'tmp'"
/newer/duckdb mydata.new.db -c "IMPORT DATABASE 'tmp'"

이후 mydata.old.db는 옛 형식으로 남고, mydata.new.db는 같은 데이터를 담되 더 최신 DuckDB 버전이 접근할 수 있는 형식이며, tmp 폴더는 같은 데이터를 보편적인 형식의 다른 파일들로 담게 돼요. 문법에 대한 자세한 내용은 EXPORT 문서를 참고해 주세요.

기본 스토리지 버전

기본적으로 DuckDB v1.0부터 v1.5까지는 v1.0.0에 해당하는 버전 64의 DuckDB 데이터베이스 파일을 만들어요. 새 스토리지를 사용하려면 명시적 스토리지 버전을 설정하세요.

명시적 스토리지 버전

DuckDB v1.2.0은 스토리지 버전을 명시적으로 지정할 수 있는 STORAGE_VERSION 옵션을 도입했어요. 이를 사용해 새롭고 상위 비호환적인 기능에 opt-in할 수 있습니다.

ATTACH 'file.db' (STORAGE_VERSION 'v1.2.0');

최신 스토리지 버전으로 데이터베이스를 초기화하려면 다음을 사용하세요.

ATTACH 'file.db' (STORAGE_VERSION 'latest');

명령줄 클라이언트에서는 -storage-version 인자를 사용할 수 있어요.

duckdb -storage-version v1.2.0 my_database.duckdb

최신 스토리지 버전으로 데이터베이스를 만들려면 다음을 사용하세요.

duckdb -storage-version latest my_database.duckdb

스토리지 버전 설정은 데이터베이스 파일을 읽을 수 있어야 하는 최소 DuckDB 버전을 지정해요. 이 옵션으로 데이터베이스 파일을 쓰면, 결과 파일은 지정된 버전보다 오래된 DuckDB 릴리스로는 열 수 없어요. 지정된 버전과 모든 이후 DuckDB 버전으로는 읽을 수 있어요.

DuckDB 데이터베이스에 attached하면 다음 명령어로 스토리지 버전을 조회할 수 있어요.

SELECT database_name, tags
FROM duckdb_databases();

이렇게 하면 스토리지 버전이 보여요.

┌───────────────┬───────────────────────────────────┐
│ database_name │               tags                │
│    varchar    │       map(varchar, varchar)       │
├───────────────┼───────────────────────────────────┤
│ file1         │ {storage_version=v1.2.0}          │
│ file2         │ {storage_version=v1.0.0 - v1.1.3} │
│ ...           │ ...                               │
└───────────────┴───────────────────────────────────┘

이는 file2는 과거 DuckDB 버전으로 열 수 있지만 file1v1.2.0(또는 미래 버전)과만 호환된다는 뜻이에요.

스토리지 호환성 설정

storage_compatibility_version 구성 옵션으로 사용할 스토리지 버전을 지정할 수도 있어요. 다양하게 지정할 수 있어요.

Python 클라이언트에서는 새 데이터베이스에 연결할 때 지정해야 해요.

duckdb.connect("file.db", config={'storage_compatibility_version': 'latest'})
# 또는
duckdb.connect("file.db", config={'storage_compatibility_version': 'v1.4.0'})

CLI와 일부 다른 클라이언트에서는 구성 옵션을 다음과 같이 설정하세요.

SET storage_compatibility_version = 'latest';
-- 또는
SET storage_compatibility_version = 'v1.4.0';

CLI 클라이언트에서는 -storage-version 명령줄 인자로 전체 CLI 세션의 스토리지 버전을 지정할 수도 있어요.

스토리지 버전 간 변환

호환성을 위해 새 형식에서 옛 형식으로 변환하려면, DuckDB v1.2.0+에서 다음 순서를 사용해요.

ATTACH 'file1.db';
ATTACH 'converted_file.db' (STORAGE_VERSION 'v1.0.0');
COPY FROM DATABASE file1 TO converted_file;

스토리지 헤더

DuckDB 파일은 메인 헤더의 체크섬을 담은 uint64_t로 시작하고, 이어서 네 개의 매직 바이트(DUCK), 그다음 uint64_t의 스토리지 버전 번호가 옵니다.

hexdump -n 20 -C mydata.db
00000000  01 d0 e2 63 9c 13 39 3e  44 55 43 4b 2b 00 00 00  |...c..9>DUCK+...|
00000010  00 00 00 00                                       |....|
00000014

Python으로 스토리지 버전을 읽는 간단한 예시는 다음과 같아요.

import struct

pattern = struct.Struct('<8x4sQ')

with open('test/sql/storage_version/storage_version.db', 'rb') as fh:
    print(pattern.unpack(fh.read(pattern.size)))

스토리지 버전 표

각 릴리스의 변경 사항은 GitHub의 change log를, 각 스토리지 버전을 변경한 커밋은 commit log를 참고해 주세요.

스토리지 버전 DuckDB 버전
68 v1.5.x
67 v1.4.x
66 v1.3.x
65 v1.2.x
64 v0.9.x, v0.10.x, v1.0.0, v1.1.x
51 v0.8.x
43 v0.7.x
39 v0.6.x
38 v0.5.x
33 v0.3.3, v0.3.4, v0.4.0
31 v0.3.2
27 v0.3.1
25 v0.3.0
21 v0.2.9
18 v0.2.8
17 v0.2.7
15 v0.2.6
13 v0.2.5
11 v0.2.4
6 v0.2.3
4 v0.2.2
1 v0.2.1 및 이전

압축

DuckDB는 경량 압축(lightweight compression)을 사용해요. 기본적으로 압축은 영속 데이터베이스에만 적용되며 인메모리 인스턴스에는 적용되지 않아요. 인메모리 데이터베이스에 압축을 켜려면 ATTACH와 함께 COMPRESS 옵션을 사용하세요.

사용 가능한 압축 알고리즘은 사용되는 스토리지 버전에 따라 달라지므로, 모든 압축 알고리즘에 접근하려면 명시적 스토리지 버전을 설정해야 할 수도 있어요.

압축 알고리즘

DuckDB가 지원하는 압축 알고리즘은 다음과 같아요.

  • Constant Encoding
  • Run-Length Encoding (RLE)
  • Bit Packing
  • Frame of Reference (FOR)
  • Dictionary Encoding
  • Fast Static Symbol Table (FSST) – VLDB 2020 논문
  • Adaptive Lossless Floating-Point Compression (ALP) – SIGMOD 2024 논문
  • Chimp – VLDB 2022 논문
  • Patas
  • Zstd

디스크 사용량

DuckDB 형식의 디스크 사용량은 데이터 타입과 데이터 분포, 사용된 압축 방법 등 여러 요인에 따라 달라져요. 대략적으로, 압축되지 않은 CSV 파일 100GB를 DuckDB 데이터베이스 파일에 로드하면 약 25GB의 디스크 공간이 필요하고, Parquet 파일 100GB를 로드하면 약 120GB가 필요해요.

행 그룹

DuckDB의 스토리지 형식은 데이터를 행 그룹(row groups), 즉 데이터의 수평 파티션으로 저장해요. 이 개념은 Parquet의 행 그룹과 동등합니다. 병렬성과 압축을 포함한 DuckDB의 여러 기능이 행 그룹에 기반해요.

행 그룹 크기는 ATTACH 문의 옵션으로 지정할 수 있어요.

ATTACH '/tmp/somefile.db' AS db (ROW_GROUP_SIZE 16384);

문제 해결

비호환 데이터베이스 파일을 열 때의 오류 메시지

사용 중인 DuckDB 버전과 다른 DuckDB 버전이 작성한 데이터베이스 파일을 열면 다음 오류 메시지가 발생할 수 있어요.

Error: unable to open database "...": Serialization Error: Failed to deserialize: ...

이 메시지는 데이터베이스 파일이 더 새로운 DuckDB 버전으로 만들어졌고, 파일을 읽는 데 사용된 DuckDB 버전과 하위 비호환적인 기능을 사용한다는 뜻이에요.

두 가지 잠재적 해결 방법이 있어요.

  1. DuckDB 버전을 최신 안정 버전으로 업데이트하세요.
  2. 최신 버전의 DuckDB로 데이터베이스를 열고 표준 형식(예: Parquet)으로 내보낸 뒤, 어떤 버전의 DuckDB로든 다시 가져오세요. 자세한 내용은 EXPORT/IMPORT DATABASE 문을 참고해 주세요.

더 알아보기 (Learn more)

  • EXPORT/IMPORT 문법은 sql/statements/export 문서를 참고해 주세요.