Query exceeded distributed total memory limit of 8GB
메시지에 나온 한계가 어느 속성인지 정확히 읽는 것이 시작이다. Trino 의 메모리 한계는 세 개이고 서로 다른 것을 잰다.
| 속성 | 기본값 | 재는 것 | 세션 속성 |
|---|---|---|---|
query.max-memory-per-node |
JVM 힙의 30% | 쿼리 하나가 노드 하나에서 쓰는 사용자 메모리 | query_max_memory_per_node |
query.max-memory |
20GB |
쿼리 하나가 클러스터 전체에서 쓰는 사용자 메모리 | query_max_memory |
query.max-total-memory |
query.max-memory × 2 |
회수 가능 메모리까지 포함한 클러스터 전체 합계 | query_max_total_memory |
distributed total memory limit 은 세 번째, query.max-total-memory 다. query.max-memory 를 아무리 올려도 이 메시지는 사라지지 않는다. 현재 적용값을 먼저 확인한다.
SHOW SESSION LIKE 'query_max%';
세 값은 서로 묶여 있고, 그 위에 JVM 힙이 있다. 힙을 넘겨 잡으면 한계에 걸리는 대신 GC 폭주나 OOM 으로 바뀔 뿐이라 상황이 더 나빠진다.
# config.properties — 워커 3대, 각 힙 32GB 인 경우의 예
query.max-memory-per-node=8GB
query.max-memory=18GB
query.max-total-memory=36GB
지켜야 할 것은 셋이다. query.max-total-memory 는 query.max-memory 이상이어야 한다. query.max-memory-per-node 는 워커 힙에서 memory.heap-headroom-per-node(기본 힙의 30%) 를 뺀 나머지를 넘지 않아야 한다. query.max-memory 는 워커 수 × query.max-memory-per-node 를 넘겨 잡아도 실효가 없다.
세션에서 임시로 올릴 수도 있다. 클러스터 설정을 바꾸기 전에 어느 값이 걸리는지 확인하는 용도로 쓴다.
SET SESSION query_max_total_memory = '16GB';
설정값을 올렸는데 여전히 낮은 한계로 실패한다면 resource group 을 본다. etc/resource-groups.json 의 softMemoryLimit · hardConcurrencyLimit 이 그룹 단위로 별도 상한을 건다. 클러스터 전체 설정보다 우선한다.
메모리에 다 올릴 수 없는 조인·집계·정렬을 디스크로 흘려보내는 기능이다. 쿼리는 통과하지만 느려지므로 최후 수단이다.
spill-enabled=true
spiller-spill-path=/data/trino/spill
spill 경로는 충분한 용량과 빠른 디스크여야 하고, 여러 경로를 쉼표로 나열해 분산할 수 있다.
메모리를 터뜨리는 패턴은 대체로 정해져 있다. 한계를 올리기 전에 이쪽을 본다.
| 패턴 | 대응 |
|---|---|
| 큰 테이블끼리의 조인 | 조인 키로 미리 줄이기, 작은 쪽을 broadcast 로 유도, 통계 수집으로 조인 순서 개선 |
카디널리티가 높은 GROUP BY |
사전 집계, approx_distinct 같은 근사 함수 |
LIMIT 없는 ORDER BY |
정렬 전에 범위를 좁히거나 LIMIT 을 건다 |
의도치 않은 CROSS JOIN |
조인 조건 누락 여부 확인 |
쿼리 상태가 QUEUED 로 머무르며 "waiting for resources" 로 보이는 것은 오류가 아니라 대기다. 실행에 필요한 자원을 아직 확보하지 못한 상태다. 순서대로 좁힌다.
-- 살아 있는 노드
SELECT node_id, state FROM system.runtime.nodes;
-- 대기 중인 쿼리
SELECT query_id, state, query FROM system.runtime.queries WHERE state = 'QUEUED';
노드 목록에 워커가 보이지 않으면 워커가 코디네이터에 등록되지 않은 것이다. Trino 연결 거부와 노드 등록 실패 를 본다.
워커는 정상인데 계속 대기한다면 원인은 셋 중 하나다. 첫째, 앞선 쿼리들이 클러스터 메모리를 잡고 있어 새 쿼리가 예약에 실패하는 경우다. 둘째, resource group 의 동시 실행 상한에 걸린 경우다. BI 도구나 대시보드가 짧은 쿼리를 다수 던지는 환경에서 CPU 는 한가한데 대기만 길어진다면 대체로 이쪽이다. 셋째, 커넥터가 스플릿을 만드는 데 시간이 걸리는 경우다. 파티션이 매우 많은 Hive 테이블에서 메타스토어 응답이 느리면 실행 전 단계가 길어진다.
Kubernetes 환경에서는 워커 파드의 CPU·메모리 limit 이 너무 빡빡해 파드가 Pending 이거나 반복 재시작 중인 경우가 흔하다. 노드 목록에 몇 대가 보이는지부터 확인한다.