diff --git a/backend/src/app.setup.spec.ts b/backend/src/app.setup.spec.ts index 16f8f7f..665239e 100644 --- a/backend/src/app.setup.spec.ts +++ b/backend/src/app.setup.spec.ts @@ -168,7 +168,12 @@ describe('JSON 본문의 모양 한도 (JSON_SHAPE_LIMITS — DTO 변환 · 검 }); describe('전역 DTO 검증 (createValidationPipe — stopAtFirstError)', () => { - /** 모양 한도 안의 가장 무거운 본문들 — 본문 파서 · 모양 확인을 지난 뒤 DTO 변환 · 검사까지 0.5초 안에 400 이다 */ + /** + * 모양 한도 안의 가장 무거운 본문들 — 본문 파서 · 모양 확인을 지난 뒤 DTO 변환 · 검사까지 해도 400 이 싸게 난다. + * CPU 시간(test/cpu-time.ts) — 지금 0.02~0.08초. 한도 2초는 부하를 견디는 값이다: 2026-10-28 CI 와 같은 Linux 컨테이너에서 + * 메모리가 모자라고 테스트가 함께 돌 때 0.59초까지 재졌다(평소의 7배 — 그때 한도 0.5초로 CI 가 실패했다) + */ + const DTO_CPU_BUDGET_MS = 2_000; const filtersWithManyUnknowns = { version: 'v', rules: Array.from({ length: 50 }, (_, index) => ({ @@ -230,7 +235,7 @@ describe('전역 DTO 검증 (createValidationPipe — stopAtFirstError)', () => { ids: Array.from({ length: 1_000 }, (_, index) => `m${index}`) }, ], ])( - '%s — 모양 확인을 지나도 DTO 검사가 0.5초 안에 400', + '%s — 모양 확인을 지나도 DTO 검사가 2초 안에 400 (CPU 시간)', async (_label, metatype, body) => { const text = JSON.stringify(body); const pipe = createValidationPipe(); @@ -242,7 +247,7 @@ describe('전역 DTO 검증 (createValidationPipe — stopAtFirstError)', () => await expect( pipe.transform(parsed, { type: 'body', metatype }), ).rejects.toBeInstanceOf(BadRequestException); - expect(cpuMs()).toBeLessThan(500); + expect(cpuMs()).toBeLessThan(DTO_CPU_BUDGET_MS); }, ); diff --git a/backend/src/app.setup.ts b/backend/src/app.setup.ts index c4a43f6..c39a7fa 100644 --- a/backend/src/app.setup.ts +++ b/backend/src/app.setup.ts @@ -42,7 +42,7 @@ export const SEND_MESSAGE_JSON_BODY_LIMIT = '4mb'; * 지금 계약의 어떤 본문보다 넉넉히 둔다: 가장 긴 배열은 받는 사람 · id 100(발송 본문의 첨부 목록 상한도 100), 가장 깊은 본문은 5단 * (필터 — 본문 › rules › 규칙 › conditions › 조건), 값이 가장 많은 본문은 필터 50개를 모두 채운 저장(약 1,600개). * 넘으면 400 VAL_001 — 이 한도를 넘는 계약을 새로 만들면 여기부터 올린다. - * 한도까지 채운 본문도 DTO 변환 · 검사가 0.5초 안에 끝난다 (app.setup.spec.ts 가 CPU 시간으로 본다) + * 한도까지 채운 본문도 DTO 변환 · 검사가 0.1초 안쪽에 끝난다 (app.setup.spec.ts 가 CPU 시간으로 본다 — 부하를 견디게 한도는 2초) */ export const JSON_SHAPE_LIMITS: JsonShapeLimits = { maxArrayLength: 1_000, diff --git a/backend/src/modules/mail/html/html-parse-budget.spec.ts b/backend/src/modules/mail/html/html-parse-budget.spec.ts index bb0ba61..42e5a9d 100644 --- a/backend/src/modules/mail/html/html-parse-budget.spec.ts +++ b/backend/src/modules/mail/html/html-parse-budget.spec.ts @@ -13,8 +13,9 @@ import { sanitizeOutgoingHtml } from './sanitize-outgoing-html'; /** * CPU 시간 한도 (벽시계가 아니다 — test/cpu-time.ts). 지금 검사는 2MB 를 끝까지 읽어도 CPU 0.3초 안쪽이고 공격 모양은 0.15초 안쪽에 멈춘다. * 막으려는 제곱 경로는 같은 공격 입력을 정화하는 것 — '' 2MB 에 172초다. + * 한도는 평소의 30배쯤 — CPU 시간도 부하에서는 몇 배로 는다(2026-10-28 CI 와 같은 Linux 컨테이너에서 평소의 4~7배, test/cpu-time.ts) */ -const GENEROUS_CPU_MS = 2_000; +const GENEROUS_CPU_MS = 10_000; /** 보내는 서식 본문 한도 (계약 2절 — DTO 가 막는다) */ const OUTGOING_MAX_CHARS = 500_000; diff --git a/backend/src/modules/mail/html/sanitize-mail-html.spec.ts b/backend/src/modules/mail/html/sanitize-mail-html.spec.ts index a3601d5..87c4a57 100644 --- a/backend/src/modules/mail/html/sanitize-mail-html.spec.ts +++ b/backend/src/modules/mail/html/sanitize-mail-html.spec.ts @@ -257,9 +257,10 @@ describe('sanitizeMailHtml', () => { const result = sanitizeMailHtml('x'.repeat(count)); expect(result.html).toBe('x'.repeat(count)); - // CPU 시간 (test/cpu-time.ts) — 지금 1.1~1.6초(부하 중에도). 예전 경로(결과를 이미지마다 다시 자름)는 27만 개(1.6MB)에 15초, 이 크기면 20초가 넘는다. - // 벽시계로 재던 때는 바쁜 PC 에서 5.9초·9.1초로 재져 실패했다 (2026-10-27) - expect(cpuMs()).toBeLessThan(5_000); + // CPU 시간 (test/cpu-time.ts) — 지금 1.1~1.6초. 예전 경로(결과를 이미지마다 다시 자름)는 27만 개(1.6MB)에 15초, 이 크기면 20초가 넘는다. + // 벽시계로 재던 때는 바쁜 PC 에서 5.9초·9.1초로 재져 실패했다 (2026-10-27). CPU 시간도 2026-10-28 CI 와 같은 Linux 컨테이너에서 + // 메모리가 모자라고 테스트가 함께 돌 때 5.75초까지 재져 한도 5초로 실패했다 — 그래서 평소의 10배 가까이, 예전 경로(20초+)보다는 아래로 둔다 + expect(cpuMs()).toBeLessThan(15_000); }); }); diff --git a/backend/test/cpu-time.ts b/backend/test/cpu-time.ts index 17b8095..d2ccaaf 100644 --- a/backend/test/cpu-time.ts +++ b/backend/test/cpu-time.ts @@ -6,7 +6,9 @@ * 전체 테스트 4번 중 2번이 실패했다. CPU 시간은 이 프로세스가 실제로 계산한 시간만 세므로 다른 작업이 많아도 거의 늘지 않는다 — * "계산량이 입력 길이에 비례하는가(예전의 제곱 경로가 돌아오지 않았는가)"를 보는 단언에 쓴다. * - * - 프로세스 전체(GC·JIT 스레드 포함)의 user + system 시간이다 — 한도는 지금 값보다 넉넉하게, 예전의 제곱 경로는 확실히 넘는 값으로 둔다. + * - 프로세스 전체(GC·JIT 스레드 포함)의 user + system 시간이다 — 한도는 지금 값의 10배쯤 넉넉하게, 예전의 제곱 경로는 확실히 넘는 값으로 둔다. + * CPU 시간도 늘지 않는 것은 아니다 — 2026-10-28 CI 와 같은 Linux 컨테이너(Docker Desktop)에서 메모리가 모자라고 테스트가 함께 돌 때 + * 평소의 4~7배로 재져, 여유가 3~6배뿐이던 한도 두 곳이 CI 를 실패시켰다(이미지 많은 정화 1.6초 → 5.75초, DTO 검사 0.08초 → 0.59초). * - 기다리는 시간(I/O·타이머)은 CPU 시간에 들어가지 않는다 — 그런 단언(예: 파일이 지워질 때까지 기다리기)에는 쓰지 않는다. * - Windows 에서는 CPU 시간이 스케줄러 틱(약 15.6ms) 단위로 는다 — 수십 ms 아래를 비교하는 데 쓰지 않는다. *