한도 안에서 작업하기
한도 안에서 작업하기 (Work within the limits)
계정 한도와 일시적 용량 거절 때문에 무한 재시도로 이어지지 않게 작업을 제한하는 방법을 알아볼게요.
출처: 문서
본문
계정 한도와 일시적 용량 거절이 무한 재시도로 바뀌지 않도록 작업을 제한해요.
인증된 클라이언트를 사용해요. 계정 한도는 SDK 설정과 별개라서, 클라이언트를 바꿔도 추가 용량을 주지 않아요.
컴퓨트 크기 선택하기 (Choose a compute size)
클라우드 샌드박스는 고정된 CPU/메모리 쌍을 과금 형태(billing shape)로 사용해요:
| 크기 (Size) | vCPUs | 메모리 (Memory) | SDK 메모리 값 (MiB) |
|---|---|---|---|
| Micro | 1 | 2 GiB | 2048 |
| Small | 2 | 4 GiB | 4096 |
| Medium | 4 | 8 GiB | 8192 |
| Large | 8 | 16 GiB | 16384 |
| XL | 16 | 32 GiB | 32768 |
이름으로 크기를 선택해요. 예를 들어 resources: 'small'. 지원되는 이름은 micro, small, medium, large, xl이에요. 리소스를 생략하면 키트 실행이 기본적으로 Small을 사용해요. 명시적 설정이 우선해요.
CPU와 메모리를 둘 다 직접 제공할 수도 있어요. 지원되는 쌍을 사용하세요. 클라우드 샌드박스에서 1 CPU + 1024 MiB는 지원되지 않아요. 저장된 이미지는 자체 리소스를 제공하므로, 그 이미지로 만들 때는 덮어쓰지 마세요. 요청이 받아들여질지는 여러분의 계정과 사용 가능한 용량이 결정해요. 가격은 Docker 과금 약관을 확인하세요.
계정 할당량 이해하기 (Understand account quotas)
기본 한도는 동시 샌드박스 10개, 저장된 샌드박스 50개, 볼륨 100개, 시크릿 100개, 한 번에 준비 중인 이미지 3개예요. 이들은 계정 전역 기본값이지 사용자별 별도 허용량이 아니에요. 계정마다 한도가 다를 수 있으니, 대규모 워크로드를 구성하기 전에 Docker에 확인하세요.
일반 샌드박스를 멈추면 동시성 슬롯은 풀리지만 저장 사용량에는 남아요. 다시 시작하거나 재개하려면 또 슬롯이 필요해요. 타임아웃 시 자동 재시작하도록 설정된 always-on 샌드박스는 멈춘 상태에서도 동시성 예약을 유지해요. 더 이상 필요 없는 샌드박스는 삭제해 저장 사용량을 해제하세요.
기존 샌드박스 세기 (Count existing sandboxes)
모든 샌드박스 페이지를 순회하며 자신에게 보이는 리소스를 세어요. 이것은 애플리케이션에 사용량 관찰을 줄 뿐, 예약이나 권위 있는 할당량 잔액은 아니에요. 같은 계정의 다른 사용자가 여러분의 목록에 안 보이는 리소스를 보유할 수 있어요.
다른 호출자가 그 직후 샌드박스를 만들 수도 있어요. 계획한 한도 아래로 카운트가 보여도 create 응답을 최종 결정으로 다루세요.
TypeScript
let count = 0;
for await (const sandbox of client.all({ pageSize })) {
if (sandbox.name) count++;
}
return count;
전체 TypeScript 예시: limits/budget.ts
import type { Sandboxes } from '@docker/sandboxes';
export async function countSandboxes(client: Sandboxes, pageSize: number) {
let count = 0;
for await (const sandbox of client.all({ pageSize })) {
if (sandbox.name) count++;
}
return count;
}
거절 후 백오프하기 (Back off after a refusal)
제공된 서버의 재시도 지연을 사용하고, 같은 create 요청에 같은 멱등성 키(idempotency key)를 유지해요. 시도 횟수를 제한하고 전체 작업에 데드라인을 두세요.
예시는 재시도 루프를 직접 소유하고, SDK 자동 재시도를 끄며, 전체 타임아웃을 설정해요. 두 재시도 정책을 겹쳐 쓰지 마세요 — 결합된 시도 횟수와 데드라인을 계산하지 않으면 안 돼요.
할당량 거절은 즉각적인 다음 요청보다 정리나 계정 변경이 필요할 수 있어요. 상한에서 멈추고, 실패를 보고하고, 받아들여진 샌드박스 정체성은 검사할 수 있게 보관하세요.
TypeScript
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'resourceExhausted' ||
attempt + 1 === attempts
)
throw error;
await pause(retryAfter(error, fallbackMs), undefined, { signal });
}
}
throw new Error('attempt count was validated');
전체 TypeScript 예시: limits/backoff.ts
import { setTimeout as pause } from 'node:timers/promises';
import {
RequestError,
type Sandbox,
type Sandboxes,
} from '@docker/sandboxes';
export async function createWithBackoff(
client: Sandboxes,
displayName: string,
attempts: number,
fallbackMs: number,
requestId: string,
): Promise<Sandbox> {
if (!Number.isInteger(attempts) || attempts < 1)
throw new Error(`attempts must be at least 1, got ${attempts}`);
const signal = AbortSignal.timeout(300000);
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.kits.launch(
'shell',
{ displayName, resources: { cpus: 2, memoryMib: 4096 } },
{
idempotencyKey: requestId,
timeoutMs: 300_000,
signal,
maxRetries: 0,
},
);
} catch (error) {
if (
!(error instanceof RequestError) ||
error.raw.code !== 'resourceExhausted' ||
attempt + 1 === attempts
)
throw error;
await pause(retryAfter(error, fallbackMs), undefined, { signal });
}
}
throw new Error('attempt count was validated');
}
export function retryAfter(
error: RequestError,
fallbackMs: number,
): number {
const header = error.retryAfter?.trim();
if (header) {
const delay = /^\d+(?:\.\d+)?$/.test(header)
? Number(header) * 1000
: /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?\s/.test(header)
? Math.max(0, Date.parse(header) - Date.now())
: NaN;
if (Number.isFinite(delay) && delay >= 0) return delay;
}
return error.decoded.retryDelayMs ?? fallbackMs;
}
더 알아보기 (Learn more)
전체 레시피 목록은 Browse all recipes 에서 확인할 수 있어요.