디스크 스필

디스크 스필 (Spill to disk)

메모리 집약적인 연산에서 Trino는 중간 연산 결과를 디스크로 내려(offload) 실행할 수 있어요.

출처: 문서

본문

개요 (Overview)

메모리 집약적인 연산의 경우 Trino는 중간 연산 결과를 디스크로 내려 실행할 수 있어요. 이 메커니즘의 목표는 쿼리당 또는 노드당 한도를 초과하는 메모리를 요구하는 쿼리를 실행할 수 있게 하는 것입니다.

이 메커니즘은 OS 수준의 페이지 스와핑과 비슷해요. 다만 Trino의 특정 요구를 해결하기 위해 애플리케이션 수준에서 구현되었어요.

스필링 관련 속성은 스필링 속성(Spilling properties) 문서에 설명되어 있어요.

경고 (Warning)

디스크 스필 기능과 구현은 Trino의 레거시 기능이에요. task 재시도 정책과 구성된 교환 매니저를 쓰는 장애 허용 실행을 고려하세요.

메모리 관리와 스필 (Memory management and spill)

기본적으로 쿼리 실행이 요청한 메모리가 세션 속성 query_max_memory 또는 query_max_memory_per_node를 초과하면 Trino는 쿼리를 종료해요. 이 메커니즘은 쿼리에 메모리를 공정하게 할당하고 메모리 할당으로 인한 데드락을 방지해요. 클러스터에 작은 쿼리가 많을 때는 효율적이지만, 한도 안에 머물지 못하는 큰 쿼리를 종료시키는 문제가 있어요.

이 비효율을 극복하기 위해 회수 가능한 메모리(revocable memory) 개념이 도입됐어요. 쿼리는 한도에 계산되지 않는 메모리를 요청할 수 있지만, 이 메모리는 메모리 매니저가 언제든 회수할 수 있어요. 메모리가 회수되면 쿼리 러너는 중간 데이터를 메모리에서 디스크로 스필하고 나중에 계속 처리해요.

실제로 클러스터가 유휴 상태이고 모든 메모리가 사용 가능할 때는 메모리 집약적인 쿼리가 클러스터의 모든 메모리를 사용할 수 있어요. 반대로 클러스터에 여유 메모리가 많지 않으면 같은 쿼리도 중간 데이터 저장에 디스크를 쓰도록 강제될 수 있어요. 디스크로 스필하도록 강제된 쿼리는 완전히 메모리에서 실행되는 쿼리보다 실행 시간이 수 배에서 수십 배 길어질 수 있어요.

스필을 활성화한다고 모든 메모리 집약적 쿼리의 실행이 보장되는 것은 아니라는 점에 유의하세요. 쿼리 러너가 중간 데이터를 메모리에 들어갈 만큼 작은 청크로 나누지 못하면, 디스크에서 데이터를 로드하는 동안 Out of memory 오류가 여전히 발생할 수 있어요.

스필 디스크 공간 (Spill disk space)

중간 결과를 디스크로 스필하고 다시 가져오는 것은 IO 연산 측면에서 비용이 커요. 그래서 스필을 사용하는 쿼리는 디스크에 의해 조절(throttle)될 가능성이 커요. 쿼리 성능을 높이려면 스필링 속성spiller-spill-path 속성으로 별도의 로컬 장치에 여러 경로를 제공하는 것이 권장돼요.

시스템 드라이브에는 스필하지 말아야 하고, 특히 JVM이 실행되고 로그를 쓰는 드라이브에는 절대 스필하면 안 돼요. 그러면 클러스터가 불안정해질 수 있어요. 또한 구성된 스필 경로의 디스크 포화 상태를 모니터링하는 것이 권장돼요.

Trino는 스필 경로를 독립적인 디스크로 취급하므로(JBOD 참고), 스필에 RAID를 쓸 필요가 없어요.

스필 압축 (Spill compression)

spill-compression-codec 속성으로 스필 압축을 활성화하면 스필된 페이지는 디스크에 쓰기 전에 압축돼요. 이 기능을 활성화하면 스필된 페이지의 압축·해제에 추가 CPU 부하가 드는 대신 디스크 IO를 줄일 수 있어요.

스필 암호화 (Spill encryption)

스필링 속성spill-encryption-enabled 속성으로 스필 암호화를 활성화하면 스필 내용이 (스필 파일마다) 무작위로 생성된 비밀 키로 암호화돼요. 이걸 활성화하면 CPU 부하가 늘고 디스크 스필 처리량이 줄지만, 스필 파일에서 스필된 데이터가 복구되는 것을 보호할 수 있어요. 스필 암호화를 활성화할 때는 스필링 지연 증가를 감안해 memory-revoking-threshold 값을 낮추는 것을 고려하세요.

지원 연산 (Supported operations)

모든 연산이 디스크 스필을 지원하는 것은 아니며, 각 연산은 스필을 다르게 처리해요. 현재 이 메커니즘은 다음 연산에 구현되어 있어요.

조인 (Joins)

조인 연산 중에는 조인되는 테이블 중 하나가 메모리에 저장돼요. 이 테이블을 빌드 테이블(build table)이라고 불러요. 다른 테이블의 행은 스트리밍되어 빌드 테이블의 행과 매칭되면 다음 연산으로 전달돼요. 조인의 가장 메모리 집약적인 부분이 바로 이 빌드 테이블이에요.

태스크 동시성이 1보다 크면 빌드 테이블은 파티셔닝돼요. 파티션 수는 task.concurrency 구성 파라미터의 값과 같아요(태스크 속성 참고).

빌드 테이블이 파티셔닝되면 스필 메커니즘은 조인 연산에 필요한 최대 메모리 사용량을 줄일 수 있어요. 쿼리가 메모리 한도에 가까워지면 빌드 테이블의 파티션 일부가, 그 파티션에 속하는 다른 테이블의 행과 함께 디스크로 스필돼요. 스필되는 파티션 수는 필요한 디스크 공간 양에 영향을 줘요.

이후 스필된 파티션을 하나씩 다시 읽어 조인 연산을 마무리해요.

이 메커니즘으로 조인 연산자가 사용하는 최대 메모리는 가장 큰 빌드 테이블 파티션의 크기로 줄어들 수 있어요. 데이터 스큐가 없다고 가정하면 이는 전체 빌드 테이블 크기의 1 / task.concurrency 배예요.

집계 (Aggregations)

집계 함수는 값 그룹에 연산을 수행하고 하나의 값을 반환해요. 집계하는 그룹 수가 많으면 상당한 메모리가 필요할 수 있어요. 스필이 활성화되어 있고 메모리가 충분하지 않으면 중간 누적 집계 결과가 디스크에 기록돼요. 이 결과는 더 낮은 메모리 사용량으로 다시 로드되어 병합돼요.

Order by

더 많은 양의 데이터를 정렬하려고 하면 상당한 메모리가 필요할 수 있어요. order by에 대한 스필이 활성화되어 있고 메모리가 충분하지 않으면 중간 정렬 결과가 디스크에 기록돼요. 이 결과는 더 낮은 메모리 사용량으로 다시 로드되어 병합돼요.

윈도우 함수 (Window functions)

윈도우 함수는 행의 윈도우에 연산을 수행하고 각 행에 대해 하나의 값을 반환해요. 이 행의 윈도우가 크면 상당한 메모리가 필요할 수 있어요. 윈도우 함수에 대한 스필이 활성화되어 있고 메모리가 충분하지 않으면 중간 결과가 디스크에 기록돼요. 이 결과는 메모리가 사용 가능할 때 다시 로드되어 병합돼요. 단일 윈도우가 매우 큰 경우처럼 스필이 모든 경우에 동작하지는 않는 현재의 제한이 있어요.

더 알아보기 (Learn more)

스필 관련 속성 값이 궁금하다면 스필링 속성(Spilling properties) 문서를 이어서 읽어 보세요.