인덱스
인덱스 (Indexes)
DuckDB는 두 가지 내장 인덱스 타입을 제공해요. 인덱스는 확장을 통해서도 정의할 수 있어요.
출처: 문서
본문
인덱스 타입 (Index Types)
최소-최대 인덱스 (Min-Max Index / Zonemap)
min-max 인덱스(zonemap 또는 block range index라고도 해요)는 모든 일반 목적 데이터 타입 컬럼에 대해 _자동으로 생성_돼요.
Adaptive Radix Tree (ART)
Adaptive Radix Tree (ART)는 주로 기본 키 제약을 보장하고, 특정 값(point) 조회와 선택도가 매우 높은(< 0.1%) 쿼리를 빠르게 처리하는 데 사용돼요. ART 인덱스는 CREATE INDEX 절을 이용해 수동으로 만들 수 있고, UNIQUE 또는 PRIMARY KEY 제약이 있는 컬럼에는 자동으로 생성돼요.
경고: ART 인덱스는 현재 인덱스 생성 시점에 메모리에 들어갈 수 있어야 해요. 인덱스가 생성 시점에 메모리에 맞지 않는다면 ART 인덱스 생성을 피하세요.
확장으로 정의된 인덱스 (Indexes Defined by Extensions)
DuckDB는 spatial 확장을 통해 공간 인덱싱용 R-tree를 지원해요.
영속성 (Persistence)
min-max 인덱스와 ART 인덱스는 모두 디스크에 영속화돼요.
CREATE INDEX와 DROP INDEX 문
ART 인덱스를 만들려면 CREATE INDEX 문을 사용해요. ART 인덱스를 삭제하려면 DROP INDEX 문을 사용해요.
ART 인덱스의 제약사항 (Limitations of ART Indexes)
ART 인덱스는 데이터의 사본을 두 번째 위치에 추가로 만들어요. 이 사본을 유지 관리하는 것은 처리를 복잡하게 만들기 때문에, 현재는 보조 인덱스(secondary index)에도 저장된 데이터를 수정할 때 몇 가지 제약이 적용돼요.
예상대로 인덱스는 성능에 큰 영향을 줘요. 로딩과 업데이트는 느려지지만 특정 쿼리는 빨라져요. 자세한 내용은 성능 가이드를 참고하세요.
UPDATE 문에서의 제약 확인 (Constraint Checking in UPDATE Statements)
인덱스가 있는 컬럼과 제자리(in place)에서 업데이트할 수 없는 컬럼에 대한 UPDATE 문은, 원래 행을 DELETE한 뒤 업데이트된 행을 INSERT하는 형태로 변환돼요. 이 재작성(rewrite)은 영향을 받은 컬럼만 다시 쓰는 게 아니라 전체 행을 다시 쓰기 때문에, 특히 넓은(wide) 테이블에서 성능에 영향을 줘요.
또한 이 때문에 UPDATE 문에 다음과 같은 제약 확인 제한이 생겨요. PostgreSQL 같은 다른 DBMS에도 같은 제한이 존재해요.
아래 예시에서 행 수가 DuckDB의 기본 벡터 크기인 2048을 초과하는 점을 주목해 보세요. UPDATE 문은 DELETE 후 INSERT로 재작성돼요. 이 재작성은 DuckDB 처리 파이프라인을 통과하는 데이터 청크(2048행)마다 발생해요. i = 2047을 i = 2048로 업데이트할 때 우리는 2048이 2049가 되는 것을 아직 알 수 없어요. 그 청크를 아직 보지 못했기 때문이에요. 그래서 제약 위반을 던지게 돼요.
CREATE TABLE my_table (i INTEGER PRIMARY KEY);
INSERT INTO my_table SELECT range FROM range(3_000);
UPDATE my_table SET i = i + 1;
Constraint Error:
Duplicate key "i: 2048" violates primary key constraint.
해결 방법은 UPDATE를 DELETE ... RETURNING ... 다음 INSERT로 나누고, DELETE의 결과를 (임시로) 저장하는 추가 로직을 사용하는 거예요. 모든 문은 BEGIN으로 트랜잭션 안에서 실행하고 마지막에 COMMIT해요.
명령줄 클라이언트에서 그렇게 보일 수 있는 예시를 보여드릴게요.
CREATE TABLE my_table (i INTEGER PRIMARY KEY);
INSERT INTO my_table SELECT range FROM range(3_000);
BEGIN;
CREATE TEMP TABLE tmp AS SELECT i FROM my_table;
DELETE FROM my_table;
INSERT INTO my_table SELECT i FROM tmp;
DROP TABLE tmp;
COMMIT;
다른 클라이언트에서는 DELETE ... RETURNING ...의 결과를 가져올 수 있을 거예요. 그 결과를 이후 INSERT ... 문에 사용하거나, DuckDB의 Appender(클라이언트에서 사용 가능한 경우)를 활용할 수 있어요.
외래 키에서의 과도한 제약 확인 (Over-Eager Constraint Checking in Foreign Keys)
이 제한은 다음 조건을 만족할 때 발생해요.
- 테이블에
FOREIGN KEY제약이 있다. - 복합 payload 컬럼(예:
LIST나STRUCT)에 대한UPDATE와 그에 해당하는PRIMARY KEY테이블이 있는데, DuckDB가 이를DELETE후INSERT로 재작성한다. - 삭제 대상 행이 외래 키 테이블에 존재한다.
이 조건들이 성립하면 예상치 못한 제약 위반을 만나게 돼요.
CREATE TABLE pk_table (id INTEGER PRIMARY KEY, payload VARCHAR[]);
INSERT INTO pk_table VALUES (1, ['hello']);
CREATE TABLE fk_table (id INTEGER REFERENCES pk_table(id));
INSERT INTO fk_table VALUES (1);
UPDATE pk_table SET payload = ['world'] WHERE id = 1;
Constraint Error:
Violates foreign key constraint because key "id: 1" is still referenced by a foreign key in a different table. If this is an unexpected constraint violation, please refer to our foreign key limitations in the documentation
그 이유는 DuckDB가 아직 "미리 보기(looking ahead)"를 지원하지 않기 때문이에요. INSERT 동안에는 UPDATE 재작성의 일부로 외래 키 값을 다시 삽입한다는 사실을 알지 못해요.