IT/코딩

최신 기술 동향과 실무 중심의 튜토리얼을 만나보세요.

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

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

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

자세히 보기
VS Code 로 GitHub 연동 및 자동 배포 시스템 구축

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

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

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

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

<h2>서론: 엣지(Edge) 환경의 축복, 그리고 데이터베이스 연결이라는 악몽</h2><p><br></p><p>최근 웹 개발 생태계에서 Cloudflare Workers로 대표되는 엣지 컴퓨팅(Edge Computing) 기술은 선택이 아닌 필수가 되어가고 있습니다.</p><p> 콜드 스타트(Cold Start)가 거의 없는 빠른 실행 속도, 전 세계 어디서나 사용자와 가장 가까운 노드에서 코드가 실행된다는 점은 서버리스 아키텍처의 혁명과도 같습니다.</p><p> 저 역시 Hono 프레임워크와 결합하여 초고속 API 서버를 구축하는 과정에서 엣지 환경의 매력에 푹 빠졌습니다.</p><p>&nbsp;</p><p>하지만 이 눈부신 엣지 환경에 입문한 개발자들이 가장 먼저, 그리고 가장 뼈아프게 좌절을 겪는 마의 구간이 있습니다.</p><p> 바로 **'전통적인 관계형 데이터베이스(RDBMS)와의 연결'**입니다. Node.js 환경에서는 너무나도 당연하고 쉽게 사용하던 <code>mysql2</code> 라이브러리가</p><p> Cloudflare Workers 환경에 올라가는 순간, 수많은 에러를 뿜어내며 개발자의 멘탈을 흔들어 놓습니다.</p><p>&nbsp;</p><p>그중에서도 저를 며칠 동안 밤잠 설치게 만들었던 가장 악명 높은 에러 메시지가 바로 이것입니다.</p><p>&nbsp;</p><blockquote><code>Error: Code generation from strings disallowed for this context</code></blockquote><blockquote>&nbsp;</blockquote><p>본 포스팅에서는 Hono 프레임워크, Drizzle ORM, 그리고 MySQL을 Cloudflare Workers 환경에서 구축하려다 마주한</p><p> 이 끔찍한 에러의 원인을 런타임 아키텍처 관점에서 철저히 분석해 봅니다. 또한, Cloudflare의 데이터베이스 프록시 서비스인 'Hyperdrive'를 활용하여</p><p> 로컬 개발 환경과 실서버 환경 모두에서 완벽하게 동작하는 우아한 해결책을 공유하고자 합니다.</p><p>&nbsp;</p><p>수많은 삽질과 테스트 끝에 얻어낸 실전 노하우이니, 저와 같은 환경을 구축 중인 분들이라면 이 글을 통해 귀중한 디버깅 시간을 크게 절약하실 수 있을 것입니다.</p><p>&nbsp;</p><p><br></p><h2>본론 1: 엣지 환경의 구원투수, Hyperdrive 도입과 초기 설정</h2><p>Cloudflare Workers와 같은 Serverless/Edge 환경에서 외부 RDBMS(MySQL, PostgreSQL 등)에 직접 연결을 시도하면 두 가지 큰 문제에 직면하게 됩니다.</p><p> 첫째는 물리적 거리로 인한 네트워크 지연(Latency)이고, 둘째는 수많은 엣지 노드가 동시에 DB에 접속하면서 발생하는 커넥션 풀(Connection Pool) 고갈 문제입니다.</p><p>이를 해결하기 위해 Cloudflare는 전역적인 커넥션 풀링과 쿼리 캐싱을 지원하는 <strong>Hyperdrive</strong>라는 훌륭한 서비스를 제공합니다. Hyperdrive를 사용하기 위한 첫걸음은</p><p> 프로젝트의 <code>wrangler.toml</code> 파일에 바인딩 설정을 구성하는 것입니다.</p><p>&nbsp;</p><p>이때 로컬 환경(<code>wrangler dev</code>)에서도 원격 DB에 직접 붙어서 원활한 테스트를 진행해야 하므로, <code>localConnectionString</code> 설정이 매우 중요합니다.</p><p>&nbsp;</p><h3>올바른 <code>wrangler.toml</code> 설정 예시</h3><p><br></p><pre class="ql-syntax" spellcheck="false"> 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" </pre><p><code> </code>위와 같이 설정하면, 애플리케이션 내에서 <code>c.env.HH_DB.connectionString</code>을 호출할 때 로컬 환경에서는 <code>localConnectionString</code> 값을,</p><p> 배포된 Workers 환경에서는 Cloudflare가 제공하는 최적화된 프록시 연결 문자열을 자동으로 반환해 줍니다.</p><p>&nbsp;</p><h2>본론 2: 삽질의 시작과 'Code generation...' 에러의 진짜 정체</h2><p>Wrangler 설정을 마치고, Drizzle ORM과 <code>mysql2</code> 라이브러리를 사용해 데이터베이스 커넥션 풀을 생성하는 코드를 작성했습니다.</p><p> 네트워크 핑 테스트(Telnet)도 정상이었고, 논리적으로 완벽한 구조라고 확신했습니다.</p><p>하지만 <code>wrangler dev</code>를 통해 실행하는 순간, 여지없이 애플리케이션이 크래시되며 다음 에러가 발생했습니다.</p><p>&nbsp;</p><p> <code>Code generation from strings disallowed for this context</code> </p><p> 연결 자체(Network Connection)는 성공했지만, 데이터베이스에 쿼리를 던지고 결과를 받아오는 과정에서 런타임이 코드를 강제로 종료시킨 것입니다.</p><p> 대체 왜 이런 일이 발생하는 것일까요? 원인은 <strong>Cloudflare Workers의 런타임 환경과 </strong><code><strong>mysql2</strong></code><strong> 라이브러리의 내부 파싱 최적화 로직 간의 치명적인 충돌</strong>에 있었습니다.</p><p>&nbsp;</p><h3>1) Cloudflare Workers의 V8 Isolates 보안 정책</h3><p>전통적인 Node.js 런타임과 달리, Cloudflare Workers는 Chrome 브라우저의 자바스크립트 엔진인 <strong>V8 Isolates</strong> 환경에서 실행됩니다.</p><p> 이 엣지 환경은 보안과 격리를 최우선으로 하기 때문에, 문자열을 동적으로 자바스크립트 코드로 변환하여 실행하는 <code>eval()</code> 함수나 <code>new Function()</code> 생성자의 사용을 엄격하게 금지하고 있습니다.</p><p> (출처: Cloudflare Workers Runtime Architecture Official Docs)</p><p>&nbsp;</p><h3>2) <code>mysql2</code> 라이브러리의 파서(Parser) 메커니즘</h3><p>반면, Node.js 생태계에서 가장 널리 쓰이는 데이터베이스 드라이버인 <code>mysql2</code>는 극강의 성능을 끌어내기 위해 내부적으로</p><p><strong>동적 코드 생성(Dynamic Code Generation)</strong> 기법을 사용합니다. 데이터베이스로부터 텍스트 형태의 쿼리 결과(Row)를 전달받으면,</p><p> 이를 가장 빠르게 자바스크립트 객체로 매핑하기 위해 내부적으로 <code>new Function()</code>을 호출하여 맞춤형 파서 코드를 실시간으로 생성해 버립니다. 바로 이 지점입니다.</p><p> 로컬 Node.js에서는 완벽했던 최적화 기법이, V8 Isolates 환경에 올라가는 순간 **"보안 정책 위반"**으로 간주되어 무참히 차단당했던 것입니다.</p><p>&nbsp;</p><h2>본론 3: 단 한 줄의 마법, 완벽한 해결책 적용하기</h2><p>이 문제를 우회하기 위해 로컬 환경과 Workers 환경을 억지로 분리(<code>if (isWorker)</code>)하거나, Drizzle의 HTTP 프록시 드라이버를 억지로 끼워 맞추는 등의 수많은 시행착오를 겪었습니다.</p><p>하지만 해답은 생각보다 훨씬 가까운 곳에 있었고, 매우 단순했습니다.</p><p> Cloudflare 공식 문서의 Hyperdrive 가이드와 <code>mysql2</code> 레포지토리의 이슈 트래커를 교차 검증한 결과, 이 환경 충돌을 해결하기 위해 라이브러리 차원에서 지원하는 <strong>마법의 옵션</strong>을 찾아냈습니다.</p><p>바로 <code><strong>disableEval</strong></code> 속성입니다.</p><p>이 옵션을 적용하기 위해서는 다음의 간단한 절차를 거치면 됩니다.</p><p>&nbsp;</p><h3>Step 1: <code>mysql2</code> 라이브러리를 최신 버전으로 강제 업데이트</h3><p>이 우회 기능은 과거 버전에서는 완벽히 지원되지 않았습니다. 반드시 터미널을 열고 프로젝트의 패키지를 최신 상태로 정비해야 합니다.</p><p><br></p><pre class="ql-syntax" spellcheck="false"> npm install mysql2@latest </pre><h3><code> </code></h3><h3> Step 2: 커넥션 코드에 <code>disableEval: true</code> 추가</h3><p>데이터베이스 연결을 담당하는 핵심 파일(<code>db_conn.js</code>)에서 <code>mysql.createPool()</code>을 호출할 때 설정 객체에 해당 옵션을 추가합니다.</p><p> 이 한 줄은 <code>mysql2</code> 엔진에게 **"동적 함수 생성을 통한 속도 최적화를 포기하는 대신, 순수 자바스크립트 기반의 안전한 파싱을 수행하여 V8 보안 정책을 우회하라"**고 지시하는 강력한 명령어입니다.</p><p><br></p><pre class="ql-syntax" spellcheck="false"> // src/functions/db_conn.js import { drizzle } from "drizzle-orm/mysql2"; import mysql from "mysql2/promise"; // 싱글톤 패턴을 위한 커넥션 인스턴스 전역 변수 let db = null; export const getConnection = async (c) =&gt; { // 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; }; </pre><p><code> </code>이 옵션을 부여하고 애플리케이션을 다시 실행해 보았습니다. 결과는 대성공이었습니다.</p><p> 그토록 저를 괴롭히던 <code>Code generation...</code> 에러는 자취를 감추었고, 로컬과 Workers 배포 환경 모두에서 단일 로직으로 Drizzle ORM의 쿼리들이 물 흐르듯 실행되는 것을 확인할 수 있었습니다.</p><p>&nbsp;</p><h2>결론: 엣지 환경 DB 연결의 황금 레시피 요약</h2><p>새로운 기술 스택(Cloudflare Workers + Hono)을 도입하면서 겪은 이 일련의 트러블슈팅 과정은,</p><p> 개발 생태계의 패러다임이 바뀔 때 런타임 환경에 대한 깊은 이해가 얼마나 중요한지 깨닫게 해 준 소중한 경험이었습니다.</p><p>&nbsp;</p><p>수많은 가설과 실험이 오갔지만, 결국 이 길고 길었던 삽질의 <strong>가장 핵심적인 원인과 결론은 </strong><code><strong>disableEval: true</strong></code><strong> 옵션의 부재</strong> 하나였습니다.</p><p> Cloudflare의 V8 Isolates 환경이 <code>mysql2</code>의 내부 <code>eval()</code> 동작을 보안상의 이유로 차단했고, 이를 비활성화하는 옵션을 켜줌으로써 모든 퍼즐이 맞춰진 것입니다.</p><p>&nbsp;</p><p>앞서 장황하게 설명한 설정들을 모두 걷어내고, 현재 2026년 기준 Cloudflare Workers 환경에서 MySQL 서버와 Drizzle ORM을 연결하기 위해 필요한</p><p><strong>최종적이고 필수적인 라이브러리 세팅</strong>은 딱 두 가지로 요약됩니다.</p><p>&nbsp;</p><ol><li><code><strong>drizzle-orm</strong></code> : 타입 안정성(Type-Safety)과 가벼움을 자랑하며 엣지 환경에 가장 적합한 ORM.</li><li><code><strong>mysql2@latest</strong></code> : 반드시 최신 버전을 설치하여 <code>disableEval</code> 옵션을 온전히 지원받을 것. (불필요한 프록시 드라이버나 복잡한 분기 로직은 모두 삭제하셔도 무방합니다.)</li><li>&nbsp;</li></ol><p>엣지 컴퓨팅 기반의 서버리스 애플리케이션을 준비 중이신가요? DB 연결에서 알 수 없는 에러를 뿜어내며 좌절하고 계셨다면,</p><p> 지금 당장 여러분의 <code>package.json</code> 버전 명세서를 확인하시고 커넥션 풀에 <code>disableEval: true</code>를 살포시 추가해 보시길 권장합니다.</p><p>이 글이 누군가의 퇴근 시간을 극적으로 앞당겨주는 기분 좋은 해결책이 되었기를 바랍니다.</p><p><br></p><p> 앞으로도 직접 몸으로 부딪히며 얻어낸 생생한 트러블슈팅 기록들로 찾아오겠습니다.</p><p> 감사합니다!</p>

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

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

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

자세히 보기
Cloudflare 의 Hyperdrive 설정 삽질 과정

Cloudflare 의 Hyperdrive 설정 삽질 과정

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

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

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

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

자세히 보기