Athena에서 느린 쿼리, 리소스 초과 오류, 불필요한 비용
AWS 공식 문서를 바탕으로 Athena 쿼리를 더 빠르고 효율적으로 만드는 핵심 기법을 정리해보겠습니다.
색깔별 구분
- Athena + RDB 범용적으로 적용 가능: 초록색
- 부분적으로 RDB에도 적용 가능: 빨간색
- Athena 전용(RDB는 해당 없음): 회색
1. 조인 순서: 큰 테이블을 왼쪽에
Athena는 분산 해시 조인을 실행할 때, 오른쪽 테이블을 빌드측으로 사용합니다.
이 해시 테이블이 메모리를 초과하면 쿼리가 실패합니다.
항상 작은 테이블을 JOIN 오른쪽에 배치합니다.
해시 조인이란?
두 테이블을 조인할 때, 가장 직관적인 방법은 모든 행을 일일이 비교하는 것입니다.
orders 100 만행 x customers 10만행 = 1,000억번 비교 -> 당연히 너무 느립니다.
해시 조인이란 이 문제를 해결하는 방식인데, 사전(dictionary)을 만드는 것과 같습니다.
작은 테이블(customers)로 먼저 '해시 테이블'이라는 조회표를 메모리에 만들어 두고, 큰 테이블(orders)을 한 줄씩 읽으면서 조회 표에서 찾습니다. 전화번호부를 처음부터 끝까지 찾는 대신, 이름 색인으로 바로 찾는 것과 같습니다.
그래서 분산 해시 조인이란?
Athena는 데이터를 혼자 처리하는 게 아니라, 여러 워커 노드가 병렬로 처리합니다.
그래서 해시 테이블도 여러 노드에서 쪼개서 각자 만들고, 큰 테이블도 같은 규칙으로 쪼개서 각 노드가 자기 담당 조각만 조인합니다.
그래서 왜 '작은 테이블을 오른쪽에'가 중요할까?
빌드 측 (작은 테이블)의 해시 테이블은 각 노드의 메모리에 올라가야 합니다.
만약 빌드 측이 너무 크면 메모리가 부족해서 쿼리가 실패합니다.
Athena는 자동으로 작은 테이블을 빌드 측으로 쓰려고 하지만, 작성자가 직접 써줘야 할 떄도 있습니다.
TIP) 빌드 측 크기를 메모리에 맞출 수 없다면, 빌드 테이블의 하위 세트를 나눠 여러번 조인하는 방식을 고려해볼 수 있어요.
2. 윈도우 함수 대신 집계 함수 사용
윈도우 함수는 모든 레코드를 유지하므로, `Query exhausted resources at this scale factor` 오류의 주범이 됩니다.
row_number, rank대신 max_by, min_by, arbitrary같은 집계 함수를 활용하면 메모리 사용량을 크게 줄일 수 있습니다.
집계함수 vs 윈도우 함수
- 집계함수 (GROUP BY와 함께)
- 행을 합쳐서 줄임, 메모리 사용 적음
- SUM, COUNT, MAX, max_by등이 해당
- 그룹당 결과가 딱 1행 나옴.
윈도우 함수 (OVER와 함께)
- 행을 유지하며 계산, 전체를 메모리에 보관
- ROW_NUMBER(), RANK(), LAG()등이 해당
- 5행이 들어오면 5행이 그대로 나오되, 순번같은 컬럼이 하나 추가됨
arbitrary()는 GROUP BY를 쓸 때 생기는 SQL 문법 제약을 피하기 위한 함수입니다.
SELECT에 쓴 컬럼은 반드시 GROUP BY에 있거나 집계 함수로 감싸야 하는데, 특정 집계가 필요하지 않다면 arbitrary()로 묶어서 에러를 피할 수 있습니다. 메모리도 아끼고 오류도 없앨 수 있습니다.
SELECT
user_id,
MAX(order_date) as last_order,
ARBITRARY(address) as any_address -- 사용자 주소가 여러 개여도 하나만 가져옴
FROM orders
GROUP BY user_id;
3. GROUP BY 절 최적화
GROUP BY 절에는 꼭 필요한 열만 포함해야 합니다.
숫자는 문자열보다 메모리를 덜 사용합니다.
그래서 카테고리 이름 대신, 카테고리 ID로 그룹화하는 것이 도움이 됩니다.
쿼리 오류를 피하기 위해 GROUP BY에 추가하는 열은 arbitrary()함수로 대체할 수 있습니다.
4. 상위 N개 쿼리: ORDER BY + LIMIT
Athena는 ORDER BY와 LIMIT이 함께 사용될 때 전용 최적화 작업을 수행합니다.
따라서 최근 N개, 또는 가장 빈번한 N개 값을 찾을 때, row_number() 윈도우 함수 대신 이 방법이 권장됩니다.
5. 필요한 열만 SELECT
열 기반 형식 (parquet, ORC)을 사용할 때, SELECT * 대신 필요한 열만 지정하면 s3에서 읽는 데이터 양도 함께 줄어듭니다.
결과 열 수가 너무 많으면 ` GENERIC_INTERNAL_ERROR: io.airlift.bytecode.CompilationException ` 오류가 발생할 수 있습니다.
TIP) Athena는 스캔한 데이터 용량만큼 과금됩니다. 열 수를 줄이면 쿼리 속도 뿐만 아니라, 비용도 함께 절감됩니다.
6. 근사 집계 함수 사용
정확한 값이 필요하지 않은 분석이라면 근사 함수를 사용하여 메모리와 실행 시간을 크게 줄일 수 있습니다.
COUNT(DISTICT)대신 approx_distinct
7. LIKE 대신 regexp_like, 검색 범위 고정
`LIKE '%substring%'`처럼 앞뒤 와일드카드를 모두 사용하면 전체 문자열을 스캔합니다.
접두사 검색이라면 `'substring%'`으로 검색을 고정하거나,` regexp_like(col, '^substring')`을 사용하세요.
regex_like가 like보다 유리한 경우
- 여러 패턴을` | `(or) 로 한번에 검사
- like를 세 번 따로 쓰는 것보다 regex_like가 더 빠름
- 정규 표현식 엔진이 더 빠르게 처리한다.
- like 5개짜리 쿼리를 regexp_like 하나로 바꾸면 17% 빨라진다.
8. UNION ALL vs UNION
UNION은 중복 제거를 위해 모든 레코드를 처리하기 때문에, 메모리와 컴퓨팅 자원을 많이 소모합니다.
중복 제거가 꼭 필요한 경우가 아니라면 반드시 UNION ALL을 사용하세요.
9. 대용량 결과는 UNLOAD로 내보내기
일반 쿼리 결과는 압축되지 않은 단일 csv로 저장되지만, `UNLOAD`는 워커 노드에서 병렬로 직접 쓰기 때문에 훨씬 빠릅니다.
parquet, json등 압축 방식으로도 저장할 수 있습니다.
TIP) 결과가 수만행을 초과하거나, 후속 분석을 위해 압축/컬럼형 포맷이 필요할 때 UNLOAD를 선택하세요.
10. 자주 쓰는 집계는 CTAS로 구체화
여러 쿼리가 동일한 조인과 집계를 공유한다면, `CREATE TABLE AS SELECT(CTAS)`또는 Glue ETL로 결과를 새 테이블로 사전 계산해둡니다. 대시보드처럼 동일 로직을 반복 실행하는 경우에 특히 효과적입니다. 단, 새 테이블을 주기적으로 최신 상태로 갱신해야 합니다.
CTAS = Create Table As Select
- CTAS로 공통 부분을 먼저 저장하고, 이후 위젯들은 가벼운 쿼리만 날림
- 조인 결과를 새 테이블로 저장 -> 이후 쿼리는 이 테이블만 읽음
- 즉, 자주 쓰는 복잡한 쿼리를 미리 계산해서 테이블로 저장해두는 것.
11. 쿼리 결과 재사용 설정
짧은 시간 안에 동일한 쿼리가 여러번 실행될 때 (ex. 여러 사용자가 같은 대시보드 오픈), Athena의 쿼리 결과 재사용 기능을 활성화하면 이전 결과를 반환합니다. TTL을 지정해 refresh를 제어할 수 있습니다.
12. EXPLAIN으로 실행 계획 사전 분석
복잡한 조인 조건은 모든 레코드를 교차 비교시킬 수 있어서, 실행 시간이 레코드 수의 제곱에 비례해 증가합니다.
쿼리를 실행하기 전에 EXPLAIN문으로 실행 계획을 확인하고 병목을 사전에 파악하면 좋습니다.
쿼리 최적화 - Amazon Athena
이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.
docs.aws.amazon.com
'인프라 > AWS' 카테고리의 다른 글
| 2주안에 AWS SAA + DVA 뽀개기 (0) | 2026.03.27 |
|---|---|
| [RDBMS와 NoSQL의 추구미] 돌고 도는 트레이드 오프 (0) | 2026.03.24 |
| AWS RDS(MySQL) Migration하기 (📦 새 계정으로 이사가요) (0) | 2025.11.11 |
| Auto Scaling with NLB (Network Load Balancer) (0) | 2025.06.03 |
