
Edge Crash 발생 해결: Cloudflare의 Agentic Inbox에서 SQLite의 50바이트 패턴 제한 문제 해결
요약
Cloudflare의 Agentic Inbox 프로젝트에서 SQLite의 LIKE 패턴 길이 제한으로 인해 발생하는 Edge 런타임 충돌 문제를 분석하고 해결했습니다. 검색어에 와일드카드가 추가되면서 50바이트 제한을 초과하여 발생하는 500 에러를 방지하는 과정을 다룹니다.
핵심 포인트
- Cloudflare Durable Object의 SQLite는 LIKE 패턴 길이를 50바이트로 제한함
- 와일드카드(%) 추가 시 검색어 길이가 48자를 초과하면 런타임 에러 발생
- 해당 에러는 Worker 실행 컨텍스트를 충돌시켜 HTTP 500 에러를 유발함
- Edge 환경의 메모리 보호 및 DoS 방지를 위한 SQLite의 설계 제약 사항 이해
이 게시물은 Sentry가 지원하는 DEV's Summer Bug Smash: Clear the Lineup을 위한 제출물입니다.
프로젝트 개요
cloudflare/agentic-inbox는 Edge-native 방식의 셀프 호스팅 가능한 자율형 AI 이메일 클라이언트입니다. Cloudflare Workers, React Router v7, 그리고 Workers AI (Llama 3.1)를 기반으로 구축되었으며, Cloudflare Email Routing을 통해 라우팅되는 수신 메일을 자동으로 처리합니다.
각 메일함은 임베디드 SQLite 데이터베이스를 기반으로 하는 격리된 Cloudflare Durable Object (MailboxDO) 내부에서 작동합니다. AI 에이전트는 Model Context Protocol (MCP) 도구를 사용하여 메일 기록을 검색하고, 스레드 전반에 걸쳐 컨텍스트를 검색하며, 자율적으로 응답 초안을 작성합니다.
Agentic Inbox 스택에 대한 로컬 통합 테스트를 실행하던 중, 자동 스레드 검색이 간헐적으로 실패하는 현상이 발생했습니다. 수신된 이메일에 긴 제목 헤더나 트래킹 문자열이 포함되어 있으면, AI 에이전트의 백그라운드 컨텍스트 검색이 서버 에러와 함께 Worker 실행 컨텍스트를 완전히 충돌(crash)시켰습니다.
버그 수정 또는 성능 개선
저는 Issue #36: "long search strings can 500 via Durable Object SQLite LIKE pattern limit" 문제를 해결했습니다.
기술적 분석: Edge 런타임에서의 SQLite 패턴 경계
Cloudflare Workers Durable Objects는 경량 SQLite 인스턴스를 Edge 메모리 공간에 직접 임베디드합니다. 공유 Edge 하드웨어에서 메모리 고갈 및 서비스 거부(DoS) 공격 벡터를 방지하기 위해, SQLite 컴파일 타임 기본값은 SQL LIKE 패턴 문자열에 대해 엄격한 50바이트 최대 길이를 강제합니다 (SQLITE_MAX_LIKE_PATTERN_LENGTH = 50).
MailboxDO.#buildSearchConditions에서 사용자가 제공한 검색 파라미터(query, from, to, subject)는 %${param}%를 사용하여 와일드카드 패턴에 직접 삽입되었습니다.
다음과 같은 쿼리를 실행할 때:
SELECT * FROM emails WHERE subject LIKE '%cf-agentic-inbox-external-inbound-test-20260728T203000Z%'
SQLite는 컬럼 문자열의 길이가 아니라 패턴 파라미터 자체의 바이트 길이를 평가합니다. 문자열 포맷팅 과정에서 입력값 앞뒤로 두 개의 % 와일드카드 바이트가 추가되기 때문에, 48자를 초과하는 검색 파라미터는 패턴 버퍼를 50바이트 이상으로 밀어냅니다.
예를 들어, 49자의 검색어는 51바이트 패턴(% + 49자 + % = 51바이트)을 생성합니다. 이를 Durable Object의 SQLite 인스턴스에서 실행하면, SQLite는 다음과 같은 처리되지 않은 런타임 에러를 발생시킵니다:
Uncaught Error: SQLite pattern length limit exceeded (pattern length exceeds 50 bytes in LIKE clause)
이 에러는 Durable Object의 실행 컨텍스트(execution context)를 충돌(crash)시키고, 클라이언트에 HTTP 500 Internal Server Error를 반환했습니다.
AI Agent에 미치는 영향
AI 에이전트는 들어오는 스레드를 평가할 때 과거 컨텍스트를 검색하기 위해 searchEmails 도구(tool)에 의존합니다. 자동화된 워크플로우나 사용자 프롬프트가 48자를 초과하는 제목(subject line), 메시지 ID(message ID) 또는 긴 검색어를 searchEmails에 전달하면, 도구 호출이 HTTP 500과 함께 실패했습니다. 이 처리되지 않은 충돌은 에이전트의 자율 실행 루프(autonomous execution loop)를 작업 도중에 중단시켜, 처리되지 않은 도구 호출 실패(unhandled tool invocation failures)를 초래했습니다.
코드
개선 사항
추가적인 런타임 오버헤드를 도입하거나 검색 기능을 망가뜨리지 않고 충돌을 해결하기 위해 다음과 같이 조치했습니다:
workers/durableObject/index.ts에MAX_LIKE_PARAM_LENGTH = 48을 정의했습니다.query,from,to,subject검색 필드 전체에 대해%...%와일드카드 패턴을 구성하기 전, 입력 문자열을 48자로 슬라이싱(slice)했습니다.- 48자에 와일드카드 2바이트를 더하면 정확히 50바이트가 되므로, 생성된 패턴 문자열이 SQLite의 50바이트 제한 내에 머무는 것을 보장합니다.
diff --git a/workers/durableObject/index.ts b/workers/durableObject/index.ts
index e2cbbfa..57cfce8 100644
--- a/workers/durableObject/index.ts
...
아키텍처 트레이드오프(Architectural Trade-offs) 및 프로덕션 정교화
검색 문자열을 48자로 자르는 것은 검색 정확도를 유지하면서 에지 데이터베이스(edge database)의 경계를 준수하는 정교한 해결책(surgical fix)입니다. 일반적인 사용자 이메일 워크플로(workflow)에서 발신자 주소, 수신자 또는 제목의 처음 48자는 정확한 SQL 매칭을 반환하기에 충분한 엔트로피(entropy)를 포함하고 있습니다.
SQL 바인딩(binding) 직전에 파라미터 슬라이싱(parameter slicing)을 수행함으로써, 애플리케이션 상태 내의 원래 검색 쿼리를 변형하거나 사용자에게 전체 검색 문자열을 표시하는 다운스트림 UI 컴포넌트를 깨뜨리는 것을 방지할 수 있습니다.
로컬 반복 작업(local iterations) 과정에서 외부 테스트 의존성을 추가했을 때 package.json 및 락파일(lockfiles)에 원치 않는 수정 사항이 발생했습니다. Cloudflare의 기여 표준(contribution standards)을 충족하기 위해, 모든 외부 테스트 의존성과 개발 환경 플래그(dev-environment flags)를 제거했습니다. 최종 풀 리퀘스트(pull request)는 npm run typecheck 및 npm run build를 통해 깔끔하게 컴파일되는 10줄, 1개 파일, 0-의존성(0-dependency) 수정 사항을 제공합니다.
Sentry의 최적 활용
Cloudflare Workers 및 Durable Objects 전반에 걸쳐 트레이싱 스팬(tracing spans)과 처리되지 않은 예외(uncaught exceptions)를 캡처하기 위해 @sentry/cloudflare를 사용하여 Worker 진입점에서 Sentry를 계측(instrumented)했습니다.
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
...
1. 에러 발견 및 예외 브레드크럼(Exception Breadcrumbs)
검색 엔드포인트가 49자 파라미터(GET /api/v1/mailboxes/test-mailbox/search?subject=cf-agentic-inbox-external-inbound-test-20260728T203000Z)로 쿼리되었을 때, Sentry는 AGENTIC-INBOX-1 이슈 아래에서 처리되지 않은 예외를 실시간으로 캡처했습니다.
트레이스 프리뷰(trace preview)는 실패한 http.server 루트 스팬(root span)을 강조 표시했으며, SQLite가 패턴 길이 에러(pattern length error)를 발생시킨 MailboxDO.#buildSearchConditions의 정확한 라인을 짚어냈습니다.
2. Sentry Seer AI 근본 원인 분석 (Root Cause Analysis)
Sentry Seer RCA는 스택 트레이스 (stack trace)를 분석하고, 기반이 되는 Durable Object 코드를 조사하여 문제를 진단했습니다:
근본 원인 (Root Cause): 검색 쿼리 파라미터가 정제되지 않은 상태로 SQLite의 LIKE 절에 전달되어, Cloudflare Workers의 50바이트 LIKE 패턴 제한을 초과함.
계획 (Plan): 검색 필터 파라미터를 LIKE 패턴으로 감싸기 전에 48자로 잘라내어(truncate), 패턴이 SQLite의 50바이트 제한 내에 유지되도록 함.

3. Sentry Bot을 통한 자동 수정 생성
Seer는 Sentry 내부에서 직접 패치 (patch)를 생성했으며, sentry bot을 통해 저장소 포크 (repository fork)에 Pull Request #1 (Fix: Truncate search parameters for SQLite LIKE compatibility)을 자동으로 생성했습니다.


4. 수정 후 텔레메트리 (Telemetry) 검증
자르기(truncation) 패치를 적용한 후, 동일한 49자 검색 요청을 다시 실행했습니다.
Sentry의 트레이스 (Traces) 뷰에서 해결되었음을 확인했습니다:
- HTTP 상태 (HTTP Status):
200 OK - Sentry 상태 (Sentry Status):
status: ok - 포착되지 않은 예외 (Uncaught Exceptions):
Issues: 0 - 응답 페이로드 (Response Payload):
{"emails":[],"totalCount":0}
트레이스 타임라인(trace timeline)이 완전한 초록색으로 변하며, 이제 검색 작업이 Durable Object 컨텍스트를 충돌시키지 않고 안정적으로 완료됨을 확인했습니다.

에지 런타임(edge runtimes)과 Durable Objects를 디버깅하는 것은 오류가 아이솔레이트(isolate) 경계 깊은 곳에서 발생하기 때문에 종종 블랙박스 내부에서 작업하는 것처럼 느껴질 수 있습니다. @sentry/cloudflare를 사용하여 계측(instrumenting)함으로써 Worker에서 Durable Object로 이어지는 경계를 가로질러 실패를 즉각적으로 추적할 수 있었습니다. Sentry Seer AI가 스택 트레이스(stack trace)를 분석하고, SQLite의 내부 컴파일 제한을 식별하며, sentry bot을 통해 작동하는 패치를 자동으로 작성하는 모습은 현대적인 관측성(observability) 도구들이 수동적인 오류 로깅에서 능동적이고 자동화된 해결 방식으로 진화하고 있음을 보여주었습니다.
Google AI의 최적 활용
Google AI (Gemini)는 근본 원인 분석(root-cause analysis), 에지 제약 조건 검증(edge constraint verification), 그리고 정밀한 리팩터링(surgical refactoring) 과정 전반에 걸쳐 능동적인 AI 페어 프로그래머(AI pair programmer) 역할을 수행했습니다.
1. 에지 런타임 제약 조건 검증 (Edge Runtime Constraint Verification)
Sentry가 처리되지 않은 SQLite 예외를 포착했을 때, Gemini는 Cloudflare Durable Objects의 컴파일 타임 SQLite 플래그를 표준 SQLite 사양과 교차 참조함으로써 에지 런타임 제약 조건을 분석하는 데 도움을 주었습니다. 이를 통해 SQLITE_MAX_LIKE_PATTERN_LENGTH의 기본값이 50바이트임을 확인하였고, SQLite가 기본 컬럼 값의 길이가 아닌 포맷팅된 패턴 문자열(%param%)의 바이트 길이를 평가한다는 사실을 검증했습니다.
2. 정밀한 경계 계산 (Surgical Boundary Calculation)
Gemini는 문자열 슬라이싱 임계값(MAX_LIKE_PARAM_LENGTH = 48)을 평가하는 데 도움을 주었습니다. 이를 통해 48개의 문자와 2개의 와일드카드 바이트(%...%)를 합친 길이가 모든 쿼리 필드(query, from, to, subject)에서 표준 검색 문자열에 대한 엣지 케이스(edge-case) 절단을 유발하지 않으면서, SQLite의 50바이트 버퍼 제한을 100% 준수함을 보장했습니다.
3. 프로덕션 표준 코드 감사 (Production Standard Code Audit)
풀 리퀘스트(pull request)가 Cloudflare의 업스트림 기여 가이드라인을 충족하는지 확인하기 위해, Gemini를 사용하여 git diff를 검토하고 임시 개발 플래그, 모의(mock) 파일 및 추가 패키지 의존성을 식별하여 제거했습니다. 그 결과, 업스트림 리뷰를 받을 준비가 된 1개 파일, 10줄, 의존성 제로(zero-dependency)의 깔끔한 기여를 완성했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

