원문 캡처 · blog.cloudflare.com
원문 캡처 · blog.cloudflare.com

Cloudflare가 Workers용 데이터베이스 연결 풀러 Hyperdrive의 MySQL 지원을 정식 출시했다. 베타 기간에 발견한 캐시 버그를 수정한 뒤 클라이언트 연결 성공률이 99.99%에 도달했다고 밝혔다.

Hyperdrive는 Cloudflare의 전 세계 네트워크에 SQL 연결을 유지해 Workers 요청 사이에서 공유한다. 자주 실행하는 쿼리는 글로벌 CDN 캐시에 저장해 데이터베이스 부하와 지연 시간을 줄일 수도 있다. 자세한 소개는 Hyperdrive 소개 글에서 확인할 수 있다.

베타에서 발견한 문제

베타 기간에는 이모지가 데이터베이스에 저장된 뒤 올바르게 반환되지 않는 문자 인코딩 문제가 있었다. Hyperdrive가 연결 협상에서 utf8을 사용했는데, MySQL에서 이는 유니코드 문자당 최대 3바이트만 지원하는 폐기 예정 별칭 utf8mb3를 가리켰다. 전체 UTF-8을 지원하도록 utf8mb4로 바꾸어 문제를 해결했다. 연결 초기화 명령인 COM_RESET_CONNECTION을 추가한 뒤에도 서버 기본 문자 집합으로 돌아가는 문제가 생겨, 초기화 후 utf8mb4를 다시 설정하도록 수정했다. 관련 내용은 MySQL 문자 집합 문서와 COM_RESET_CONNECTION 문서에 설명돼 있다.

더 드물게는 결과가 있어야 하는 쿼리에서 빈 응답이 반환되는 문제가 발생했다. 발생 빈도는 약 0.001%였으며, 캐싱이 켜진 Hyperdrive 인스턴스에서만 관찰됐다.

캐시와 MySQL 프로토콜의 차이

Hyperdrive 인스턴스는 Worker와 같은 머신에서 로컬 캐시를 처리하는 Client 노드와, 원본 데이터베이스 가까이에 배치돼 연결 풀을 관리하는 Endpoint 노드로 구성된다. 두 노드가 같은 데이터센터에서 실행되면 CDN 캐시를 공유한다.

문제는 두 노드가 MySQL 응답을 서로 다른 방식으로 인코딩할 수 있다는 점이었다. Hyperdrive는 원본 데이터베이스와 통신할 때 자체 기능 협상을 사용하지만, Worker에 응답할 때는 Worker의 MySQL 드라이버가 기대하는 형식으로 결과를 바꾼다. 예를 들어 CLIENT_DEPRECATE_EOF 기능을 협상하지 않은 드라이버에는 결과 집합의 경계를 표시하는 EOF 패킷을 보내야 한다. Node.js용 MySQL 클라이언트 라이브러리 mysql2는 이 기능을 지원하지 않는다고 Cloudflare는 설명했다. 기능 협상과 패킷 형식은 MySQL 프로토콜 문서에서 확인할 수 있다.

기존 캐시 코드는 Worker에 보낸 바이트를 그대로 저장해도 된다고 가정했다. 하지만 Client가 Worker용으로 변환한 응답을 캐시에 넣으면 Endpoint가 기대하는 형식과 달라질 수 있고, 반대로 Endpoint가 저장한 Hyperdrive 형식의 응답을 Client가 변환 없이 Worker에 보내도 문제가 생긴다. 두 노드가 캐시를 공유하고 한쪽이 다른 쪽이 저장한 값을 읽을 때 잘못된 형식의 응답이 전달돼 빈 결과나 멈춤 현상이 나타날 수 있었다.

Cloudflare는 어느 노드가 저장하든 캐시에 Hyperdrive 기능 기준의 정규화된 결과 바이트를 기록하도록 수정했다. 수정 사항을 배포한 뒤 빈 행 문제가 사라졌고, Client 연결 성공률이 99.99%에 도달했다고 밝혔다.

기존 MySQL 호환 데이터베이스에 연결하는 Hyperdrive는 대시보드나 wrangler로 만들 수 있다. PlanetScale 데이터베이스 연결 방법은 Cloudflare 개발자 문서에서 확인할 수 있다.

bash
npx wrangler hyperdrive create HYPERDRIVE_NAME --connection-string="mysql://user:password@db-host.example.com:3306/dbname"