본문 바로가기
Backend/Troubleshooting

[Trino] MySQL은 연결되는데 Trino Worker는 연결되지 않았던 이유

미지시료 2026. 8. 30.
반응형

데이터베이스 연결 문제로 보였던 Trino 장애에서 타임존이라는 가설을 거쳐, 실제 원인이 서로 다른 VPC 사이의 Worker 통신이라는 것을 확인한 과정을 정리했다.





시작하며

여러 MySQL을 Trino Catalog로 연결하는 과정에서 일부 데이터 소스의 조회가 실패했다. 대상 MySQL에는 CLI로 바로 접속할 수 있었고 계정과 비밀번호도 정상이었다.

mysql -h DB_HOST -u READ_ONLY_USER -p

처음에는 JDBC Driver와 MySQL의 타임존 협상을 의심했다. JVM 타임존과 Trino 세션 타임존을 확인하고 설정도 변경했지만 장애는 해결되지 않았다.

결국 문제는 MySQL 연결보다 앞선 곳에 있었다. 다른 VPC에 설치한 Trino Worker가 자신의 사설 IP로 등록됐고, Coordinator가 그 주소에 접근하지 못해 클러스터 통신이 끊기고 있었다.

이 글에서는 타임존 가설이 왜 그럴듯했는지, 어떤 로그를 통해 실제 실패 구간을 찾았는지, Worker와 Catalog의 배치를 어떻게 다시 설계했는지를 살펴본다.





처음 확인한 증상

장애 상황은 다음처럼 보였다.

  • 기존 데이터 소스의 일부 쿼리는 정상적으로 실행됐다.
  • 새 데이터 소스의 MySQL에는 CLI로 접속할 수 있었다.
  • Trino에 Catalog 설정 파일도 배포했다.
  • 새 서버에 Trino Worker를 실행했다.
  • 하지만 Worker가 안정적으로 활성 상태를 유지하지 못했다.
  • Coordinator 로그에는 특정 노드와 통신할 수 없다는 메시지가 반복됐다.

MySQL CLI 접속이 성공했기 때문에 네트워크가 모두 정상이라고 생각하기 쉽다. 하지만 이 판단에는 서로 다른 두 연결 경로가 섞여 있었다.

경로 A: 운영 서버 → MySQL
경로 B: Trino Coordinator ⇄ Trino Worker

경로 A가 성공해도 경로 B는 실패할 수 있다.





실제 조사에서는 가설을 하나씩 지웠다

처음에는 “MySQL에는 접속되는데 Trino에서만 안 된다”는 현상만 보고 JDBC 설정을 먼저 확인했다. 그러나 조사 과정에서 각 확인 결과가 보장하는 범위를 적어 보니, MySQL과 Worker 통신을 하나의 연결 문제로 묶고 있었다는 점이 드러났다.

확인한 내용 결과 이 결과로 알 수 있었던 것 아직 알 수 없었던 것
운영 서버에서 MySQL CLI 실행 SELECT 1 성공 해당 서버에서 MySQL 주소·계정·3306 포트 사용 가능 Coordinator와 Worker 사이의 8080 통신
JVM과 Trino 세션 타임존 확인 설정을 바꿔도 RemoteNodeState 오류 반복 현재 Worker 장애의 직접 원인은 타임존이 아님 JDBC를 사용하는 특정 Catalog의 별도 연결 문제
Coordinator 로그 확인 Worker 사설 IP의 /v1/info 요청 실패 MySQL에 도달하기 전에 노드 상태 확인 단계에서 실패 사설 IP가 왜 도달 불가능한지
Worker 주소와 VPC 대조 Coordinator와 Worker가 서로 다른 VPC 별도 라우팅이 없으면 등록된 사설 IP로 접근 불가 Worker를 그 데이터 서버에 반드시 둬야 하는지
Worker와 Catalog 역할 재검토 Worker가 모든 Catalog를 조회할 수 있음 데이터 서버마다 Worker가 필요하다는 전제가 잘못됨 최종 배치 후 모든 데이터 소스에 접근 가능한지

가장 큰 실수는 CLI 성공의 범위를 넓게 해석한 것이었다. CLI를 실행한 서버에서 MySQL까지 연결됐다는 사실은 확인했지만, Coordinator가 Worker의 등록 주소에 접근할 수 있다는 사실은 확인하지 않았다.

잘못 배치한 Worker는 중지하고 자동 시작도 해제했다.

sudo systemctl stop trino
sudo systemctl disable trino

그 데이터 서버는 Trino 노드로 합류시키지 않고 Catalog의 원격 MySQL로만 등록했다. 이후에는 다음 두 조건을 따로 검증했다.

1. Coordinator에서 정상 Worker가 active로 보이는가
2. 실행 노드에서 원격 MySQL의 작은 조회가 성공하는가

이렇게 확인 대상을 나누고 나서야 클러스터 통신과 데이터 소스 연결을 독립적으로 판단할 수 있었다.





타임존을 먼저 의심했던 이유

MySQL CLI는 성공하지만 JDBC 연결만 실패하는 문제에는 여러 원인이 있을 수 있다.

  • JDBC URL과 SSL 옵션
  • MySQL Connector 버전
  • 서버와 JVM의 문자 인코딩
  • 서버와 JVM의 타임존
  • Driver가 연결 시 수행하는 초기 메타데이터 조회

특히 여러 MySQL 버전이 함께 운영되는 환경에서는 같은 JDBC 설정이 일부 서버에서만 실패할 수 있다. 그래서 다음 두 설정을 확인했다.

JVM 기본 타임존
-Duser.timezone=...

Trino 쿼리 세션 타임존
X-Trino-Time-Zone: ...

타임존은 충분히 확인할 가치가 있는 가설이었다. 하지만 설정을 변경해도 Worker 통신 오류가 그대로였다. 이때부터 JDBC Connection이 생성되는 단계보다 앞에서 실패하는 것은 아닌지 확인해야 했다.

처음 의심한 경로와 실제 실패한 경로





JVM은 UTC를 유지하는 것이 맞았다

결론부터 말하면 Trino를 실행하는 JVM은 UTC를 유지하는 것이 맞았다.

-Duser.timezone=UTC

Coordinator와 모든 Worker에 같은 값을 적용해야 한다. 특정 노드만 다른 타임존을 사용하면 쿼리가 어느 노드에서 실행되느냐에 따라 시간 계산과 JDBC 동작을 예측하기 어려워진다.

실제로 JVM을 Asia/Seoul로 두면 MySQL 5.7 Catalog에서 JDBC 연결이 실패할 수 있었다. MySQL CLI는 같은 서버에 바로 접속되지만 Trino에서는 다음 오류가 나타나는 별도의 문제였다.

Could not create connection to database server.
Attempted reconnect 3 times. Giving up.

MySQL 8.0 Catalog는 정상인데 5.7 Catalog만 실패할 수 있어 URL이나 계정 문제처럼 보이기도 했다. 하지만 이 경우의 실패 지점은 Worker의 /v1/info가 아니라 Connector/J가 MySQL과 연결을 만드는 단계다. 그래서 JVM을 UTC로 되돌리는 판단 자체는 맞았지만, 그것만으로 서로 다른 VPC의 Worker가 합류할 수 있는 것은 아니었다.

Asia/Seoul이 필요한 것은 JVM 전체가 아니라 애플리케이션이 실행하는 Trino 쿼리 세션이다. 현재 구성에서는 요청 헤더로 세션 타임존을 지정할 수 있다.

X-Trino-Time-Zone: Asia/Seoul

두 설정의 역할은 다르다.

설정 역할
JVM 기본 타임존 UTC Trino 프로세스와 노드의 공통 기준
Trino 세션 타임존 Asia/Seoul 쿼리의 날짜·시간 함수 해석 기준

따라서 Worker 연결 문제를 해결하기 위해 JVM을 Asia/Seoul로 변경할 이유는 없었다. JVM은 UTC로 되돌리고, 필요한 지역 시간은 세션 단위로 전달하는 것이 설정의 책임도 더 명확하다.

다만 이것이 Worker 장애를 해결한 것은 아니다. 타임존을 올바르게 정리한 뒤에도 노드 간 통신 오류는 계속됐다.





결정적인 단서는 RemoteNodeState 로그였다

Coordinator 로그에서 다음 형태의 오류가 반복됐다.

RemoteNodeState ... Failed communicating
Error fetching node state from http://PRIVATE_IP:8080/v1/info
Node state update request has not returned

이 로그는 MySQL이나 Catalog의 JDBC 연결 오류가 아니다. Coordinator가 Worker의 Trino HTTP endpoint에 접근하지 못했다는 의미다.

오류 주소가 사설 IP라는 점이 중요했다.

10.x.x.x
172.16.x.x ~ 172.31.x.x
192.168.x.x

Worker는 Coordinator의 주소로 Discovery 등록을 시도하고, Coordinator도 등록된 Worker 주소로 상태 확인과 Task 통신을 수행한다. Worker가 다른 VPC의 사설 IP로 등록되면 VPC Peering이나 VPN처럼 사설망을 연결하는 구성과 올바른 Route·Security Group이 없는 한 Coordinator가 그 주소에 접근할 수 없다.

즉 실제 실패 지점은 다음과 같았다.

Coordinator ──X──> Worker private IP

MySQL CLI 성공 여부는 이 경로를 검증하지 않는다.

비슷하게 보이는 조회 실패도 로그 문구에 따라 먼저 볼 계층이 달랐다.

로그 또는 증상 실패 계층 먼저 확인할 항목
RemoteNodeState ... /v1/info ... Failed communicating Coordinator → Worker 등록된 Worker IP, VPC Route, Security Group, 8080 양방향 통신
No nodes available Trino 실행 노드 Active Worker 수, Coordinator의 Task 참여 설정, 노드 상태
Could not create connection ... Attempted reconnect 실행 노드 → MySQL MySQL 버전, JVM 타임존, JDBC URL, 3306 접근 권한
Catalog '...' does not exist Catalog 배포 모든 노드의 Catalog 파일, 파일명, 재시작 여부
조회 결과의 시각만 9시간 차이 쿼리 세션·SQL X-Trino-Time-Zone, 날짜 경계, now() 의존 여부

오류를 이 표처럼 분류하면 Worker 통신 장애에 JDBC URL을 바꾸거나, Catalog 배포 누락에 VPC 설정을 바꾸는 식의 우회 작업을 줄일 수 있다.





Trino 노드 통신은 양방향이다

Worker가 Coordinator에 한 번 등록하면 끝나는 구조로 생각하기 쉽다. 실제로는 클러스터가 동작하는 동안 양방향 통신이 계속 필요하다.

방향 용도
Worker → Coordinator Discovery 등록, 상태 보고
Coordinator → Worker 상태 확인, 실행할 Task 전달
Worker ⇄ Worker 쿼리 실행 중 중간 데이터 교환
실행 노드 → MySQL Catalog를 통한 원본 데이터 조회

따라서 Worker에서 Coordinator로 curl이 성공하는 것만으로는 충분하지 않다. Coordinator도 Worker가 등록한 주소로 접근할 수 있어야 한다.

# Worker에서 Coordinator 확인
curl http://COORDINATOR_HOST:8080/v1/info

# Coordinator에서 Worker 확인
curl http://WORKER_PRIVATE_HOST:8080/v1/info

두 번째 요청이 실패한다면 JDBC URL을 바꾸기 전에 VPC, Route Table, Security Group과 Worker가 광고하는 주소를 확인해야 한다. Worker가 여러 대라면 Worker 사이의 Trino endpoint 접근도 함께 확인한다.





Worker와 Catalog는 일대일 관계가 아니다

문제의 출발점에는 “MySQL 서버마다 Trino Worker 하나가 필요하다”는 전제가 있었다. 하지만 Worker와 Catalog는 서로 다른 개념이다.

구분 Worker Catalog
역할 분산 SQL Task 실행 데이터 소스 연결 정보 정의
MySQL 서버마다 필요한가 아니다 연결할 데이터 소스마다 필요하다
배치·구성 위치 Trino 클러스터 노드 Coordinator와 모든 Worker의 Catalog 설정 경로
네트워크 요구사항 Trino 노드와 상호 통신 실행 노드가 대상 MySQL에 접근 가능해야 함

Coordinator는 실행 계획에 따라 어느 Worker에든 Task를 배정할 수 있다. 따라서 각 Worker는 자신과 같은 서버의 MySQL만 읽는 것이 아니라, 쿼리에 사용될 수 있는 모든 Catalog에 접근할 수 있어야 한다.

Worker A
├── Catalog A → MySQL A
├── Catalog B → MySQL B
└── Catalog C → MySQL C

이 사실을 이해하면 데이터 서버마다 Worker를 설치할 필요가 없다는 결론에 도달한다.

Coordinator가 Task 실행에도 참여하도록 설정했다면 Coordinator 역시 하나의 실행 노드가 된다. 이 경우 Coordinator에도 같은 Catalog 파일이 필요하며, 각 MySQL에 접근할 수 있어야 한다.





최종 Worker 배치

운영 구성을 단순하게 유지하기 위해 Worker는 Coordinator와 사설 네트워크 통신이 가능한 같은 VPC에만 배치했다. 다른 VPC의 데이터 서버에는 Worker를 설치하지 않고 MySQL만 Catalog로 등록했다.

다른 VPC에 Worker를 두는 것이 기술적으로 항상 불가능한 것은 아니다. VPC 사이에 양방향 사설 라우팅을 구성하고 Trino 노드가 사용하는 주소와 포트를 모두 열면 클러스터를 구성할 수 있다. 이번 환경에서는 데이터 조회를 위해 그 수준의 네트워크 구성을 추가하는 대신 Worker와 원격 MySQL의 역할을 분리했다.

같은 VPC의 Worker가 여러 Catalog를 조회하는 최종 구조

이 구조에서는 통신 경로가 다음처럼 분리된다.

Trino 노드 통신
Coordinator ⇄ Worker
Worker ⇄ Worker
→ 같은 VPC의 사설 네트워크 사용

데이터 소스 통신
실행 노드 → 각 MySQL
→ Catalog에 설정된 접근 가능한 주소 사용

다른 VPC의 MySQL을 조회하려면 Worker가 해당 MySQL 주소와 포트에 접근할 수 있어야 한다. 하지만 그 데이터 서버가 Trino Worker로 클러스터에 합류할 필요는 없다.





문제를 다시 진단한다면

같은 상황을 다시 만난다면 다음 순서로 확인하는 것이 빠르다.

1. 실패 계층을 먼저 구분한다

Query API
  → Trino Coordinator
  → Worker
  → JDBC Connector
  → MySQL

오류가 어느 구간에서 발생했는지 확인한다. RemoteNodeState는 Worker 통신, Could not create connection은 JDBC와 MySQL 구간을 우선 의심해야 한다.

2. 활성 노드 목록을 확인한다

SELECT node_id, coordinator, state, http_uri
FROM system.runtime.nodes;

예상한 Worker가 active 상태인지, 어떤 주소로 등록됐는지 확인한다. Coordinator의 Web UI에서 Active Workers 수도 함께 확인할 수 있다.

3. 양방향 네트워크를 확인한다

  • Worker에서 Coordinator의 Trino endpoint에 접근
  • Coordinator에서 Worker의 등록 주소에 접근
  • Worker가 여러 대라면 Worker 사이의 Trino endpoint에 접근
  • Worker에서 필요한 모든 MySQL에 접근
  • Coordinator가 실행에 참여한다면 Coordinator에서도 모든 MySQL에 접근

4. 노드 설정을 비교한다

  • node.environment가 동일한가
  • node.id가 중복되지 않는가
  • Coordinator 주소가 모든 Worker에서 같은가
  • JVM 타임존이 모두 UTC인가
  • Catalog 파일이 모든 Trino 노드에 동일한가

5. Worker가 합류한 뒤 Catalog를 확인한다

SHOW CATALOGS;
SHOW SCHEMAS FROM catalog_a;

Worker가 정상적으로 합류하기 전부터 JDBC와 SQL을 수정하면 원인과 무관한 설정만 늘어날 수 있다.





타임존 가설에서 얻은 교훈

타임존을 의심한 것 자체가 잘못은 아니었다. CLI와 JDBC가 다른 연결 과정을 사용하고, MySQL 버전과 Driver 조합에 따라 차이가 생길 수 있기 때문이다.

문제는 가설보다 실패 계층을 먼저 확인하지 않은 데 있었다.

증상: 데이터 조회 실패

잘못된 접근
→ JDBC 설정부터 변경

더 나은 접근
→ Coordinator 로그 확인
→ Worker 상태 확인
→ 실패 구간 식별
→ 해당 계층의 설정 확인

또한 타임존 설정을 수정하면서 JVM과 쿼리 세션의 역할도 분리할 수 있었다.

  • JVM은 모든 Trino 노드에서 UTC로 통일한다.
  • 지역 시간대가 필요한 쿼리는 세션 헤더로 지정한다.
  • 날짜 범위가 중요하면 애플리케이션이 명시적인 값을 전달한다.

이 원칙은 VPC 문제의 해결책은 아니지만, 조사 과정에서 생길 수 있는 별도의 시간대 문제를 줄여 준다.





재발 방지를 위한 체크리스트

장애가 발생한 직후

  • Coordinator 로그에서 최초 오류 문구와 대상 IP를 기록했는가
  • system.runtime.nodes 또는 /v1/node 결과를 저장했는가
  • Worker → Coordinator와 Coordinator → Worker를 각각 확인했는가
  • CLI를 어느 서버에서 실행했는지 기록했는가
  • 정상 Catalog와 실패 Catalog의 MySQL 버전을 비교했는가
  • 모든 Trino 노드의 JVM 타임존과 Catalog 파일 차이를 확인했는가

설정을 변경하기 전에 이 정보를 남겨 두면 변경 전후를 비교할 수 있다. 여러 설정을 동시에 바꾸면 어떤 변경이 장애를 해결했는지 다시 판단하기 어렵다.

Worker를 추가할 때

  • Coordinator와 같은 VPC이거나 VPC 사이에 양방향 사설 라우팅이 구성되어 있는가
  • Coordinator와 모든 Worker 사이의 Trino 포트가 양방향으로 열려 있는가
  • Worker가 여러 대라면 Worker 사이의 Trino 포트도 열려 있는가
  • Worker가 어떤 사설 주소로 등록되는가
  • node.environment가 같은가
  • node.id가 고유한가
  • JVM 타임존이 UTC인가

Catalog를 추가할 때

  • 모든 Trino 노드에 같은 Catalog 파일을 배포했는가
  • 모든 Worker에서 원본 MySQL에 접근할 수 있는가
  • Coordinator가 실행에 참여한다면 Coordinator에서도 원본 MySQL에 접근할 수 있는가
  • 읽기 전용 계정으로 작은 쿼리를 실행할 수 있는가
  • 애플리케이션의 Catalog 매핑과 이름이 일치하는가
  • 설정 변경 후 모든 노드를 재시작했는가

Worker 추가와 Catalog 추가를 별도 절차로 관리하는 것이 핵심이다.





마치며

MySQL CLI 접속이 성공했지만 Trino 조회가 실패하자 JDBC와 타임존을 먼저 의심했다. 그러나 실제 오류는 JDBC Connection보다 앞선 Coordinator와 Worker 사이에서 발생하고 있었다.

이번 문제에서 얻은 결론은 다음과 같다.

  1. MySQL 연결과 Trino 노드 통신은 별개의 경로다.
  2. 오류 메시지로 실패 계층부터 구분해야 한다.
  3. RemoteNodeState와 사설 IP 통신 실패는 Worker 네트워크를 먼저 확인한다.
  4. Worker는 Coordinator와 통신 가능한 VPC에 배치한다.
  5. 다른 VPC의 MySQL은 Worker 없이 Catalog로만 연결할 수 있다.
  6. Worker와 Catalog는 일대일 관계가 아니다.
  7. JVM 타임존은 모든 노드에서 UTC로 통일한다.
  8. 지역 시간대가 필요한 쿼리는 세션 설정으로 분리한다.

분산 시스템의 연결 실패를 해결할 때는 가장 그럴듯한 설정부터 바꾸기보다, 요청이 지나가는 경로를 그리고 어느 구간까지 성공했는지 확인하는 것이 더 빠르다.

반응형