개발 매뉴얼

개발 매뉴얼을 다루는 공간입니다.

DEV_MANUAL

VS Code 설치 및 Cloudflare 배포 매뉴얼 (D1 데이터베이스 생성)

VS Code 설치 및 Cloudflare 배포 매뉴얼 (D1 데이터베이스 생성)

작성일: 2024-03-20목적: Cloudflare D1 데이터베이스 생성, 테이블 구축, Workers 프로젝트 생성 및 초기 배포 과정을 체계적으로 정리하여 유지보수 및 재현 가능성을 확보함.1. 로컬 개발 환경 준비 (PowerShell & Scoop) 1) 터미널 열기: Windows에서 PowerShell (또는 Windows Terminal)을 실행 - Powershell 버전이 낮으면 scoop 실행이 안될 수 있음. Powersheel 최신 버전 설치 - scoop 설치 : 가. Set-ExecutionPolicy RemoteSigned -scope CurrentUser 나. iwr -useb get.scoop.sh | iex 2) Node.js 설치 (Scoop 활용): Scoop이 설치되어 있다면 아래 명령어로 Node.js를 설치 - PowerShell : scoop install nodejs 3) VS Code 설치 (Scoop 활용): 코드 에디터로 가볍고 강력한 VS Code를 설치 - PowerShell :scoop install vscode 4) vscode 실행 후 프로젝트를 만들 폴더로 이동 - 메뉴 > File > Open Folder ( 예: Z:\workspace\nodejs )2. Workers 프로젝트 생성 및 설정 (npm create) 1) 프로젝트 생성 명령어 입력: Ctrl+~ 을 누르면 터미널이 생성되고, 아래 명령어를 입력 - npm create cloudflare@latest [프로젝트명] 2) Which template would you like to use? **Worker only**를 선택 - D1과 AI API를 자유롭게 연결할 수 있는 가장 깔끔한 도화지 3) Which language do you want to use? **JavaScript**를 선택 - 빠른 개발 속도와 수익화를 위해 가장 효율적입니다. 4) AI 가이드 파일 추가: Do you want to add an AGENTS.md file...? **Yes**를 선택 - AI 코딩 도구(나 같은 어시스턴트)가 Cloudflare API를 더 정확히 파악하게 도와줌 5) Do you want to use git for version control? **Yes**를 선택 - 버전 관리 필수 6) 배포 안 함 선택: Do you want to deploy your application? **No**를 선택 - 로컬에서 먼저 확인하기 위해3. 로컬 테스트 및 실서버 배포 (npx wrangler) 1) 터미널 (Ctrl+~) - cd [프로젝트명] 입력 - code . -r 입력 2) VS Code의 EXPLORER에서 src/index.js 파일을 열고, 내용을 수정한 후 저장(Ctrl+S) 3) 터미널을 열고 아래 명령어를 입력 - npx wrangler dev - 브라우저 확인: http://localhost:8787로 접속하여 입력한 문구가 나오는지 확인 (성공!) 4) Cloudflare 로그인 (최초 1회): 로컬 서버를 끄고(Ctrl+C), 터미널에 아래 명령어를 입력하여 동기화 - npx wrangler login 5) 배포! - npx wrangler deploy여기부터 선택사항4. Cloudflare D1 데이터베이스 생성 (웹 대시보드) 1) Cloudflare 로그인: dash.cloudflare.com에 접속하여 로그인 2) D1 메뉴 진입: 왼쪽 사이드바 메뉴에서 [Workers & Pages] -> **[D1]**을 클릭 3) 데이터베이스 생성: 화면 중앙의 [Create database] 버튼을 클릭 4) 이름 설정: Database name에 **DB명** 입력하고 **[Create]**를 클릭 5) 정보 보관: 생성된 D1의 상세 화면에서 Database ID (UUID 형태의 긴 문자열)를 복사하여 안전한 곳에 메모 - 나중에 wrangler.jsonc 설정에 필수5. 테이블 구축 (SQL 실행) 1) 콘솔 진입: 방금 생성한 textray-db 상세 화면에서 상단의 [Console] 탭을 클릭 2) SQL 쿼리 입력: 쿼리 입력창에 아래 SQL 문을 복사하여 붙여넣기 3) 아래 내용 확인SQLCREATE TABLE tb_dream ( id INTEGER PRIMARY KEY AUTOINCREMENT, ... 필요한 컬럼들 ... reg_date DATETIME DEFAULT CURRENT_TIMESTAMP);실행: 오른쪽 하단의 [Execute] 버튼을 클릭합니다. "Success" 메시지가 뜨면 테이블 생성이 완료된 것임

자세히 보기

DEV_MANUAL

VS Code 로 GitHub 연동 및 자동 배포 시스템 구축

VS Code 로 GitHub 연동 및 자동 배포 시스템 구축

작성일: 2026-03-20목적: 로컬 코드를 GitHub에 안전하게 보관하고, 인증 에러 발생 시 우회하여 배포를 완수함.1. 환경 설정 (최초 1회)Git의 커밋 기록을 위한 사용자 식별 정보를 등록한다.1) git config --global user.email "이메일"2) git config --global user.name "이름"2. 로컬 저장소 초기화 및 커밋작업 내역을 로컬 데이터베이스에 기록한다.1) git init (저장소 초기화)2) git add . (변경 사항 스테이징)3) git commit -m "메시지" (기록 확정)3. 원격 저장소 연결 및 표준화GitHub와 연결하고 브랜치 명칭을 글로벌 표준인 main으로 통일한다.1) git remote add origin https://github.com/... <-- GitHub에서 복사해오기2) git branch -M main (기본 브랜치 명칭 변경 : 강력추천 - 추후 충돌 방지)4. 장애 발생 및 대응 (Troubleshooting)터미널에서 git push 시 인증 단계에서 무한 대기(먹통) 현상이 발생할 경우의 대응책이다.원인: Git Credential Manager의 팝업 창이 백그라운드에서 고립되거나 통신 지연 발생.해결: VS Code 내장 GUI 기능을 이용해 인증 세션을 강제 연결한다.VS Code 좌측 [Source Control] 탭 진입.[Publish Branch] 또는 [Sync Changes] 버튼 클릭.상단 알림창에서 [Allow] 선택 후 브라우저 인증 승인.인증 완료 후 VS Code로 복귀하여 동기화 확인.5. 최초 전송 및 운영 루틴설정 완료 후 아래 3단계 명령으로 배포를 완료한다.[중요 : 최초 전송]git push -u origin main[일상 배포]1) git add .2) git commit -m "변경한 내용"3) git push

자세히 보기

DEV_MANUAL

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

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

서론: 엣지(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을 연결하기 위해 필요한최종적이고 필수적인 라이브러리 세팅은 딱 두 가지로 요약됩니다. drizzle-orm : 타입 안정성(Type-Safety)과 가벼움을 자랑하며 엣지 환경에 가장 적합한 ORM.mysql2@latest : 반드시 최신 버전을 설치하여 disableEval 옵션을 온전히 지원받을 것. (불필요한 프록시 드라이버나 복잡한 분기 로직은 모두 삭제하셔도 무방합니다.) 엣지 컴퓨팅 기반의 서버리스 애플리케이션을 준비 중이신가요? DB 연결에서 알 수 없는 에러를 뿜어내며 좌절하고 계셨다면, 지금 당장 여러분의 package.json 버전 명세서를 확인하시고 커넥션 풀에 disableEval: true를 살포시 추가해 보시길 권장합니다.이 글이 누군가의 퇴근 시간을 극적으로 앞당겨주는 기분 좋은 해결책이 되었기를 바랍니다. 앞으로도 직접 몸으로 부딪히며 얻어낸 생생한 트러블슈팅 기록들로 찾아오겠습니다. 감사합니다!

자세히 보기

DEV_MANUAL

Cloudflare-GitHub 자동 배포(CI/CD) 연동 가이드

Cloudflare-GitHub 자동 배포(CI/CD) 연동 가이드

1. 전제 조건 (Prerequisites) 로컬 코드가 GitHub 원격 저장소(main 브랜치)에 최종 푸시된 상태여야 함. Cloudflare 계정에 대상 Workers 프로젝트가 생성되어 있어야 함. 2. Cloudflare 대시보드 설정 1) 프로젝트 진입: Cloudflare 대시보드 -> [Workers & Pages] -> 해당 Worker 선택. 2) 배포 설정 이동: 상단 탭의 [설정] 클릭 3) 저장소 연결: 빌드 섹션에서 [연결] 버튼 클릭. 4) 권한 승인: GitHub 계정 인증 후, 배포할 저장소를 목록에서 선택. 빌드 구성 확인: Production branch: main (기본값) Build command: (공백) 또는 npm run deploy (필요시 자동 인식됨) Root directory: / (기본값) 저장: **[Save and Deploy]**를 눌러 최종 연결을 확정함. 3. 자동 배포 메커니즘 (작동 원리) 1) Push = Deploy: 개발자가 로컬에서 git push를 실행하는 즉시 Cloudflare가 이를 감지함. 2) Build Pipeline: Cloudflare 내부 서버가 코드를 가져와 빌드 후 실서버에 즉시 반영. 3) 중앙 집중 관리: Cloudflare 대시보드 에디터를 거치지 않고 오직 GitHub 코드가 '단일 진실 공급원(SSOT)'이 됨. 4. 장애 대응 및 주의사항 1) 배포 실패 시: 대시보드 [Deployments] 탭에서 상세 로그를 확인하여 빌드 에러 원인 분석. 2) 코드 불일치 주의: 대시보드 웹 에디터에서 직접 수정 금지. 반드시 로컬 VS Code에서 수정 후 push 할 것. 5. 일상 개발 워크플로우 (3단계) 1) 코드 수정: VS Code에서 기능 개발. 2) 로컬 검증: npx wrangler dev로 사전 테스트. 자동 배포: git add . -> git commit -m "내용" -> git push (배포 완료).

자세히 보기

DEV_MANUAL

Cloudflare 의 Hyperdrive 설정 삽질 과정

Cloudflare 의 Hyperdrive 설정 삽질 과정

서론: 서버리스 데이터베이스 연결의 숨겨진 장벽현대의 웹 생태계, 특히 Node.js와 JavaScript를 기반으로 하는 엣지 컴퓨팅(Edge Computing)과 서버리스(Serverless) 아키텍처에서 데이터베이스 연결 관리는 개발자들의 가장 큰 숙제 중 하나입니다. 기존의 방식대로 매 요청마다 데이터베이스 커넥션을 맺고 끊는 것은 심각한 병목 현상과 커넥션 풀(Connection Pool) 고갈을 초래하기 때문입니다. 이러한 한계를 극복하기 위해 등장한 혁신적인 솔루션이 바로 Cloudflare Hyperdrive입니다. 전역적인 커넥션 풀링을 통해 연결 지연 시간을 비약적으로 단축시켜 주는 이 기술은, Cloudflare Workers를 사용하는 개발자들에게 필수 불가결한 요소로 자리 잡고 있습니다. 하지만, 클라우드 환경이 아닌 로컬 또는 온프레미스(On-premise) 인스턴스에 안전하게 격리된 내부 데이터베이스(MySQL, MariaDB 등)를 Cloudflare Tunnel을 통해 Hyperdrive와 연동하려고 할 때, 수많은 개발자들이 예기치 못한 거대한 장벽에 부딪히게 됩니다. 공식 문서의 안내를 충실히 따랐음에도 불구하고 마주하게 되는[code: 2011] Network connection refused, 403 Forbidden, 그리고 끈질기게 괴롭히는 301 Moved Permanently 에러가 바로 그것입니다. 본 포스팅에서는 필자가 실제 운영 환경에서 겪었던 뼈아픈 실패 과정과 수많은 삽질의 기록을 투명하게 공유하고자 합니다. 겉보기에는 네트워크 보안 정책 문제 같았던 이 에러들의 진짜 원인을 분석하고, 결국 모든 복잡한 우회 설정을 원점으로 되돌린 후 단 2개의 설정 변경과 '새로운 서브도메인'을 통해 완벽한 연결을 이뤄낸 '유레카'의 순간을 서론, 본론, 결론의 구조로 상세히 풀어내겠습니다. 네트워크 인프라 구성 시 프로토콜의 특성을 이해하는 것이 얼마나 중요한지 깨닫는 계기가 되기를 바랍니다. 본론 1: 끝없는 삽질의 기록 (실패한 접근법들)Hyperdrive를 생성하고 Wrangler CLI를 통해 내부 데이터베이스 연결을 시도했을 때, 가장 먼저 마주한 것은 403 Forbidden 에러였습니다. (예: Connecting to database via Cloudflare Tunnel failed: 403 Forbidden) 첫 번째 오판: Zero Trust Access의 개입이라 맹신하다 Cloudflare의 에러 로그를 분석한 결과, 해당 도메인에 'App AUD' 로그가 찍히는 것을 확인했습니다. 이는 Cloudflare Zero Trust Access가 해당 접근을 차단하고 있다는 명백한 증거처럼 보였습니다. 데이터베이스 통신(TCP)은 브라우저를 통한 사용자 인증을 수행할 수 없으므로, 당연히 인증 벽에 막혔다고 판단했습니다. 이를 해결하기 위해 Access > Applications 메뉴에서 해당 서브도메인(예: db.example.com)에 대한 명시적인 Bypass(우회) 정책을 추가했습니다. IP 대역 0.0.0.0/0에 대해 모든 인증을 건너뛰도록 설정한 것입니다. 하지만 결과는 처참했습니다. 403 에러는 사라지지 않았고, 오히려 에러의 양상만 복잡해졌습니다. 두 번째 오판: 전역 HTTPS 강제 설정을 해제하다 Bypass 정책 적용 후 터널을 재시작하자, 이번에는 301 Moved Permanently 에러가 등장했습니다. 301은 웹 브라우저에게 "이 페이지는 영구적으로 이동되었으니 새로운 주소로 가라"고 지시하는 HTTP 상태 코드입니다. TCP 통신을 시도하는 Hyperdrive 입장에서는 이 웹 전용 응답을 이해할 수 없어 연결을 강제로 종료해 버린 것입니다. 이 문제를 해결하기 위해 Cloudflare 대시보드에서 도메인 전체에 적용된 'Always Use HTTPS (항상 HTTPS 사용)' 옵션을 완전히 비활성화(Off)해 보았습니다. 심지어 Page Rules를 통해 해당 서브도메인의 모든 보안 검사를 끄는 치트키까지 동원했습니다. 그러나 경악스럽게도, 터널 로그에는 여전히 301 리다이렉션 에러가 찍혀 있었습니다. 설정의 늪에 빠져버린 순간이었습니다. 본론 2: 문제의 본질 (숨겨진 3가지 근본 원인)며칠간의 디버깅 끝에, 이 복잡한 에러 체인의 이면에 숨겨진 3가지 근본적인 원인을 마침내 규명할 수 있었습니다. 문제는 엉뚱한 곳에 있었습니다. 1. localhost와 127.0.0.1의 치명적인 차이 (IPv6 해석 문제) Cloudflare Tunnel 설정 시 Service URL을 tcp://localhost:3306으로 지정한 것이 첫 번째 화근이었습니다. 최신 리눅스(Ubuntu 등) OS 환경에서 시스템은 localhost를 IPv4인 127.0.0.1보다 IPv6 루프백 주소인 ::1로 우선 해석(Resolve)하는 경향이 있습니다. 만약 MariaDB가 0.0.0.0(IPv4) 대역에서만 수신 대기(Listen)하도록 설정되어 있다면, 터널은 존재하지 않는 IPv6 포트를 두드리다 결국 연결을 포기하게 됩니다. (출처: Cloudflare Tunnel 공식 트러블슈팅 가이드 및 IETF RFC 3493) 2. 조용한 암살자, HSTS 하위 도메인 상속 (includeSubDomains) 'Always Use HTTPS'를 껐음에도 301 에러가 발생한 진짜 주범은 바로HSTS (HTTP Strict Transport Security) 정책이었습니다. SSL/TLS 설정 깊숙한 곳에 있는 includeSubDomains (하위 도메인에 HSTS 정책 적용) 옵션이 켜져 있으면, 최상위 도메인의 강력한 HTTPS 강제 정책이 DB 터널 용도로 파놓은 서브도메인까지 모조리 오염시킵니다. 웹 트래픽을 보호하기 위한 기능이, 순수한 TCP 트래픽을 강제로 암호화된 웹 포트(443)로 밀어 넣으려 하면서 시스템 간의 언어 불일치를 일으킨 것입니다. 3. 브라우저와 엣지 노드에 남은 '301 캐시 찌꺼기' 가장 개발자를 미치게 만드는 요소입니다. 301 리다이렉션은 '영구적인 이동'을 의미하기 때문에, 브라우저와 Cloudflare의 엣지 서버(Edge Server)는 이 응답을 매우 공격적으로 캐싱합니다. 즉, HSTS 설정을 끄고 올바르게 구성을 수정했음에도 불구하고, 중간 노드들이 과거의 301 에러를 기억하고 계속해서 못된 목적지로 트래픽을 던져버리는 '301 찌꺼기 현상'이 발생했던 것입니다. 본론 3: 모든 것을 비우고 찾아낸 '유레카'의 순간원인을 완벽히 파악한 후, 지금까지 시도했던 모든 복잡한 설정들을 과감하게 폐기했습니다. 무의미했던 Access Bypass 정책을 완전히 삭제하고, 메인 도메인의 웹 보안을 위해 껐던 'Always Use HTTPS' 옵션도 다시 원래대로 활성화(On)하여 보안을 원상 복구했습니다. 그리고 딱 다음과 같은 핵심적인 조치만을 취했습니다. 명시적인 IPv4 주소 사용: 터널의 Service URL을 불확실한 localhost 대신 **tcp://127.0.0.1:3306**으로 명확하게 픽스했습니다.HSTS 상속 단절: SSL/TLS > Edge Certificates 메뉴에서 '하위 도메인에 HSTS 정책 적용' 옵션만을 정확하게 찾아 **[Off]**로 변경했습니다. 하지만 여기서 끝이 아니었습니다. 앞서 언급한 악랄한 '301 리다이렉트 찌꺼기'를 피하기 위해 특단의 조치를 취했습니다. 기존에 사용하며 캐시가 묻어버린 서브도메인(예: db.example.com)을 과감히 버리고, 완전히 새로운 텍스트를 가진 새 서브도메인(예: db2.example.com)을 터널 호스트네임으로 추가한 것입니다. 새로운 도메인은 어떠한 캐시의 영향도 받지 않는 순백의 상태입니다. 터널 데몬(sudo systemctl restart cloudflared)을 재시작하고, 완전히 새로운 서브도메인을 향해 Hyperdrive 생성 명령어를 실행하는 순간! 화면에는 붉은색 에러 메시지 대신 영롱한 ID: xxxxxxxx... 성공 코드가 출력되었습니다. 모니터 앞에서 저도 모르게 "유레카!"를 외친 순간이었습니다. 결론: 네트워크 프로토콜에 대한 이해가 삽질을 줄인다Cloudflare Hyperdrive와 Tunnel의 조합은 성공적으로 세팅만 끝난다면, 로컬 환경의 보안성을 완벽하게 유지한 채 전 세계 어디서든 지연 없는 데이터 접근을 가능하게 하는 마법 같은 인프라입니다. 그러나 이 과정에서 겪은 수많은 실패는, 겉으로 보이는 에러 코드 이면에 숨겨진 네트워크 계층(OSI 7 Layer)의 본질을 이해해야 함을 강력하게 시사합니다.우리가 겪었던 403과 301 에러의 본질은 결국 **"Cloudflare의 강력한 웹(HTTP/HTTPS) 중심 보안 철학이, 시스템 간의 순수한 TCP 통신에 잘못 개입하면서 발생한 오해"**였습니다. 문제가 발생했을 때 무작정 우회(Bypass) 정책을 남발하거나 전역 보안 설정을 해제하는 것은 정답이 아닙니다.127.0.0.1이라는 명시적인 주소 사용의 중요성, HSTS 정책의 상속 범위 제어, 그리고 강력한 캐시 메커니즘을 우회하기 위한 '새로운 네임스페이스(서브도메인) 사용'이라는 발상의 전환이야말로 이 문제를 해결하는 가장 우아하고 정확한 방법이었습니다. [핵심 요약] 바쁜 개발자를 위한 Hyperdrive + Tunnel 설정 5단계 액션 플랜앞서 설명한 복잡한 네트워크 이론과 수많은 시행착오를 단 5개의 핵심 단계로 압축했습니다. Zero Trust Access의 Bypass 정책 추가나 전역 HTTPS 설정 해제 없이, 오직 본질적인 라우팅과 캐시 문제만을 해결하여 완벽한 연결을 구성하는 방법입니다.Step 1. 명시적인 IPv4 루프백 주소 할당경로: Cloudflare 대시보드 > Networks > Tunnels > [터널 선택] > Public Hostname작업: Service Type을 TCP로 설정하고, URL을 localhost:3306 대신 **127.0.0.1:3306**으로 명확하게 지정합니다.목적: OS의 네트워크 스택이 localhost를 IPv6(::1)로 우선 해석하여 발생하는 Connection Refused 에러를 원천 차단합니다.Step 2. HSTS 하위 도메인 상속 차단 (가장 중요)경로: Cloudflare 대시보드 > SSL/TLS > Edge Certificates > HSTS 설정작업: '하위 도메인에 HSTS 정책 적용(includeSubDomains)' 옵션을 찾아 반드시 [Off(끔)] 상태로 변경합니다. (메인 도메인의 Always Use HTTPS는 켜두어도 무방합니다.)목적: 순수 TCP 통신을 시도하는 터널 서브도메인에 강제 HTTPS 웹 리다이렉션 정책이 상속되어 발생하는 301 Moved Permanently 에러를 방지합니다.Step 3. 엣지 캐시 회피를 위한 '새로운 서브도메인' 발급작업: 기존에 403이나 301 에러를 겪었던 서브도메인(예: db.example.com)은 브라우저 및 엣지 노드에 악성 리다이렉트 캐시가 남아있을 확률이 매우 높습니다. 과감히 폐기하고 완전히 새로운 서브도메인(예: db2.example.com)을 Public Hostname에 추가하십시오.목적: 캐시 찌꺼기로 인한 유령 에러를 회피하고, 순백의 상태에서 새로운 라우팅 규칙을 적용받기 위함입니다.Step 4. Cloudflare 데몬(cloudflared) 재시작작업: 데이터베이스가 위치한 호스트 서버의 터미널에 접속하여 아래 명령어를 실행합니다.Bash sudo systemctl restart cloudflared 목적: 대시보드에서 변경한 터널 라우팅 정보와 새로운 호스트네임 정책을 서버 데몬이 즉각적으로 동기화하도록 강제합니다.Step 5. Hyperdrive 최종 생성 및 연결 확인작업: Wrangler CLI 또는 Cloudflare 대시보드를 통해 새로 만든 서브도메인으로 연결을 시도합니다.Bash npx wrangler hyperdrive create --connection-string="mysql://:@:3306/" 결과: 지긋지긋한 에러 메시지 대신 성공적으로 생성된 ID 값을 획득할 수 있습니다. 축하합니다. 데이터베이스 고속도로가 개통되었습니다! 이 치열했던 트러블슈팅 기록이, 지금 이 순간에도 Connection Refused와 싸우며 밤을 지새우고 있을 수많은 개발자들에게 명확한 해답과 안도감을 주기를 바랍니다. 기본으로 돌아가면 답이 보입니다.

자세히 보기

DEV_MANUAL

[Drizzle ORM] MariaDB schema.ts 생성 실패(Silent Fail) 원인 및 해결

[Drizzle ORM] MariaDB schema.ts 생성 실패(Silent Fail) 원인 및 해결

MariaDB 연동 시 schema.ts 생성 실패(Silent Fail) 원인 및 완벽 해결 가이드최근 웹 개발 트렌드에서 서버리스(Serverless)와 엣지 컴퓨팅(Edge Computing)의 결합은 피할 수 없는 대세가 되었습니다. 특히 초경량 웹 프레임워크인 Hono와 Cloudflare Workers, 그리고 타입 안정성을 극대화해 주는 Drizzle ORM의 조합은 압도적인 퍼포먼스와 개발 경험을 제공합니다. 하지만 완벽해 보이는 이 스택에도 실제 인프라 환경과 맞물릴 때 예상치 못한 치명적인 함정이 존재합니다.혹시 완벽한 연결 설정을 마쳤음에도 npx drizzle-kit pull (또는 introspect) 명령어가 schema.ts 파일을 뱉어내지 않고 조용히 종료되는 이른바 '침묵의 실패(Silent Fail)' 현상을 겪고 계신가요? 에러 로그조차 남지 않아 네트워크 드라이브 권한부터 OS 설정까지 뒤지며 귀중한 시간을 허비하고 계실 분들을 위해, 결론부터 가볍게 말씀드리겠습니다. 여러분의 설정이나 OS 환경의 문제가 아닙니다. 범인은 바로 MariaDB의 'JSON 데이터 타입'이며, 해당 컬럼을 일시적으로 LONGTEXT로 변경하는 것만으로 이 지독한 버그를 즉시 해결할 수 있습니다.지금부터 수많은 개발자를 절망에 빠뜨렸던 이 버그의 정확한 원인과, 아키텍처 수준에서의 기술적 분석, 그리고 가장 우아하고 확실한 해결책을 상세히 파헤쳐 보겠습니다.1. 완벽한 거짓말: 침묵의 실패(Silent Fail) 현상Drizzle ORM을 도입하여 기존 데이터베이스의 스키마를 코드로 추출(Reverse Engineering)하려 할 때, 우리는 보통 다음과 같은 터미널 로그를 마주하게 됩니다.Bash[✓] 8 tables fetched [✓] 58 columns fetched [✓] 4 indexes fetched [⢿] 1 check constraints fetching [✓] 0 views fetched 로그상으로는 분명 Contabo와 같은 원격 VPS 서버에 구축된 MariaDB에 성공적으로 접근하여 테이블과 컬럼 정보를 100% 읽어온 것처럼 보입니다. 하지만 추출 작업의 마지막 단계인 '파일 생성(File Writing)' 단계로 넘어가지 못한 채 프롬프트가 조용히 종료됩니다. verbose 옵션이나 디버그 모드를 켜도 예외(Exception)를 던지지 않기 때문에, 많은 개발자들이 이를 프로젝트 폴더 내 .prettierrc 충돌이나 윈도우 환경의 물리 드라이브(Z: 등) 쓰기 권한 문제로 오인하곤 합니다 [출처: Drizzle ORM 커뮤니티 트러블슈팅 사례 분석].2. 기술적 원인 규명: MariaDB와 Drizzle AST 엔진의 치명적 불협화음이 문제의 근본적인 원인을 이해하기 위해서는 MariaDB가 내부적으로 데이터를 처리하는 방식과 Drizzle Kit의 코드 생성기(AST Generator)가 메타데이터를 파싱하는 로직을 동시에 이해해야 합니다.첫째, MariaDB의 JSON 타입은 독립된 타입이 아닙니다. 순수 MySQL과 달리, MariaDB에서 JSON 데이터 타입은 사실 LONGTEXT 데이터 타입에 CHECK (json_valid(column_name))이라는 제약조건(Check Constraint)이 자동으로 은닉되어 붙어있는 일종의 별칭(Alias)이자 매크로입니다 [출처: MariaDB Official Documentation - JSON Data Type]. 즉, 개발자가 단순히 JSON 컬럼을 생성하더라도, DB 엔진 내부적으로는 데이터의 무결성을 검증하기 위한 CHECK 제약조건이 강제로 생성되는 것입니다.둘째, Drizzle Kit의 파싱 엔진 결함입니다. Drizzle Kit(v0.31.x 기준)이 기존 DB 구조를 읽어올 때, MariaDB의 information_schema.check_constraints 시스템 테이블에 접근하여 제약조건을 가져옵니다. 이때 MariaDB는 컬럼명과 결과값을 반환하는데, 내부적으로 자동 생성된 JSON 검증용 제약조건이나 복잡한 표현식(Expression)을 만났을 때 Drizzle의 파서가 해당 문법을 TypeScript 코드로 번역하지 못하고 뇌 정지(Hang) 상태에 빠지게 됩니다 [출처: Drizzle ORM GitHub Issue Tracker - Introspection Silent Failure]. 최악의 문제는 이 과정에서 예외 처리가 제대로 되어 있지 않아 프로세스가 시스템 에러 없이 강제 종료(Exit 0) 처리된다는 점입니다.💡 실제 프로젝트 사례: tb_finance_chart 테이블의 비극실제 금융 데이터나 API 응답 로그를 수집하는 시스템을 설계할 때, 응답 데이터를 통째로 저장하기 위해 JSON 타입을 빈번하게 사용합니다. 예를 들어 tb_finance_chart라는 테이블에 API 응답 결과를 담는 chart_data 컬럼을 JSON 타입으로 지정했다고 가정해 보겠습니다. Drizzle Kit은 이 8개의 테이블 중 단 하나의 테이블에라도 숨겨진 CHECK 제약조건(JSON 타입)이 존재한다면, tablesFilter 옵션으로 해당 테이블을 제외하려 시도하더라도 전체 프로세스를 중단시켜 버립니다. 결국 개발자는 원인도 모른 채 전체 스키마 생성을 포기하게 되는 상황에 이릅니다.3. 해결책: '눈속임'을 통한 스키마 추출 전략문제의 원인이 파악되었다면 해결은 명쾌합니다. Drizzle Kit의 파서가 해석하지 못하는 MariaDB의 '숨겨진 CHECK 제약조건'을 추출 순간에만 일시적으로 제거해 주는 것입니다. 이를 가장 안전하게 수행하는 방법은 문제가 되는 JSON 컬럼을 잠시 LONGTEXT로 속이는 것입니다.아래에 제시된 3단계의 SQL 쿼리 및 터미널 명령어 프로세스를 순서대로 진행하시기 바랍니다.Step 1. 범인 색출 (JSON 컬럼 확인)먼저 현재 사용 중인 데이터베이스 내에 JSON 타입으로 선언된 컬럼이 정확히 어디에 존재하는지 파악해야 합니다. DB 관리 툴(DBeaver, DataGrip 등)에서 아래 쿼리를 실행합니다.SQLSELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '본인의_데이터베이스명' AND DATA_TYPE = 'json'; Step 2. 타입 임시 변경 및 제약조건 해제위 쿼리를 통해 JSON 타입이 적용된 테이블과 컬럼(예: tb_finance_chart의 chart_data 컬럼)을 확인했다면, 이를 일시적으로 LONGTEXT로 변경합니다. 이 작업을 수행하면 MariaDB 내부의 CHECK (json_valid()) 제약조건이 해제됩니다. (기존 데이터가 삭제되거나 손상되지 않으니 안심하셔도 됩니다.)SQL-- Drizzle Kit이 멈추지 않도록 타입을 LONGTEXT로 일시 변경 ALTER TABLE tb_finance_chart MODIFY chart_data LONGTEXT; Step 3. Drizzle Schema 추출 및 원상 복구이제 장애물이 모두 제거되었습니다. 터미널로 돌아가 스키마 추출 명령어를 실행합니다.Bashnpx drizzle-kit pull 명령어를 실행하자마자 지긋지긋했던 침묵을 깨고 프로젝트 폴더 내에 schema.ts 파일이 완벽하게 생성되는 것을 확인할 수 있습니다. 성공적으로 파일을 얻어냈다면, 생성된 TypeScript 코드 내에서 해당 컬럼을 .json() 타입으로 수동 보정해 주고, DB의 데이터 타입 역시 원래의 JSON으로 복구해 줍니다.SQL-- 추출 완료 후 DB 구조 원상 복구 ALTER TABLE tb_finance_chart MODIFY chart_data JSON; 맺음말: 도구의 맹점을 극복하는 엔지니어링우리는 종종 강력한 프레임워크와 자동화 도구에 의존하며, 도구가 내뱉는 침묵을 나의 설정 실수로 자책하곤 합니다. 하지만 이번 MariaDB와 Drizzle ORM의 충돌 사례에서 볼 수 있듯, 각 시스템(DBMS와 ORM)이 데이터를 바라보고 해석하는 저수준(Low-level)의 차이에서 발생하는 아키텍처 결함은 언제든 발생할 수 있습니다.결론적으로, MariaDB 환경에서 Drizzle Kit이 조용히 종료된다면 당황하지 마시고 데이터베이스 내의 JSON 타입 컬럼을 색출하여 일시적으로 LONGTEXT로 변경한 뒤 추출(pull)을 시도하십시오.이러한 도구의 맹점을 파악하고 우회로를 설계해 내는 과정이야말로 개발자가 단순한 코더를 넘어 시스템 엔지니어로 성장하는 가장 가치 있는 경험일 것입니다. 본 포스팅이 원인 모를 'Silent Fail' 버그로 밤을 지새우는 수많은 개발자들의 퇴근 시간을 앞당기는 데 도움이 되기를 진심으로 바랍니다.

자세히 보기