우리의 SDK를 만들기 전 16개의 샌드박스(Sandbox) SDK 소스 코드를 분석했습니다
요약
16개의 샌드박스 및 컴퓨트 SDK 소스 코드를 심층 분석하여 도출한 설계 원칙을 공유합니다. 상태 유지 핸들, 네임스페이스 구조, 환경 변수 설정 등 공통 패턴과 더불어 재시도 로직 구현의 중요성을 다룹니다.
핵심 포인트
- 16개 경쟁사 SDK의 소스 레벨 분석을 통한 설계 결정
- 상태 유지 핸들(Stateful Handle) 패턴의 유효성 확인
- 가독성을 위한 네임스페이스 구조 설계 전략
- 에러 처리를 위한 메서드 인식 재시도(Method-aware retries)의 필요성
16개의 샌드박스(Sandbox) 및 컴퓨트(Compute) SDK에 대한 소스 레벨의 분석과 그 과정에서 도출된 설계 결정 사항들: 메서드 인식 재시도(method-aware retries), 메타데이터를 포함한 타입화된 에러(typed errors), 런타임 의존성 제로(zero runtime dependencies).
createos-sandbox SDK의 코드를 한 줄이라도 쓰기 전에, 우리는 16개의 경쟁사 제품을 클론(clone)하여 그들의 소스 코드를 읽었습니다. 문서가 아니라, 소스 코드 말입니다. E2B, Daytona, Modal, Cloudflare, CodeSandbox, Vercel, Morph, Islo, Blaxel, Superserve, Alibaba의 OpenSandbox, Beam, Tensorlake 및 기타 몇몇 제품들이 포함되었습니다.
이 조사는 880줄 분량의 내부 문서로 이어졌습니다. SDK를 형성하는 결정 사항들은 README가 아니라 전송 계층(transport layer)에 존재합니다. 우리가 무엇을 복사했고, 무엇을 버렸으며, 그 이유는 다음과 같습니다:
모두가 올바르게 구현하고 있는 부분들
거의 모든 SDK에서 공통적으로 나타나는 몇 가지 패턴이 있었으며, 이들은 그 자리를 차지할 만한 가치가 있었습니다.
팩토리(factory)로부터 반환되는 상태 유지 핸들(stateful handle). createSandbox()를 호출하면 자신의 ID를 알고 있으며 이를 통해 동작할 수 있는 객체를 반환받습니다. E2B, Daytona, Modal, CodeSandbox 모두 이 방식을 채택하고 있습니다. 우리도 마찬가지입니다:
import { CreateosSandboxClient } from "@nodeops-createos/sandbox";
const client = new CreateosSandboxClient();
...
5개가 아닌 하나의 네임스페이스(namespace). Daytona는 핸들 아래에 .fs, .git, .process, .computerUse, .codeInterpreter를 중첩시킵니다. Cloudflare는 평탄한(flat) 40개의 메서드 인터페이스를 노출합니다. ComputeSDK는 .filesystem을 제외한 모든 것을 평탄하게 유지하며, 이러한 절제는 에디터에서 가독성을 높여줍니다. 우리는 하나의 .files 네임스페이스를 유지하고 나머지는 핸들에 평탄하게 두었습니다. 제어 평면(control plane)에 git 엔드포인트가 없다면, SDK에도 .git은 없습니다.
환경 변수(Environment-variable) 설정. CREATEOS_SANDBOX_API_KEY 및 CREATEOS_SANDBOX_BASE_URL을 사용하여 스크립트 실행 시 별도의 설정이 필요 없도록 합니다. 이는 표준이며, 모든 훌륭한 SDK가 수행하는 방식입니다.
이러한 요소들은 우리를 차별화해주지 않습니다. 진짜 격차는 한 단계 아래인 각 SDK가 실패(failure)를 처리하는 방식에서 나타났습니다.
재시도(Retries), 그리고 대부분의 SDK가 이를 틀리는 이유
샌드박스 SDK는 일시적인 503 에러로 부하를 조절하는 마이크로VM(microVM) 스케줄러와 통신하므로 반드시 재시도를 해야 합니다. 하지만 대부분은 그렇지 않습니다:
- E2B: 재시도가 전혀 없습니다.
- Daytona:
axios-retry를 의존성(dependency)으로 포함하고 있지만, 실제로 연결하여 사용하지는 않습니다. - ComputeSDK, Morph, Beam, OpenSandbox, OpenComputer: 재시도 레이어(retry layer)가 없습니다.
- CodeSandbox: 고정 지연(fixed-delay) 방식을 사용하며, 백오프(backoff)나 지터(jitter)가 없습니다.
- Cloudflare:
503에러에 대해서만 재시도하며, 지터가 없고 네트워크 에러(network-error)에 대한 재시도는 하지 않습니다.
Vercel과 Modal은 예외입니다. 두 서비스 모두 지수 백오프(exponential backoff), 지터(jitter), 그리고 Retry-After 헤더를 준수하며 훌륭하게 구현되어 있습니다. 저희는 이를 모방한 뒤, 그들이 명확하게 분리하지 못한 한 가지를 추가했습니다. 바로 재시도 정책이 메서드(method)를 인식한다는 점입니다.
재시도가 안전한지를 결정하는 질문은 다음과 같습니다: 만약 서버가 이미 이 요청을 처리했다면, 다시 보내는 것이 해를 끼치는가?
반복적인 GET 또는 DELETE는 괜찮습니다. 하지만 반복적인 POST는 두 번째 샌드박스를 생성하거나 대역폭을 이중으로 청구할 수 있습니다. 따라서 멱등(idempotent) 메서드와 비멱등(non-idempotent) 메서드에는 서로 다른 규칙이 적용됩니다:
| 트리거 | GET HEAD PUT DELETE |
|---|---|
| 네트워크 에러 (DNS, reset, refused) | 재시도함 |
| ... | ... |
| 트리거 | POST PATCH |
| --- | --- |
| 네트워크 에러 (DNS, reset, refused) | 재시도하지 않음 |
POST는 서버가 핸들러(handler)에 닿기 전에 요청을 거부했음이 증명되는 두 가지 상태인 429와 503에서만 재시도합니다. POST 요청의 그 외 모든 실패는 즉시 타입화된 에러(typed error)로 나타나며, 사용자의 코드가 직접 결정할 수 있게 합니다. 스트리밍(Streaming) 요청은 절대 재시도되지 않는데, 이는 절반만 소비된 NDJSON 스트림은 안전하게 다시 재생할 수 있는 위치(replay position)가 없기 때문입니다.
백오프(Backoff) 공식은 min(base × 2^attempt + random·base, cap)입니다. 여기서 핵심은 지터(jitter) 항입니다. 호스트가 재시작되어 200개의 클라이언트가 모두 동일한 503 에러를 마주할 때, 순수한 지수 백오프(pure exponential backoff)는 클라이언트들이 동기화된 파도처럼 한꺼번에 재시도하게 만듭니다. 무작위 오프셋(random offset)은 이들을 분산시킵니다.
가르침을 주는 타입화된 에러 (Typed errors)
두 번째 격차는 에러 모델(error models)입니다. ComputeSDK, Morph, Beam, OpenSandbox는 문자열로 변환된 본문과 함께 new Error("HTTP " + status)를 던집니다. 실패 상황에 따라 분기 처리를 하려면 메시지 문자열을 파싱해야 하는데, 이는 서버가 문구를 바꾸는 순간 바로 깨져버립니다.
E2B, Daytona, 그리고 Cloudflare는 이를 제대로 수행하고 있으며, 그중에서도 E2B의 방식이 가장 마음에 들었습니다. 예를 들어 502 에러 메시지는 _'샌드박스 타임아웃(sandbox timeout)일 가능성이 높으니, .setTimeout을 호출하세요'_라고 알려줍니다. 에러가 해결 방법을 가르쳐주는 것입니다.
우리는 문자열 파싱(string parsing) 없이 instanceof로 범위를 좁힐 수 있는 10단계 계층 구조(10-class hierarchy)를 출시했습니다:
try {
await sandbox.runCommand("ls", ["/"]);
} catch (err) {
...
모든 HTTP 에러는 산문 형태의 덩어리(prose blob) 대신 구조화된 필드(structured fields)를 포함합니다: statusCode, method, endpoint, 경로에서 파싱된 resourceId, requestId, 그리고 기계가 읽을 수 있는 code가 포함됩니다. 지원 티켓(support ticket)에 requestId를 인용하면 운영자가 $O(1)$ 시간 복잡도로 귀하의 정확한 호출을 찾아낼 수 있습니다. Modal은 좋은 재시도 엔진(retry engine)을 가지고 있지만, 에러에 code나 requestId가 포함되어 있지 않아 맥락을 놓치게 됩니다.
우리가 거절한 것들
좋은 아이디어를 복제하는 것은 쉽습니다. 하지만 거절하는 데에는 더 많은 논쟁이 필요했습니다. 우리가 거절한 세 가지는 다음과 같습니다:
gRPC 및 protobuf (Modal). Modal은 HTTP/2 기반의 gRPC를 사용하여 이번 조사에서 가장 뛰어난 재시도(retry) 구현을 보여줍니다. 하지만 또한 체크인된 코드 생성(codegen), 두 개의 ts-proto 패치 해킹, 그리고 무거운 의존성(dependencies)을 동반합니다. REST + NDJSON 제어 평면(control plane)을 위해 사용자에게 돌아가는 이득 없이 빌드 복잡성(build complexity)만 엄청나게 높이는 일입니다. 우리는 이를 통과시키지 않았습니다.
OpenAPI codegen (Vercel, Islo, Blaxel). Vercel의 SDK는 코드 생성(codegen)이 어떻게 확장되는지를 보여주는 가장 강력한 증거입니다. 40개 이상의 네임스페이스(namespaces)가 단 하나의 수동 편집 파일로 관리됩니다. 하지만 이는 12개의 작업이 아닌 40개 이상의 작업이 있을 때 비로소 효과를 발휘합니다. Blaxel의 생성된 빌드는 재생성할 때마다 11개의 sed 패치와 perl 재작성이 필요합니다. 우리의 인터페이스(surface)는 직접 작성하여 가독성을 유지할 수 있을 만큼 충분히 작습니다.
WebSocket 데이터 평면 (CodeSandbox, Daytona). 재개(resume)와 재연결(reconnect) 측면에서 우아합니다. 하지만 우리의 제어 평면(control plane)은 소켓 엔드포인트(socket endpoint)가 없는 REST + NDJSON 방식이므로, WS 레이어(WS layer)를 도입하는 것은 존재하지 않는 연결을 모델링하는 격이 됩니다.
그 결과, 런타임 의존성(runtime dependencies)은 제로이며, 각 모듈은 300줄 미만인 9개의 모듈로 구성되었습니다. Cloudflare의 sandbox.ts는 단일 6,295줄 파일입니다. 우리의 것은 오후 한나절이면 읽을 수 있는 수동 작성된 fetch 클라이언트입니다.
호출자가 에이전트(agent)일 때 이것이 중요한 이유
현재 샌드박스 (Sandbox) SDK로 향하는 트래픽의 절반은 사람이 아닌 코드를 작성하는 LLM (Large Language Model)으로부터 발생합니다. 이는 좋은 SDK가 호출자에게 제공해야 할 역할이 달라졌음을 의미합니다.
에이전트 (agent)는 502 에러를 읽고 ".setTimeout을 호출해야겠다"라고 직관적으로 판단할 수 없습니다. 에이전트는 타입 (types)과 구조화된 신호 (structured signals)를 기반으로 작동합니다. 안정적인 code 필드를 포함한 타입이 지정된 에러 (Typed errors)는 에이전트가 결정론적으로 분기 (branch)할 수 있는 근거를 제공합니다. 메서드 인지형 재시도 (Method-aware retries)는 재시도 폭풍 (retry storm) 중에 POST 요청을 대량으로 발사하는 에이전트가 중복된 VM (Virtual Machine)을 조용히 생성하지 않도록 보장합니다. 또한, 다른 에이전트가 생성한 샌드박스 내부에서 SDK가 실행될 때는 의존성 제로 (zero dependencies)가 더욱 중요해집니다.
우리는 또한 llms.txt와 번들 형태의 llms-full.txt를 함께 제공하여, 코딩 에이전트가 메서드 이름을 보고 추측하는 대신 전체 API 표면 (API surface)을 컨텍스트 (context)로 로드할 수 있도록 합니다.
직접 읽어보세요
이 SDK는 @nodeops-createos/sandbox입니다: TypeScript 기반이며, 런타임 의존성이 없고 (zero runtime dependencies), ESM을 지원하며, Node 20+, Bun, Deno, Cloudflare Workers 및 브라우저에서 실행됩니다. 문서는 Diátaxis 프레임워크를 따르며, 16개 SDK에 대한 전체 비교 내용은 리포지토리 (repo)에 포함되어 있습니다.
만약 여러분이 샌드박스나 컴퓨팅 SDK를 유지 관리하고 있다면, 경쟁사 제품을 몇 개 클론 (clone)하여 전송 계층 (transport layer)을 읽어보십시오. 이것은 여러분의 SDK에 무엇이 빠져 있는지 알아내는 가장 빠른 방법입니다.
Refs
Quickstart: https://nodeops.network/createos/docs/Sandbox/Quickstart
Sandbox web: https://createos.sh/app/sandbox
SDK: https://github.com/NodeOps-app/createos-sandbox-sdk
SDK Examples: https://github.com/NodeOps-app/createos-sandbox-sdk
CLI: https://github.com/NodeOps-app/createos-cli
Docs: https://nodeops.network/createos/docs/Sandbox/Overview
자체 인프라에서 Sandbox 실행하기: https://nodeops.network/createos/docs/Sandbox/Bring-Your-Own-Storage
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기