diff --git a/app/gateway-api/src/orchestrator/buildRunner.ts b/app/gateway-api/src/orchestrator/buildRunner.ts index c936832d..43159c5b 100644 --- a/app/gateway-api/src/orchestrator/buildRunner.ts +++ b/app/gateway-api/src/orchestrator/buildRunner.ts @@ -26,7 +26,17 @@ export interface BuildRunner { } export const MAX_BUILD_OUTPUT_CHARS = 64 * 1024; -export const RELEASE_TURBO_CONCURRENCY = 2; +export const DEFAULT_RELEASE_TURBO_CONCURRENCY = 1; + +export const resolveReleaseTurboConcurrency = (env?: Record): number => { + const configured = env?.RELEASE_TURBO_CONCURRENCY?.trim(); + if (!configured) return DEFAULT_RELEASE_TURBO_CONCURRENCY; + const parsed = Number(configured); + if (!Number.isInteger(parsed) || parsed <= 0) { + throw new Error('RELEASE_TURBO_CONCURRENCY must be a positive integer.'); + } + return parsed; +}; export const resolveReleaseTurboCacheDir = (cacheAnchorRoot: string, env?: Record): string => { const configured = env?.TURBO_CACHE_DIR?.trim(); @@ -48,7 +58,7 @@ export const buildTurboReleaseCommand = ( 'build', ...packageNames.map((packageName) => `--filter=${packageName}`), `--cache-dir=${resolveReleaseTurboCacheDir(cacheAnchorRoot, env)}`, - `--concurrency=${RELEASE_TURBO_CONCURRENCY}`, + `--concurrency=${resolveReleaseTurboConcurrency(env)}`, '--ui=stream', '--output-logs=new-only', ], diff --git a/app/gateway-api/test/buildRunner.test.ts b/app/gateway-api/test/buildRunner.test.ts index b0ec6cb7..8094e4db 100644 --- a/app/gateway-api/test/buildRunner.test.ts +++ b/app/gateway-api/test/buildRunner.test.ts @@ -7,6 +7,7 @@ import { MAX_BUILD_OUTPUT_CHARS, PnpmBuildRunner, resolveReleaseTurboCacheDir, + resolveReleaseTurboConcurrency, } from '../src/orchestrator/buildRunner.js'; describe('Turbo release build plan', () => { @@ -24,6 +25,14 @@ describe('Turbo release build plan', () => { ).toBe('/srv/core/repository/.cache/turbo'); }); + it('defaults to one worker for bounded runtimes and accepts a larger-host override', () => { + expect(resolveReleaseTurboConcurrency()).toBe(1); + expect(resolveReleaseTurboConcurrency({ RELEASE_TURBO_CONCURRENCY: '2' })).toBe(2); + expect(() => resolveReleaseTurboConcurrency({ RELEASE_TURBO_CONCURRENCY: '0' })).toThrow( + 'RELEASE_TURBO_CONCURRENCY must be a positive integer.' + ); + }); + it('uses a bounded streaming Turbo build for the selected packages', () => { expect( buildTurboReleaseCommand( @@ -41,7 +50,7 @@ describe('Turbo release build plan', () => { 'build', '--filter=@sammo-ts/game-api', '--cache-dir=/srv/core/repository/.turbo/release-cache', - '--concurrency=2', + '--concurrency=1', '--ui=stream', '--output-logs=new-only', ], diff --git a/app/gateway-api/test/orchestratorPlan.test.ts b/app/gateway-api/test/orchestratorPlan.test.ts index 3c5609a2..1116ad75 100644 --- a/app/gateway-api/test/orchestratorPlan.test.ts +++ b/app/gateway-api/test/orchestratorPlan.test.ts @@ -237,7 +237,7 @@ describe('buildWorkspaceCommands', () => { '--filter=@sammo-ts/game-api', '--filter=@sammo-ts/gateway-api', '--cache-dir=/srv/sammo/controller/.turbo/release-cache', - '--concurrency=2', + '--concurrency=1', '--ui=stream', '--output-logs=new-only', ], diff --git a/app/release-controller/README.md b/app/release-controller/README.md index 33523f5b..ec8bf9fd 100644 --- a/app/release-controller/README.md +++ b/app/release-controller/README.md @@ -32,6 +32,9 @@ Gateway process 환경에 전달하지 않습니다. 이 값이 frontend 정의 - `TURBO_CACHE_DIR`: 선택 사항인 공유 local cache 경로입니다. 없으면 원래 `RELEASE_CONTROLLER_WORKSPACE_ROOT/.turbo/release-cache`를 사용합니다. 상대 경로는 원래 workspace 기준으로 해석합니다. +- `RELEASE_TURBO_CONCURRENCY`: Turbo worker 수입니다. 기본값 1은 실행 중인 game/Gateway + process와 4 GiB runtime을 공유하는 production cold build의 OOM을 피합니다. 더 큰 + 격리 build host에서만 측정 후 2 이상으로 올립니다. 비밀값은 Git에서 제외된 환경 파일 또는 process 환경으로 전달해 주세요. `VITE_*`에는 공개 URL만 넣어 주세요. @@ -49,7 +52,7 @@ DEPLOY의 rollback이 frontend build가 없는 controller worktree를 이전 Gat ```sh pnpm install --frozen-lockfile -pnpm exec turbo run build --filter=@sammo-ts/release-controller --concurrency=2 --ui=stream +pnpm exec turbo run build --filter=@sammo-ts/release-controller --concurrency=1 --ui=stream pnpm --filter @sammo-ts/infra prisma:migrate:deploy:gateway pnpm --filter @sammo-ts/release-controller start ``` diff --git a/docs/release-operations.md b/docs/release-operations.md index 83348466..b3f16796 100644 --- a/docs/release-operations.md +++ b/docs/release-operations.md @@ -45,7 +45,9 @@ profile 범위 권한과 별개인 전역 `admin.releases.manage` 권한이 필 Node heap/Rayon 상한을 상속한 동일 toolchain으로 초기 Gateway와 profile worktree를 빌드하여 구형 Rolldown의 과도한 native thread 생성을 피합니다. - Profile, Gateway와 controller self-upgrade의 server package build는 Turbo DAG를 - 동시성 2로 실행합니다. 기본 local cache는 원래 Core checkout의 + 사용합니다. 실행 중인 game/Gateway process와 4 GiB runtime을 공유하는 기본 + 동시성은 1이며, 더 큰 격리 build host에서는 `RELEASE_TURBO_CONCURRENCY`를 측정 후 + 2 이상으로 올릴 수 있습니다. 기본 local cache는 원래 Core checkout의 `.turbo/release-cache`이므로 commit별 worktree가 달라도 재사용됩니다. 별도 persistent 경로가 필요하면 controller/orchestrator 환경에 `TURBO_CACHE_DIR`을 설정합니다. Cache는 재생성 가능한 build artifact이며 DB/Redis backup이 아닙니다.