Skip to content

자원 예산 계약 — DB 연결 수·timeout·worker 배치·Edge/Vercel 함수 한도·인입 크기 (이슈 #1332, B02)

한 문장 — 앱·Edge 함수·worker·관리자가 나눠 쓰는 자원의 현재 값을 어디서 왔는지와 함께 적은 표다. 실측한 값(Production 읽기 전용 프로브), 코드에 적힌 값, 플랫폼 문서의 값, 아직 달성 못 한 목표, 재지 않은 값을 구분한다. 재지 않은 동시 처리량은 보장하지 않는다. 상한을 넓히는 것은 오너 결정(전역 지침 §26 ③)이고, 코드 상수와 이 표를 같은 PR 에서 바꾼다.

  • 이슈: #1332 (계획 ID B02). 코드: src/react/services/appResourceContract.ts RELEASE_RESOURCE_CONTRACT(이 표와 같은 값). 증거: scripts/resources/evidence/production-2026-09-07.json(프로브 scripts/resources/probe-production.mjs, 2026-09-07T10:44:12Z). 대조: tests/react/resourceContract.test.mjs(코드 ↔ 증거 ↔ 이 문서).
  • 누가 쓰나: D08(통계 worker 의 lease/fence·timeout), I01(인입 parser 의 메모리·시간·파일 크기), R04(혼합 부하 합격 기준), D11(worker 라운드 목표). G04 성능 예산의 항목·배율은 그대로다 — 이 문서는 그 옆에 놓이는 자원 쪽 표다.
  • 다시 재는 법: node scripts/resources/probe-production.mjs(supabase CLI 로그인 토큰, 쓰기 없음) → 새 evidence 파일 → 코드 상수·이 표를 같이 고친다. 테스트가 세 곳의 값이 다르면 실패한다.

1. 읽는 법 — 유저 A 의 저장 하나로

유저 A 가 세트를 저장하면 앱은 authenticated 역할로 RPC 를 부른다. 그 문장은 8초 안에 끝나야 한다(역할별 statement_timeout). 저장이 끝나면 통계 잡이 큐에 들어가고, 매분 도는 cron 이 잡을 25개씩 집어 처리한다 — 이 라운드는 postgres 역할이라 120초 상한을 받는다. G04 실측에서 10년치 기록을 가진 사용자 한 명의 라운드가 127초였다. 즉 지금은 상한과 같은 크기이고, 그래서 "라운드 60초 이하" 는 목표(D04·D08 이 달성)이지 현재 값이 아니다. 연결은 컴퓨트 Micro 가 직접 60개(관리자 예약 3개 제외 57개)·풀러 200개를 주고, 프로브 시점에 앱 쪽 연결은 4개였다.

2. 상태의 뜻

status근거 형식
measuredProduction 에서 읽기 전용 프로브로 실측evidence JSON 의 같은 값
code레포 코드의 상수파일 경로·상수 이름
documented플랫폼 공식 문서의 한도. 우리가 재지 않았다인용 URL
target이번 릴리스 목표. 달성·검증 전달성 담당 ID
unmeasured값 없음(null). 보장하지 않는다누가 잴지

3. 계약 표

3-1. Postgres (Supabase 컴퓨트 Micro · 조직 플랜 pro · Postgres 17.6.1.127)

단위status근거소비자
computeVariantci_microvariantmeasuredManagement API billing/addons — Micro: 2 vCPU(공유)·1GBR04
directConnectionsMax60connectionsmeasuredpg_settings max_connections = 컴퓨트 Micro connections_directR04, D08
superuserReservedConnections3connectionsmeasuredpg_settings superuser_reserved_connectionsR04
directConnectionsUsable57connectionsmeasuredmax_connections − superuser_reserved_connections. 앱·Edge·cron·관리 도구가 나눠 쓴다R04, D08
poolerClientConnectionsMax200connectionsmeasured컴퓨트 Micro connections_pooler (pooler max_client_conn 은 null = 등급 기본값)R04
poolerModetransactionmodemeasuredconfig/database/pooler pool_mode (port 6543)R04, D08
poolerDefaultPoolSizeconnectionsunmeasuredpooler default_pool_size = null (등급 기본값, API 가 숫자를 주지 않음)R04
statementTimeoutAnonMs3000msmeasuredpg_db_role_setting anonR04
statementTimeoutAuthenticatedMs8000msmeasuredpg_db_role_setting authenticated — 앱 화면 RPC 의 실제 상한R04, D08, I01
statementTimeoutServiceRoleMs120000msmeasuredpg_db_role_setting service_role — Edge 함수·관리자 대리 경로D08, I01
statementTimeoutServerMs120000msmeasuredpg_settings statement_timeout — cron(postgres 역할)이 상속D08
lockTimeoutMs0msmeasuredpg_settings lock_timeout = 0 (무제한; authenticator 역할만 8s)D08
idleInTransactionSessionTimeoutMs0msmeasuredpg_settings = 0 (무제한). 열린 트랜잭션이 연결을 붙들어도 서버가 끊지 않는다D08, R04
workMemKb3500kBmeasuredpg_settings work_memR04
sharedBuffersMb256MBmeasuredpg_settings shared_buffersR04
effectiveCacheSizeMb768MBmeasuredpg_settings effective_cache_sizeR04
maxWorkerProcesses6processesmeasuredpg_settings max_worker_processes (pg_cron·autovacuum·parallel 공유)D08
maxParallelWorkers2workersmeasuredpg_settings max_parallel_workersD08
clientBackendsObserved4connectionsmeasuredpg_stat_activity client backend — 프로브 시점 1회 관측(postgrest·mgmt-api·postgres_exporter·기타). 상한이 아니다R04

3-2. 통계·정리 worker (pg_cron + Edge)

단위status근거소비자
statsRefreshCronSchedule* * * * *cronmeasuredcron.job lift-guild-stats-refreshD08, R04
statsRefreshCronBatch25jobs/runmeasuredrun_user_exercise_stats_refresh_cron(25)D08, R04
statsBackfillScanBatch50owners/runmeasuredbarbelic-stats-backfill-scan 매시 0분, run_user_exercise_stats_backfill_scan_cron(50)D08
prOverviewRolloverBatch10owners/runmeasuredlift-guild-pr-overview-snapshot-rollover 매분, run_user_pr_overview_snapshot_rollover(10)D08
statsEdgeProcessLimit10jobs/callcodesupabase/functions/stats-process-refresh-jobs/index.ts body.limit 기본 10, 최대 100D08
statsEdgeEnqueueLimit50owners/callcode같은 파일 enqueue_limit 기본 50, 최대 500D08
wodupImportEdgeBatchLimit5batches/callcodesupabase/functions/wodup-process-import-jobs/index.ts limit 1..5 (기본 1)I01
workerRoundStatementTimeoutMs120000msmeasuredcron 잡은 postgres 역할 → 서버 statement_timeout 120s 상속. G04 실측 10년 프로필 라운드 127,617ms — 이미 이 상한 크기D08
workerRoundTargetMs60000mstarget다음 분의 cron 과 겹치지 않도록 60s 이하 — D04(영향 범위만 재계산)·D08(lease/fence)이 달성. 현재 값이 아니다D08, D11
workerConcurrencyworkersunmeasuredcron 잡 겹침·Edge 함수 동시 호출 수는 재지 않았다 — D08 이 lease/fence 로 정하고 R04 가 부하에서 잰다D08, R04

3-3. Supabase Edge Functions (Deno) — 문서 인용

단위status근거소비자
memoryMb256MBdocumentedhttps://supabase.com/docs/guides/functions/limits Maximum MemoryI01, D08
wallClockMs400000msdocumented같은 문서 Paid plans 400s (조직 플랜 pro 실측)I01, D08
cpuTimeMs2000msdocumented같은 문서 Maximum CPU Time 2s — parse 같은 CPU 작업은 2초 안에I01, D08
requestIdleTimeoutMs150000msdocumented같은 문서 Request idle timeoutI01
bundleSizeMb20MBdocumented같은 문서 Function size (local bundling)B02
requestBodyBytesbytesunmeasured문서에 명시 없음I01

3-4. Vercel Functions (api/**) — 문서 인용, Hobby

단위status근거소비자
maxDurationMs300000msdocumentedhttps://vercel.com/docs/functions/limitations Fluid compute Hobby 300s 기본=최대 (프로젝트 생성 2026-05, Fluid 기본; 대시보드 미확인)R04
memoryMb2048MBdocumented같은 문서 Hobby 2GB / 1 vCPUR04
requestBodyBytes4500000bytesdocumented같은 문서 request/response body 4.5MB (413)I01, R04
adminRequestBodyBytes4096bytescodeapi/auth/_shared.js BODY_LIMIT_BYTES(readJsonBody) — 관리자·계정 API 공용, 초과 시 413 뒤 연결 종료(#1331)R04
testAdminRequestBodyBytes4096bytescodeapi/auth/test-admin/session.js — 같은 readJsonBody 기본 상한(#1331 뒤 별도 2048 상한 없음)R04
fileDescriptors1024descriptorsdocumented같은 문서 — 동시 실행이 공유R04

3-5. 클라이언트 인입 파일 (브라우저 parse)

단위status근거소비자
motraFileBytes31457280bytescodefeatures/import/importContract.ts IMPORT_LIMITS.motra.maxFileBytes (30MiB; 관리자 파서 사본은 Barbelic-docs admin)I01
inbodyFileBytes20971520bytescode같은 파일 IMPORT_LIMITS.inbody.maxFileBytes (20MiB)I01
wodupJsonlMaxLines10000linescode같은 파일 IMPORT_LIMITS.wodup.maxSampleLinesI01
parseMemoryBytes54945496bytesmeasuredI01(#1415) 2026-09-10 scripts/performance/import/wodup-large-file.mjs(npm run perf:import -- --sessions 10000): 10,000세션·24.3MB 파일(표본 1만 줄 한도에 닿는 대표 큰 파일)의 브라우저 사전검사 동안 힙 최고점 증가, Node 24 win32 3회 최댓값. 완료 뒤 남는 양 0.2MB. 2,000세션(4.9MB)은 12.5MB. 표본 한도(32MB·1만 줄)가 상한I01, R04
preflightMaxSyncBlockMs47msmeasured같은 계측 — 500줄마다 양보하는 사전검사가 이벤트 루프를 한 번에 멈춘 최대 시간(24.3MB 표본 문자열을 줄로 쪼개는 한 번이 가장 길다). 전체 사전검사 0.29~0.33초. 브라우저 실측 아님(Node)I01, R04
workerNormalizeHeapPeakBytes225966552bytesmeasured같은 계측 — worker 정규화(normalizeWodupJsonl, 동기 0.92~0.94초) 동안 힙 최고점 증가; 산출 81MB·세트 12만. Edge 함수 메모리 256MB 의 88%(CPU 0.94초는 2초 한도 안). 2,000세션은 52MB·0.2초. 파일 전체를 한 문자열로 들어 세션 수에 비례 — 줄 단위 스트리밍은 D09 runtime 과 함께 후속I01, D09, R04

4. 인계 — 누가 어느 값을 읽나

담당읽는 값
D08통계 worker 의 lease/fence·timeout 설계: statementTimeoutServiceRoleMs·statementTimeoutServerMs·workerRoundStatementTimeoutMs·workerRoundTargetMs·statsRefreshCronBatch·maxWorkerProcesses·idleInTransactionSessionTimeoutMs
I01인입 parser 의 자원 사용 계약: edgeFunctions.cpuTimeMs·memoryMb·wallClockMs·clientImport.*·vercelFunctions.requestBodyBytes·wodupImportEdgeBatchLimit
R04혼합 부하 합격: directConnectionsUsable·poolerClientConnectionsMax·statementTimeoutAuthenticatedMs·vercelFunctions.*·clientBackendsObserved(기준 관측)
D11통계 세대 개선의 목표: workerRoundTargetMs

5. 이 표에서 하지 않은 것 (숫자로 쓰지 않은 것)

  • 동시 처리량(초당 저장 수, 동시 사용자 수)은 재지 않았다 — R04 의 혼합 부하 시험이 G04 절차로 잰다. 여기 있는 연결 수·timeout 은 그 시험이 넘지 말아야 할 경계이지 처리량 보장이 아니다.
  • Vercel 프로젝트가 실제로 Fluid compute 인지, 요금제가 여전히 Hobby 인지는 대시보드에서만 보인다(오너 손 목록). 표의 값은 그 전제로 인용했다.
  • Edge 함수의 요청 본문 한도는 문서에 없다. Wodup 인입은 파일을 Storage 에 올리고 Edge 는 URL 만 받으므로 지금 경로에서는 걸리지 않는다.
  • G04 예산의 상한은 넓히지 않았다. workerRoundTargetMs 는 그 예산보다 낮은 목표다.