비결정적 동작

비결정적 동작 (Non-Deterministic Behavior)

DuckDB의 여러 연산자는 비결정적(non-deterministic)으로 동작해요. 특히 SQL은 집합(set) 의미론을 쓰기 때문에 결과가 서로 다른 순서로 반환될 수 있어요. DuckDB는 이를 활용해 성능을 개선하고, 특히 멀티스레드 쿼리 실행에서 이득을 얻어요. 여기에 더해 컴파일러, 운영체제, 하드웨어 아키텍처가 다르면 정렬 순서가 달라질 수도 있어요.

이 페이지는 비결정성이 예상된 동작인 경우를 정리해 둔 거예요. 쿼리를 결정적으로 만들고 싶다면 아래 "비결정성 우회하기" 섹션을 참고하세요.

출처: 공식문서

집합 의미론 (Set Semantics)

비결정성의 가장 흔한 원인은 SQL이 쓰는 집합 의미론이에요. 예를 들어 아래 쿼리를 반복해서 실행하면 두 가지 다른 결과가 나올 수 있어요.

SELECT *
FROM (
    SELECT 'A' AS x
    UNION
    SELECT 'B' AS x
);

A, BB, 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 범위의 아주 작은 숫자가 나올 수 있어요.

비결정성 우회하기

대부분의 사용 사례에서 비결정성은 문제를 일으키지 않아요. 하지만 결정적인 결과가 필요한 경우도 있죠. 그런 경우 아래 우회 방법을 시도해 보세요.

  1. 멀티스레딩이 만드는 비결정성을 막기 위해 스레드 수를 제한해요.
SET threads = 1;
  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)