Trino Fault-tolerant Execution 심화 — Exchange Manager 와 재시도 정책
장시간 ETL 배치 도중 워커가 죽어도 쿼리를 살리는 Trino 의 FTE 를 깊게 들여다봅니다. QUERY vs TASK 재시도 정책, exchange manager(스풀 저장소) 구성, 스팟 인스턴스 운영, 그리고 대화형 쿼리에서 켜면 안 되는 이유까지.
몇 시간을 달려온 ETL 배치가 마지막 단계에서 워커 한 대가 갑자기 죽어 처음부터 다시 돌려야 했던 경험, 한 번쯤 있지 않으신가요? 스팟 인스턴스를 쓰고 있다면 이런 일은 더 자주 생깁니다. Trino 기본 실행 모델은 "all-or-nothing" 이라서, 태스크 하나만 실패해도 쿼리 전체가 실패합니다. 짧은 대화형 쿼리라면 다시 던지면 그만이지만, 장시간 배치가 통째로 날아가는 건 비용과 시간 모두 엄청난 낭비죠.
Fault-tolerant Execution(FTE) 은 바로 이 문제를 해결하기 위해 만들어졌습니다. 중간 결과를 외부 스토리지에 스풀해 두고, 실패한 단위만 다른 워커에서 재시도하거든요. 이 글에서는 FTE 가 내부적으로 어떻게 동작하는지, 어떻게 설정하는지, 그리고 언제 켜고 언제 꺼야 하는지를 함께 살펴봅시다.
이 글에서 배우는 것
- Trino 기본 실행 모델의 한계와 FTE 가 필요한 이유
- 중간 결과 스풀링의 핵심 아이디어와 exchange manager 역할
QUERYvsTASK두 가지 재시도 정책의 차이와 선택 기준- Exchange manager 구성 방법과 스풀 저장소 선택 요령
- 스팟 인스턴스 비용 절감에 FTE 를 활용하는 패턴
1. 왜 FTE 가 필요한가 — 기본 실행 모델의 한계
일반 모드에서 Trino 가 어떻게 동작하는지 먼저 이해해 봅시다. 스테이지 간 데이터를 메모리/네트워크로 직접 스트리밍(streaming exchange)합니다. 빠르지만, 중간 데이터가 어디에도 보존되지 않습니다.
데이터 규모가 크고 실행이 길수록 "도중에 워커가 죽을 확률"이 올라갑니다. 스팟 인스턴스를 쓰면 회수(reclaim)로 워커가 수시로 사라지므로 이 확률은 더 커집니다. 긴 배치를 매번 처음부터 재실행하는 것은 비용과 시간 모두 낭비입니다.
2. FTE 의 핵심 아이디어 — 중간 결과 스풀
한 문장으로: FTE 는 스테이지 간 데이터를 외부 스토리지에 저장해 두고, 실패한 부분만 그 지점부터 다시 실행하는 체크포인트 메커니즘입니다.
FTE 는 스테이지 간 데이터를 직접 스트리밍하는 대신, exchange manager 를 통해 외부 스토리지(S3/GCS/HDFS 등)에 스풀합니다. 중간 결과가 보존되므로, 워커가 죽어도 그 지점부터 다시 시작할 수 있습니다.
| 일반(streaming) | FTE(spooling) | |
|---|---|---|
| 스테이지 간 데이터 | 메모리/네트워크 직접 전달 | exchange manager 가 외부 스토리지에 스풀 |
| 워커 소실 시 | 쿼리 전체 실패 | 실패 단위만 재시도 |
| 지연(latency) | 낮음 | 스풀 I/O 만큼 증가 |
| 적합 | 대화형 쿼리 | 장시간 ETL 배치 |
3. 두 가지 재시도 정책 — QUERY vs TASK
FTE 를 켜기로 했다면, 재시도를 어떤 단위로 할지 선택해야 합니다. FTE 는 retry-policy 로 재시도 단위를 정합니다.
# etc/config.properties
retry-policy=TASK| 정책 | 재시도 단위 | 동작 | 적합 |
|---|---|---|---|
QUERY | 쿼리 전체 | 실패 시 쿼리를 통째로 다시 실행 | 짧은 쿼리가 많은 대화형 클러스터 |
TASK | 개별 태스크 | 실패한 태스크만 다른 워커에서 재시도 | 장시간·대규모 ETL 배치 |
QUERY 정책
쿼리가 실패하면 코디네이터가 자동으로 전체를 재실행합니다. 사용자는 실패를 모르고 결과만 받게 되죠. 짧은 쿼리에 적합하고, exchange manager 없이도(작은 결과는 코디네이터 메모리에) 동작할 수 있지만, 큰 쿼리에는 exchange manager 가 권장됩니다.
TASK 정책
쿼리를 구성하는 개별 태스크 단위로 재시도합니다. 반드시 exchange manager 가 필요합니다. 장시간 배치에서 한 워커가 죽어도 그 워커가 하던 태스크만 살아있는 다른 워커로 넘겨 재실행하므로, 전체를 다시 돌리지 않습니다. 또한 TASK 모드는 적응형 태스크 스케줄링이 가능해, 워커 수가 변해도(오토스케일·스팟) 유연하게 대응합니다.
규칙: 대화형 클러스터는
QUERY, ETL/배치 클러스터는TASK. 하나의 클러스터에서 둘을 섞고 싶다면 세션 레벨로 전환할 수 있습니다 —SET SESSION retry_policy = 'TASK'.
4. Exchange Manager 구성
TASK 정책(그리고 큰 QUERY)에는 exchange manager 가 필수입니다. 스풀 저장소를 어디에 둘지 지정해 봅시다.
# etc/exchange-manager.properties
exchange-manager.name=filesystem
exchange.base-directories=s3://trino-exchange/spool
exchange.encryption-enabled=trueS3 자격증명·엔드포인트는 파일시스템 설정을 따릅니다.
# 같은 파일 또는 config 에서 S3 접근 설정
exchange.s3.region=ap-northeast-2
exchange.s3.endpoint=https://s3.ap-northeast-2.amazonaws.com스풀 저장소 선택
어떤 저장소를 골라야 할지 비교해 보면 이렇습니다.
| 저장소 | 장점 | 고려사항 |
|---|---|---|
| S3 / GCS / Azure Blob | 무제한 용량, 클러스터와 분리 | 네트워크 지연, 요청 비용 |
| HDFS | 온프레미스 기존 자산 활용 | NameNode 부하 |
| 로컬 디스크(여러 워커 공유 X) | 빠름 | 워커 소실 시 스풀도 소실 → FTE 무의미 |
핵심: 스풀 저장소는 워커와 생명주기가 분리되어야 합니다. 워커가 죽었을 때 스풀이 남아있어야 재시도가 가능하기 때문에, 로컬 디스크는 부적합하고 오브젝트 스토리지가 정석입니다.
여러 저장소 분산
대규모 클러스터에서는 스풀 I/O 가 병목이 될 수 있어, 여러 디렉터리/버킷에 분산하면 됩니다.
exchange.base-directories=s3://trino-exchange-1/spool,s3://trino-exchange-2/spool5. 관련 튜닝 파라미터
FTE 의 동작을 세밀하게 조정하고 싶다면 아래 옵션들을 활용해 보세요.
| 파라미터 | 역할 |
|---|---|
fault-tolerant-execution-task-memory | TASK 모드에서 태스크당 메모리 추정치(스케줄링 단위) |
fault-tolerant-execution-target-task-input-size | 태스크 입력 목표 크기 (태스크 분할 입도) |
fault-tolerant-execution-target-task-split-count | 태스크당 split 목표 수 |
fault-tolerant-execution-max-task-split-count | 태스크당 split 상한 |
exchange.compression-enabled | 스풀 데이터 압축으로 I/O·비용 절감 |
query.low-memory-killer.policy | 메모리 부족 시 죽일 대상 선정 정책 |
압축은 거의 항상 켜는 것이 이득입니다(네트워크·스토리지 비용 절감). 태스크 입력 크기는 너무 작으면 오버헤드가, 너무 크면 재시도 비용이 커지므로 워크로드에 맞춰 조정합니다.
6. FTE 와 스팟 인스턴스 — 비용 절감의 핵심
FTE 의 가장 강력한 활용처는 스팟 인스턴스로 워커를 운영하는 것입니다.
스팟 회수로 워커가 갑자기 사라져도, 스풀된 중간 결과 덕분에 살아있는 워커가 태스크를 이어받습니다. Kubernetes 환경이라면 코디네이터는 온디맨드, 워커는 스팟 + FTE 조합이 정석입니다. (Kubernetes 배포는 별도 글 "Trino 를 Kubernetes 에 배포하기"에서 다뤘습니다.)
7. 트레이드오프 — FTE 를 켜면 잃는 것
FTE 는 공짜가 아닙니다. 도입 전에 다음 트레이드오프를 여러분의 워크로드에 대입해 보세요.
- 지연 증가: 모든 스테이지 경계에서 외부 스토리지에 쓰고 다시 읽으므로, 짧은 쿼리는 오히려 느려집니다. 밀리초~초 단위 대화형 쿼리에는 부적합합니다.
- 스토리지 비용/요청: S3 PUT/GET 요청과 저장 용량 비용이 발생합니다. 압축으로 완화하세요.
- 운영 복잡도: exchange manager, 스풀 버킷, 권한, 정리(lifecycle) 정책을 관리해야 합니다.
| 워크로드 | 권장 |
|---|---|
| BI 대시보드, 대화형 분석 | FTE 끄기 (streaming) |
| 짧은 쿼리 다수 | QUERY (선택) |
| 장시간 ETL, 대규모 조인/집계 | TASK + exchange manager |
| 스팟 워커 클러스터 | TASK + exchange manager (필수에 가까움) |
8. 검증 — FTE 가 실제로 동작하는지
-- 현재 세션의 재시도 정책 확인
SHOW SESSION LIKE 'retry_policy';
-- 세션 단위로 전환 (대화형 클러스터에서 무거운 배치 하나만 FTE 로)
SET SESSION retry_policy = 'TASK';설정을 마쳤다면 직접 확인해 봅시다. 실행 후 코디네이터 Web UI 의 Query Detail 에서 재시도된 태스크가 있었는지, exchange 가 spooling 으로 동작했는지 확인할 수 있습니다. 스팟 회수 테스트로 워커를 의도적으로 죽여보고 쿼리가 완주하는지 검증하는 것을 권장합니다.
9. 스풀 저장소 정리 (Lifecycle)
스풀 데이터는 쿼리 종료 시 Trino 가 정리하지만, 비정상 종료·중단으로 고아 객체가 남을 수 있습니다. 버킷에 lifecycle 정책(예: prefix spool/ 의 객체를 1~3일 후 자동 삭제)을 걸어 비용 누수를 막으세요.
S3 Lifecycle Rule: prefix "spool/", expire after 2 days10. 정리
| 항목 | 일반 모드 | FTE 모드 |
|---|---|---|
| 실패 처리 | 쿼리 전체 실패 | 태스크/쿼리 단위 재시도 |
| 중간 데이터 | 메모리 스트리밍 | exchange manager 스풀 |
| 지연 | 낮음 | 스풀 I/O 만큼 증가 |
| 스팟 운영 | 위험 | 안전 (큰 비용 절감) |
| 적합 워크로드 | 대화형 | 장시간 배치 |
FTE 는 "안정성을 위해 약간의 지연을 지불"하는 기능입니다. 대화형 클러스터에는 켜지 말고, 장시간 ETL 과 스팟 기반 배치 클러스터에는 TASK 정책 + 오브젝트 스토리지 exchange manager 로 구성하세요. 스풀 압축을 켜고 버킷에 lifecycle 정책을 걸면, 안정성과 비용 두 마리를 모두 잡을 수 있습니다.
마치며 — 핵심 요약
- 기본 Trino 는 all-or-nothing: 태스크 하나가 실패하면 쿼리 전체가 날아갑니다. 장시간 배치에서는 치명적이죠.
- FTE 는 체크포인트: 스테이지 간 데이터를 외부 스토리지에 스풀해 두고, 실패한 태스크만 재시도합니다.
- 재시도 정책은 목적에 따라: 대화형 클러스터는
QUERY, ETL 배치 클러스터는TASK를 선택하면 됩니다. - 스풀 저장소는 오브젝트 스토리지가 정석: 워커와 생명주기가 분리되어야 재시도가 의미 있습니다.
- 스팟 인스턴스와의 조합이 최강: FTE(TASK) + 스팟 워커로 비용을 최대 70~90% 절감할 수 있습니다.
- 운영 필수 체크리스트: 스풀 압축 활성화 + 버킷 lifecycle 정책으로 비용 누수를 막으세요.
FTE 하나로 배치 안정성과 인프라 비용, 두 문제를 동시에 해결할 수 있습니다. 여러분의 클러스터에도 꼭 적용해 보시길 바랍니다!
이 글은 Trino 440번대 기준으로 작성되었습니다. 대규모 배치의 안정화나 스팟 기반 비용 최적화가 필요하시면 언제든 문의해 주세요.
— Data Dynamics 엔지니어링 팀