Search
moon
sun

Athena와 Trino, 같은 엔진 다른 운영 모델

제목
Tag
작성일
지난 글에서 분산 SQL 엔진이 쿼리를 Stage로 쪼개고 Coordinator와 Worker가 나눠 실행하는 구조를 살펴봤다. 이번 글은 그 구조를 실제로 구현한 엔진 하나를 다룬다. 현재 회사 데이터 팀에서는 Athena를 사용중인데 운영 클러스터에는 Trino가 별도로 떠 있고 dbt 모델의 기본 실행 엔진은 Trino로 되어있다.
서버리스 쿼리 엔진이 이미 있는데 왜 클러스터를 직접 운영할까? 이 질문에 답하려면 Trino가 무엇인지 Athena와 무엇이 같고 무엇이 다른지 그리고 그 차이가 실제 워크로드에서 어떤 문제로 나타났는지를 순서대로 봐야 한다.

Trino란

Trino는 분산 SQL 쿼리 엔진이다. 데이터를 저장하지 않고 다른 곳에 있는 데이터를 SQL로 읽고 쓰는 실행 계층만 담당한다.

Presto와 Trino

Trino를 찾아보면 Presto라는 이름이 항상 같이 나온다.
•
2012년 — Facebook이 Hive의 MapReduce 쿼리를 대체하기 위해 Presto 개발. 저장소와 엔진을 분리한 설계의 출발점
•
2019년 — 원 개발자들이 PrestoSQL로 분기, Facebook에 남은 쪽은 PrestoDB로 유지
•
2020년 — 상표 문제로 PrestoSQL이 Trino로 개명
지금은 PrestoDB와 Trino가 별개 프로젝트이고 커넥터 이름, 설정 키, 지원 SQL 기능이 서로 다르게 발전하고 있다.
Athena도 이 계보 위에 있다. 초기 엔진 버전은 Presto 기반이었고, 엔진 버전 3부터 Trino 쪽 개선 사항을 지속적으로 통합한다. 이 글에서 Athena와 Trino를 같은 엔진이라고 할 때는 엔진 버전 3을 기준으로 한다.
지난 글의 분산 SQL 엔진 모델과 대응시키면 다음과 같다.
분산 SQL 엔진 개념
Trino에서의 구현
Coordinator
Coordinator 노드 1대. 파싱, 최적화, Stage 분해, 스케줄링
Worker
Worker 노드 N대. Task 실행, Shuffle
Stage
Stage (플랜 상 Fragment)
Task
Task. Stage 하나가 Worker마다 Task 하나로 배정
Partitioning 단위
Split. 커넥터가 데이터를 쪼갠 조각
Shuffle
Exchange. 기본은 메모리 스트리밍, 옵션으로 S3 spooling
데이터 소스
Connector. 카탈로그 단위로 설정

Trino의 특징

1.
저장소와 컴퓨트 분리
•
데이터는 S3, 스키마는 Glue, 계산은 Trino Worker로 역할이 갈림
•
Worker 증감이 데이터에 영향을 주지 않음 → 쿼리가 몰리는 시간에만 늘리고 밤에는 줄이는 운영이 가능
•
Worker를 스팟 인스턴스로 돌릴 수 있는 전제도 여기서 나옴
2.
메모리 기반 파이프라인 실행
•
Stage 사이 데이터를 디스크에 쓰지 않고 메모리와 네트워크로 흘림
•
Spark와 달리 셔플 파일을 남기지 않아 대화형 쿼리에 빠름
•
대신 중간 결과가 메모리를 넘치면 쿼리가 그대로 실패. Spill은 기본 꺼짐으로 되어있음
3.
커넥터 기반 federation
데이터를 한곳으로 모으지 않고,각자 있는 자리에 둔 채 하나의 쿼리 인터페이스로 묶는 것을 페더레이션이라고 한다. ETL로 웨어하우스에 복사해 넣는 통합(consolidation)과 대비되는 접근이다.
•
카탈로그 = 커넥터 하나 + 그 커넥터의 설정
•
같은 커넥터를 설정만 달리해 여러 카탈로그로 띄우는 것이 가능
•
서로 다른 커넥터의 카탈로그를 한 쿼리에서 조인하는 것도 가능
SELECT u.name, e.event_type FROM mysql_prod.app.users u JOIN glue_table.raw.event_log e ON u.id = e.user_id
SQL
복사
사용자 입장에서는 소스가 무엇이든 catalog.schema.table로 똑같이 보인다. 다만 페더레이션이 공짜는 아니다. 원격 소스에서 데이터를 끌어오는 비용이 쿼리마다 발생하고 커넥터가 predicate·aggregation pushdown을 얼마나 지원하느냐에 따라 성능 차이가 크다.

Athena와 Trino

같은 엔진, 다른 운영 모델

앞서 본 대로 Athena 엔진 버전 3은 Trino 계보 위에 있다. AWS가 이 엔진을 서버리스로 포장하고 과금과 테넌트 격리를 붙인 것이 Athena다. 따라서 SQL 문법과 함수는 거의 같고 Athena에서 돌던 쿼리 대부분은 Trino에서 그대로 돈다. 차이는 엔진이 아니라 운영 모델에 있다.
Athena
자체 Trino (EKS)
서버
없음. AWS 관리
Coordinator 1 + Worker N을 직접 운영
과금
스캔한 데이터 양 (TB 단위)
노드 시간. 쿼리 수와 무관
동시성
계정·리전 단위 쿼터. 초과 시 요청 거부
Worker 수와 메모리로 결정
쿼리 시간 제한
기본 30분. 최대 240분까지 상향 요청
없음. 필요 시 설정
엔진 설정
워크그룹 수준 일부
카탈로그, 세션 프로퍼티, JVM까지 전부
테이블 속성
Athena가 허용하는 속성만
커넥터가 지원하는 속성 전부
Hive 파티션 덮어쓰기
INSERT INTO는 추가만
카탈로그 설정으로 자동 OVERWRITE
장애 허용
AWS 내부 처리
retry-policy=TASK로 Task 단위 재시도
운영 부담
없음
Helm, 노드풀, IAM, 모니터링 전부

Athena와 Iceberg 조합의 제약

Athena에서 Hive 테이블만 쓸 때는 위 표의 차이가 크게 체감되지 않는다. 문제는 Iceberg 테이블을 Athena로 다루기 시작하면서 드러난다. Athena는 Iceberg의 기능 중 자기가 구현한 부분만 노출하기 때문이다.
테이블 속성 제어 범위가 좁다. Athena는 Iceberg 테이블의 속성으로 미리 정의된 키만 허용한다.
구분
허용되는 키
파일
format, write_compression, write_target_data_file_size_bytes
컴팩션
optimize_rewrite_data_file_threshold, optimize_rewrite_delete_file_threshold, optimize_rewrite_min_data_file_size_bytes, optimize_rewrite_max_data_file_size_bytes
스냅샷 정리
vacuum_min_snapshots_to_keep, vacuum_max_snapshot_age_seconds, vacuum_max_metadata_files_to_keep
경로
write_data_path_enabled
압축 코덱과 사후 정리 옵션은 있지만 읽을 파일 수 자체를 줄이는 속성이 없다. Athena에는 없는 조회 지연에 영향을 주는 속성 두개를 대표적으로 살펴보자.
•
sorted_by : 파일을 쓸 때 지정한 컬럼 기준으로 행을 정렬
◦
Parquet는 row group마다 그 안에 담긴 컬럼의 min/max 통계를 보유
◦
정렬되어 있으면 각 row group의 min/max 폭이 좁아짐 (예: 1~1000 → 751~1000)
◦
user_id = '123456' 같은 필터 → 범위에 안 걸리는 row group은 읽지 않고 통째로 스킵
◦
정렬이 없으면 같은 값이 전 파일에 흩어져 min/max가 전 구간으로 넓어짐 → 통계가 있어도 걸러지는 것이 없음
•
parquet_bloom_filter_columns : min/max로 못 거르는 고카디널리티 컬럼용
◦
블룸 필터: 값의 존재 여부를 비트 배열로 압축해 담는 확률적 자료구조
◦
"없음"은 확실하고 "있음"은 틀릴 수 있는 비대칭 구조로 스킵 판정에 적합
◦
지정한 컬럼의 값 목록을 row group마다 요약해서 같이 저장
◦
user_id = '123456' 같은 조회에서 데이터를 읽기 전에 요약본부터 확인 → 없으면 그 row group은 건너뜀
◦
=나 IN 같은 등치조건 조회에만 유효. 범위 조회에는 사용불가
둘 다 파티션으로는 못 거르는 구간을 담당한다. user_id를 파티션 키로 잡으면 파티션이 수백만 개가 되므로 실제로는 날짜로 파티션을 나누고, 그러면 특정 사용자를 찾는 조회가 날짜 파티션을 통째로 읽는 비효율이 발생한다.
쓰기 제어가 제한적이다. 목표 파일 크기는 테이블 속성으로 지정할 수 있지만 Writer 수나 스케일링은 건드릴 수 없다. 작은 파일이 쌓이는 것을 쓰기 시점에 막을 수단이 부족해 사후 OPTIMIZE에 의존하게 된다.

Trino를 따로 올린 이유

1.
dbt 멱등성
기존 DAG는 dbt 증분 모델을 incremental_strategy='append'로 돌리고 그 앞에 S3 prefix 삭제 태스크를 따로 두는 구조였다. 삭제는 성공하고 dbt가 실패하면 파티션이 빈 채로 남는 문제가 있었다.
Trino Hive 커넥터에는 이 동작을 바꾸는 설정이 있다.
hive.non-managed-table-writes-enabled=true hive.insert-existing-partitions-behavior=OVERWRITE
Plain Text
복사
이 설정이 켜진 카탈로그에서는 dbt가 생성한 INSERT INTO가 파티션이 이미 존재할 때 기존 파일을 지우고 새로 쓴다. dbt-athena에도 겹치는 파티션을 지우고 넣는 insert_overwrite 전략이 있지만 그쪽은 어댑터가 S3 삭제를 대신 해주는 것이라 삭제와 쓰기가 여전히 두 단계로 나뉜다. Trino는 커넥터가 이를 한 동작으로 처리하므로 중간에 실패해 파티션이 비는 구간이 없다.
엔진
append 재실행 시
대안
Trino
파티션 자동 덮어쓰기
없음. 카탈로그 설정으로 해결
Athena
중복 누적
어댑터나 DAG에서 S3 삭제를 선행
Spark
중복 누적
insert_overwrite 전략으로 모델 수정
2.
Iceberg 테이블 제어
위에서 본 Athena의 Iceberg 제약이 그대로 도입 이유가 된다. Trino Iceberg 커넥터에서는 테이블 생성 시 파티션 transform, 정렬 순서, 블룸 필터 컬럼을 지정할 수 있고 세션 프로퍼티로 쓰기 파일 크기를 조절할 수 있다.
CREATE TABLE glue_iceberg.silver.example ( ... ) WITH ( partitioning = ARRAY['day(event_time)'], sorted_by = ARRAY['user_id'], parquet_bloom_filter_columns = ARRAY['user_id'] )
SQL
복사
Athena는 이렇게 만들어진 테이블을 읽는 데는 문제가 없다. 만드는 쪽과 튜닝하는 쪽만 Trino가 맡는다.
3.
비용 구조
Athena는 스캔량 과금이다. 사람이 가끔 치는 ad-hoc 쿼리에는 유리하지만 매시간 원천 테이블을 읽어 집계하는 dbt 모델 수십 개가 매일 도는 배치에서는 스캔량이 곧 비용이다. 모델을 추가할 때마다 비용이 선형으로 늘어난다.
Trino는 노드 시간 과금이다. Worker 2대가 떠 있는 동안 쿼리를 열 번 치든 천 번 치든 같은 비용이다. 배치가 몰리는 시간에만 HPA로 Worker를 늘리고 Worker는 스팟으로 돌린다.
# values.yaml worker: autoscaling: minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 70 nodeSelector: karpenter.sh/nodepool: trino-worker-spot-nodepool
YAML
복사
4.
동시성과 시간 제한
동시에 몇 개를 돌릴지, 하나를 얼마나 오래 돌릴지(기본 30분)를 Athena에서는 정할 수 없다. 동시 실행 쿼리 수는 계정·리전 단위 쿼터로 정해지고 쿼터는 계정 내 모든 워크그룹이 공유한다. 워크그룹을 나눠도 한도를 나눠 쓰는 것이지 늘리는 것이 아니다. 한도를 넘긴 요청은 거부되고 한도 이내여도 서비스 전체 부하에 따라 큐에서 대기할 수 있다.
Trino에서는 자원을 직접 배분할 수 있다.
•
클러스터 크기 — 배치가 몰리는 시간대에 Worker를 늘려 대응
•
쿼리별 메모리 — query.max-memory, query.max-memory-per-node로 쿼리 하나가 클러스터를 독점하지 못하게 제한
•
리소스 그룹 — 큐별로 동시 실행 수와 메모리 비중을 나눌 수 있음. 배치 때문에 ad-hoc 쿼리가 밀리는 상황을 막는 용도
•
시간 제한 — 기본적으로 없음. 필요하면 query.max-execution-time으로 직접 설정
5.
세션 프로퍼티
Trino는 쿼리 단위로 엔진 동작을 바꿀 수 있는 세션 프로퍼티를 제공한다. dbt 타겟에 걸어둔 것은 다음과 같다.
session_properties: query_max_stage_count: 300 task_max_writer_count: 1 scale_writers: false glue_table.target_max_file_size: 256MB
YAML
복사
Trino는 쓰기 속도를 높이려고 Writer를 자동으로 늘리는데 그러면 Writer 하나당 파일 하나씩 떨어져 파티션마다 조각난 파일이 생긴다. scale_writers를 끄고 Writer를 하나로 고정한 뒤 목표 파일 크기를 256MB로 잡으면 쓰는 속도는 조금 느려지지만 파일이 처음부터 적정 크기로 떨어진다. Athena라면 작은 파일이 쌓이는 것을 지켜보다가 사후에 OPTIMIZE를 돌려야 하는데 Trino에서는 쓰는 시점에 미리 막는 셈이다.
6.
스팟 Worker와 장애 허용 실행
Worker를 스팟으로 돌리면 Worker는 언제든 회수된다. 기본 Trino는 Worker 하나가 사라지면 그 Worker의 Task를 포함한 쿼리 전체가 실패한다. 장애 허용 실행(fault-tolerant execution)을 켜면 Stage 간 중간 데이터를 S3에 두고 실패한 Task만 다시 실행한다.

정리

Athena와 Trino는 같은 계보의 엔진이지만 운영 모델이 다르다. Athena는 운영 부담 없이 스캔량만큼 내는 대신 엔진과 테이블 속성을 제어할 수 없고, Iceberg를 다룰 때 그 제약이 두드러진다. dbt 배치의 멱등성, Iceberg 테이블 속성 제어, 배치 워크로드의 비용 구조, 동시성과 시간 제한, 세션 프로퍼티, 스팟 Worker 운영이 자체 Trino를 올린 이유다.
둘은 배타적인 선택이 아니다. 테이블을 만들고 배치를 돌리는 쪽은 Trino가 맡고, 만들어진 테이블을 조회하는 쪽은 Athena를 그대로 쓴다.
다음 글에서는 Trino 아키텍처에 대해 더 자세히 다뤄보겠다.