Cloudflare Workers + MySQL (Hyperdrive) 연결 가이드: 'Code generation from strings disallowed' 에러 해결기

청록비
7 read수정하기

서론: 엣지(Edge) 환경의 축복, 그리고 데이터베이스 연결이라는 악몽


최근 웹 개발 생태계에서 Cloudflare Workers로 대표되는 엣지 컴퓨팅(Edge Computing) 기술은 선택이 아닌 필수가 되어가고 있습니다.

콜드 스타트(Cold Start)가 거의 없는 빠른 실행 속도, 전 세계 어디서나 사용자와 가장 가까운 노드에서 코드가 실행된다는 점은 서버리스 아키텍처의 혁명과도 같습니다.

저 역시 Hono 프레임워크와 결합하여 초고속 API 서버를 구축하는 과정에서 엣지 환경의 매력에 푹 빠졌습니다.

 

하지만 이 눈부신 엣지 환경에 입문한 개발자들이 가장 먼저, 그리고 가장 뼈아프게 좌절을 겪는 마의 구간이 있습니다.

바로 **'전통적인 관계형 데이터베이스(RDBMS)와의 연결'**입니다. Node.js 환경에서는 너무나도 당연하고 쉽게 사용하던 mysql2 라이브러리가

Cloudflare Workers 환경에 올라가는 순간, 수많은 에러를 뿜어내며 개발자의 멘탈을 흔들어 놓습니다.

 

그중에서도 저를 며칠 동안 밤잠 설치게 만들었던 가장 악명 높은 에러 메시지가 바로 이것입니다.

 

Error: Code generation from strings disallowed for this context
 

본 포스팅에서는 Hono 프레임워크, Drizzle ORM, 그리고 MySQL을 Cloudflare Workers 환경에서 구축하려다 마주한

이 끔찍한 에러의 원인을 런타임 아키텍처 관점에서 철저히 분석해 봅니다. 또한, Cloudflare의 데이터베이스 프록시 서비스인 'Hyperdrive'를 활용하여

로컬 개발 환경과 실서버 환경 모두에서 완벽하게 동작하는 우아한 해결책을 공유하고자 합니다.

 

수많은 삽질과 테스트 끝에 얻어낸 실전 노하우이니, 저와 같은 환경을 구축 중인 분들이라면 이 글을 통해 귀중한 디버깅 시간을 크게 절약하실 수 있을 것입니다.

 


본론 1: 엣지 환경의 구원투수, Hyperdrive 도입과 초기 설정

Cloudflare Workers와 같은 Serverless/Edge 환경에서 외부 RDBMS(MySQL, PostgreSQL 등)에 직접 연결을 시도하면 두 가지 큰 문제에 직면하게 됩니다.

첫째는 물리적 거리로 인한 네트워크 지연(Latency)이고, 둘째는 수많은 엣지 노드가 동시에 DB에 접속하면서 발생하는 커넥션 풀(Connection Pool) 고갈 문제입니다.

이를 해결하기 위해 Cloudflare는 전역적인 커넥션 풀링과 쿼리 캐싱을 지원하는 Hyperdrive라는 훌륭한 서비스를 제공합니다. Hyperdrive를 사용하기 위한 첫걸음은

프로젝트의 wrangler.toml 파일에 바인딩 설정을 구성하는 것입니다.

 

이때 로컬 환경(wrangler dev)에서도 원격 DB에 직접 붙어서 원활한 테스트를 진행해야 하므로, localConnectionString 설정이 매우 중요합니다.

 

올바른 wrangler.toml 설정 예시


  name = "my-edge-app"
 main = "src/index.js"
 compatibility_date = "2024-03-01"
 
 # Node.js 호환성 모듈 활성화 (Workers에서 TCP 소켓 통신을 위해 필수)
 compatibility_flags = [ "nodejs_compat" ]
 
 [[hyperdrive]]
 binding = "HH_DB"
 id = "본인의_Hyperdrive_ID"
 # 로컬 개발 시 연결할 원격(또는 로컬) DB의 직접적인 주소
 localConnectionString = "mysql://username:password@61.253.xxx.xxx:3306/db_name"

위와 같이 설정하면, 애플리케이션 내에서 c.env.HH_DB.connectionString을 호출할 때 로컬 환경에서는 localConnectionString 값을,

배포된 Workers 환경에서는 Cloudflare가 제공하는 최적화된 프록시 연결 문자열을 자동으로 반환해 줍니다.

 

본론 2: 삽질의 시작과 'Code generation...' 에러의 진짜 정체

Wrangler 설정을 마치고, Drizzle ORM과 mysql2 라이브러리를 사용해 데이터베이스 커넥션 풀을 생성하는 코드를 작성했습니다.

네트워크 핑 테스트(Telnet)도 정상이었고, 논리적으로 완벽한 구조라고 확신했습니다.

하지만 wrangler dev를 통해 실행하는 순간, 여지없이 애플리케이션이 크래시되며 다음 에러가 발생했습니다.

 

Code generation from strings disallowed for this context

연결 자체(Network Connection)는 성공했지만, 데이터베이스에 쿼리를 던지고 결과를 받아오는 과정에서 런타임이 코드를 강제로 종료시킨 것입니다.

대체 왜 이런 일이 발생하는 것일까요? 원인은 Cloudflare Workers의 런타임 환경과 mysql2 라이브러리의 내부 파싱 최적화 로직 간의 치명적인 충돌에 있었습니다.

 

1) Cloudflare Workers의 V8 Isolates 보안 정책

전통적인 Node.js 런타임과 달리, Cloudflare Workers는 Chrome 브라우저의 자바스크립트 엔진인 V8 Isolates 환경에서 실행됩니다.

이 엣지 환경은 보안과 격리를 최우선으로 하기 때문에, 문자열을 동적으로 자바스크립트 코드로 변환하여 실행하는 eval() 함수나 new Function() 생성자의 사용을 엄격하게 금지하고 있습니다.

(출처: Cloudflare Workers Runtime Architecture Official Docs)

 

2) mysql2 라이브러리의 파서(Parser) 메커니즘

반면, Node.js 생태계에서 가장 널리 쓰이는 데이터베이스 드라이버인 mysql2는 극강의 성능을 끌어내기 위해 내부적으로

동적 코드 생성(Dynamic Code Generation) 기법을 사용합니다. 데이터베이스로부터 텍스트 형태의 쿼리 결과(Row)를 전달받으면,

이를 가장 빠르게 자바스크립트 객체로 매핑하기 위해 내부적으로 new Function()을 호출하여 맞춤형 파서 코드를 실시간으로 생성해 버립니다. 바로 이 지점입니다.

로컬 Node.js에서는 완벽했던 최적화 기법이, V8 Isolates 환경에 올라가는 순간 **"보안 정책 위반"**으로 간주되어 무참히 차단당했던 것입니다.

 

본론 3: 단 한 줄의 마법, 완벽한 해결책 적용하기

이 문제를 우회하기 위해 로컬 환경과 Workers 환경을 억지로 분리(if (isWorker))하거나, Drizzle의 HTTP 프록시 드라이버를 억지로 끼워 맞추는 등의 수많은 시행착오를 겪었습니다.

하지만 해답은 생각보다 훨씬 가까운 곳에 있었고, 매우 단순했습니다.

Cloudflare 공식 문서의 Hyperdrive 가이드와 mysql2 레포지토리의 이슈 트래커를 교차 검증한 결과, 이 환경 충돌을 해결하기 위해 라이브러리 차원에서 지원하는 마법의 옵션을 찾아냈습니다.

바로 disableEval 속성입니다.

이 옵션을 적용하기 위해서는 다음의 간단한 절차를 거치면 됩니다.

 

Step 1: mysql2 라이브러리를 최신 버전으로 강제 업데이트

이 우회 기능은 과거 버전에서는 완벽히 지원되지 않았습니다. 반드시 터미널을 열고 프로젝트의 패키지를 최신 상태로 정비해야 합니다.


  npm install mysql2@latest

Step 2: 커넥션 코드에 disableEval: true 추가

데이터베이스 연결을 담당하는 핵심 파일(db_conn.js)에서 mysql.createPool()을 호출할 때 설정 객체에 해당 옵션을 추가합니다.

이 한 줄은 mysql2 엔진에게 **"동적 함수 생성을 통한 속도 최적화를 포기하는 대신, 순수 자바스크립트 기반의 안전한 파싱을 수행하여 V8 보안 정책을 우회하라"**고 지시하는 강력한 명령어입니다.


  // src/functions/db_conn.js
 import { drizzle } from "drizzle-orm/mysql2";
 import mysql from "mysql2/promise";
 
 // 싱글톤 패턴을 위한 커넥션 인스턴스 전역 변수
 let db = null; 
 
 export const getConnection = async (c) => {
     // 1. 이미 연결된 인스턴스가 있다면 재사용 (Edge 환경 성능 최적화)
     if (db) return db;
 
     // 2. Hyperdrive 연결 문자열 가져오기 (환경에 따라 자동 분기됨)
     const connectionString = c.env.HH_DB.connectionString;
 
     // 3. 커넥션 풀 생성
     const pool = mysql.createPool({
         uri: connectionString,
         disableEval: true, // ???? [핵심] Worker 환경의 Code Generation 에러를 원천 차단하는 마법의 옵션
         waitForConnections: true,
         connectionLimit: 2, // 엣지 환경의 동시 실행 특성을 고려해 리밋을 얕게 유지
         maxIdle: 1,
     });
 
     // 4. Drizzle ORM 인스턴스 초기화
     db = drizzle(pool);
 
     return db;
 };

이 옵션을 부여하고 애플리케이션을 다시 실행해 보았습니다. 결과는 대성공이었습니다.

그토록 저를 괴롭히던 Code generation... 에러는 자취를 감추었고, 로컬과 Workers 배포 환경 모두에서 단일 로직으로 Drizzle ORM의 쿼리들이 물 흐르듯 실행되는 것을 확인할 수 있었습니다.

 

결론: 엣지 환경 DB 연결의 황금 레시피 요약

새로운 기술 스택(Cloudflare Workers + Hono)을 도입하면서 겪은 이 일련의 트러블슈팅 과정은,

개발 생태계의 패러다임이 바뀔 때 런타임 환경에 대한 깊은 이해가 얼마나 중요한지 깨닫게 해 준 소중한 경험이었습니다.

 

수많은 가설과 실험이 오갔지만, 결국 이 길고 길었던 삽질의 가장 핵심적인 원인과 결론은 disableEval: true 옵션의 부재 하나였습니다.

Cloudflare의 V8 Isolates 환경이 mysql2의 내부 eval() 동작을 보안상의 이유로 차단했고, 이를 비활성화하는 옵션을 켜줌으로써 모든 퍼즐이 맞춰진 것입니다.

 

앞서 장황하게 설명한 설정들을 모두 걷어내고, 현재 2026년 기준 Cloudflare Workers 환경에서 MySQL 서버와 Drizzle ORM을 연결하기 위해 필요한

최종적이고 필수적인 라이브러리 세팅은 딱 두 가지로 요약됩니다.

 

  1. drizzle-orm : 타입 안정성(Type-Safety)과 가벼움을 자랑하며 엣지 환경에 가장 적합한 ORM.
  2. mysql2@latest : 반드시 최신 버전을 설치하여 disableEval 옵션을 온전히 지원받을 것. (불필요한 프록시 드라이버나 복잡한 분기 로직은 모두 삭제하셔도 무방합니다.)
  3.  

엣지 컴퓨팅 기반의 서버리스 애플리케이션을 준비 중이신가요? DB 연결에서 알 수 없는 에러를 뿜어내며 좌절하고 계셨다면,

지금 당장 여러분의 package.json 버전 명세서를 확인하시고 커넥션 풀에 disableEval: true를 살포시 추가해 보시길 권장합니다.

이 글이 누군가의 퇴근 시간을 극적으로 앞당겨주는 기분 좋은 해결책이 되었기를 바랍니다.


앞으로도 직접 몸으로 부딪히며 얻어낸 생생한 트러블슈팅 기록들로 찾아오겠습니다.

감사합니다!

#Cloud#Infrastructure#Serverless#Tech2024