비결정적 동작
비결정적 동작 (Non-Deterministic Behavior)
DuckDB의 여러 연산자는 비결정적(non-deterministic)으로 동작해요. 특히 SQL은 집합(set) 의미론을 쓰기 때문에 결과가 서로 다른 순서로 반환될 수 있어요. DuckDB는 이를 활용해 성능을 개선하고, 특히 멀티스레드 쿼리 실행에서 이득을 얻어요. 여기에 더해 컴파일러, 운영체제, 하드웨어 아키텍처가 다르면 정렬 순서가 달라질 수도 있어요.
이 페이지는 비결정성이 예상된 동작인 경우를 정리해 둔 거예요. 쿼리를 결정적으로 만들고 싶다면 아래 "비결정성 우회하기" 섹션을 참고하세요.
출처: 공식문서
집합 의미론 (Set Semantics)
비결정성의 가장 흔한 원인은 SQL이 쓰는 집합 의미론이에요. 예를 들어 아래 쿼리를 반복해서 실행하면 두 가지 다른 결과가 나올 수 있어요.
SELECT *
FROM (
SELECT 'A' AS x
UNION
SELECT 'B' AS x
);
A, B든 B, A든 둘 다 올바른 결과예요.
플랫폼마다 다른 결과: array_distinct
array_distinct 함수는 플랫폼에 따라 다른 순서로 결과를 반환할 수 있어요.
SELECT array_distinct(['A', 'A', 'B', NULL, NULL]) AS arr;
이 쿼리에서는 [A, B]와 [B, A]가 모두 유효한 결과예요.
멀티스레딩과 부동소수점 집계 연산
부동소수점 불정확성 때문에 멀티스레드 설정에서 실행하면 결과가 달라질 수 있어요. 예를 들어 stddev와 corr가 비결정적 결과를 낼 수 있어요.
CREATE TABLE tbl AS
SELECT 'ABCDEFG'[floor(random() * 7 + 1)::INT] AS s, 3.7 AS x, i AS y
FROM range(1, 1_000_000) r(i);
SELECT s, stddev(x) AS standard_deviation, corr(x, y) AS correlation
FROM tbl
GROUP BY s
ORDER BY s;
이 쿼리에서 예상되는 표준편차와 상관계수는 모든 s 값에 대해 0이에요. 하지만 여러 스레드에서 실행하면 부동소수점 불정확성 때문에 0 <= z < 10e-16 범위의 아주 작은 숫자가 나올 수 있어요.
비결정성 우회하기
대부분의 사용 사례에서 비결정성은 문제를 일으키지 않아요. 하지만 결정적인 결과가 필요한 경우도 있죠. 그런 경우 아래 우회 방법을 시도해 보세요.
- 멀티스레딩이 만드는 비결정성을 막기 위해 스레드 수를 제한해요.
SET threads = 1;
- 정렬 순서를 강제해요. 예를 들어 ORDER BY ALL 절을 쓸 수 있어요.
SELECT *
FROM (
SELECT 'A' AS x
UNION
SELECT 'B' AS x
)
ORDER BY ALL;
리스트는 list_sort로 정렬할 수도 있어요.
SELECT list_sort(array_distinct(['A', 'A', 'B', NULL, NULL])) AS i
ORDER BY i;
또한 결정적 셔플링을 도입하는 방법도 있어요.
더 알아보기 (Learn more)
- ORDER BY ALL — 정렬을 강제하는 절.
- list_sort — 리스트 정렬 함수.