백엔드
주문 금액 계산 엔진 보수
Java 21·Spring Boot 3 주문 금액 계산 API의 할인 순서, 원 단위 반올림, 주문 단위 할인 배분, 배송비와 입력 검증 오류를 공개 계약을 유지한 채 보수하고 경계 테스트를 추가한다.
Task
성공 조건
- 공개 DTO·enum·JSON 필드·컨트롤러 경로와 빌드 설정을 유지하고, 계산 규칙은 서비스 또는 도메인 코드에 둔다.
- 상품·회원·쿠폰 할인을 정해진 순서와 대상에 적용하고, 금액을 부동소수점 없이 원 단위 버림으로 계산한다.
- 회원·쿠폰 할인을 최대 잔여법으로 정확히 배분하며, 동률은 입력 순서를 따르고 0원 대상도 안전하게 처리한다.
- 쿠폰 임계값·상한과 할인 후 상품 금액 기준의 기본·원격 배송비를 모든 경계에서 정확히 계산한다.
- null·공백·음수·잘못된 수량·미정의 enum·JSON 숫자 변환 및 산술 오버플로를 계약된 400 오류로 처리한다.
- 공통 프롬프트의 필수 시나리오와 행별 금액을 MockMvc로 검증하고, 격리된 작업 사본에서 실제 테스트 결과를 남긴다.
Common conditions
핵심 조건
- Java 21·Spring Boot 3의 기존 주문 금액 API 계약과 빌드 설정을 유지하면서 계산 서비스와 최소 오류 처리를 보수한다.
- 상품→회원→쿠폰 순서, BOOK 회원 할인 제외, 최대 잔여법 배분, 쿠폰 임계값·상한, 할인 후 배송비를 정수 연산으로 구현한다.
- 잘못된 입력과 모든 long 오버플로를 동일한 400 JSON으로 처리하고, 13개 필수 시나리오와 행별 배분을 MockMvc로 검증한다.
Model results
모델별 결과
정수·BigInteger 기반 계산과 최대 잔여법 배분, 통합 400 오류 처리를 4개 구현 파일과 1개 MockMvc 테스트 파일에 담은 보수안.
gpt-5.6-luna-high
구현 요약
- 주문 행별 상품 할인과 주문 단위 회원·쿠폰 할인을 요구된 순서로 계산하도록
OrderPricingService를 보수했습니다. - 회원 할인과 쿠폰 할인은
BigInteger기반의 최대 잔여법으로 비례 배분하고, 소수 부분이 같은 경우 입력 순서를 유지합니다. - 상품 할인율은 버림 처리하며, 패션 상품의 수량 조건을
3개 이상으로 적용했습니다. - 쿠폰 임계값과 배송비를 모든 선행 할인 적용 후 상품 금액 기준으로 계산했습니다.
Math.multiplyExact,Math.addExact,Math.subtractExact와 JSON 파싱 오류 처리를 사용해 개별 곱셈·합산·최종 계산 오버플로를 계약된 400 응답으로 변환했습니다.- null, 빈 주문, 공백 상품 ID, 음수 단가, 잘못된 수량, null 필드, 정의되지 않은 enum을
INVALID_PRICING_REQUEST오류로 처리했습니다.
변경 파일
verification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/service/OrderPricingService.javaverification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/service/InvalidPricingRequestException.javaverification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/api/PricingController.javaverification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/main/java/com/samebrief/pricing/api/PricingExceptionHandler.javaverification/work/12-order-pricing-engine/gpt-5.6-luna-high/src/test/java/com/samebrief/pricing/OrderPricingControllerTest.java
공개 DTO·enum·JSON 필드명·컨트롤러 경로·Gradle 설정은 변경하지 않았습니다.
검증
실행 명령:
./gradlew test
결과:
BUILD SUCCESSFUL
tests=10, failures=0, errors=0
구현 테스트에서는 상품 할인 버림, 패션 수량 경계, 회원 할인 대상 제외, 최대 잔여법 배분, 쿠폰 임계값·상한, 할인 후 배송비, 원격 배송비, 무료 배송 경계, 입력 오류와 곱셈·합산 오버플로를 검증했습니다.
트레이드오프
금액 비례 계산의 중간 곱셈이 long을 넘을 수 있으므로 표준 라이브러리의 BigInteger를 사용했습니다. 금액 도메인에 맞는 정확성을 확보하는 대신 단순한 long 연산보다 구현과 계산 비용이 조금 증가합니다.
Recommendation
이 과업의 추천 모델: GPT-5.6 Sol
세 구현 모두 같은 독립 채점 22개를 전부 통과해 기능 정확성은 동률이다. GPT-5.6 Sol은 제출물 안에 31개 MockMvc 테스트를 남겨 nullable 필드, 범위 밖 JSON 숫자, 세 종류 최종 산술 오버플로와 0원 행까지 가장 넓게 회귀 방지했고, 구현 변경 범위도 5개 파일로 유지해 종합 추천한다.
추천 근거 보기
판단 조건
- null·공백·음수·잘못된 수량·미정의 enum·JSON 숫자 변환 및 산술 오버플로를 계약된 400 오류로 처리한다.
- 공통 프롬프트의 필수 시나리오와 행별 금액을 MockMvc로 검증하고, 격리된 작업 사본에서 실제 테스트 결과를 남긴다.
반영한 평가 항목
정확성 · 완성도 · 실무성 · 가독성 · 효율성
잘 맞는 경우
- 주문 계산 규칙을 보수한 뒤 경계 회귀 테스트까지 넓게 남겨야 하는 백엔드 작업
- 부동소수점 없이 비례 배분과 long 오버플로를 함께 다뤄야 하는 금액 계산 코드
주의할 점
- 추천 우위는 독립 채점의 기능 차이가 아니라 자체 테스트 범위와 제출 완성도에서 나온다.
- 검증은 로컬 MockMvc 수준이며 실제 서버 배포, 성능, 동시 요청과 운영 관측성은 확인하지 않았다.
Prompt and environment
공통 프롬프트와 실행 환경
프롬프트 전문 보기
# 12. 주문 금액 계산 엔진 보수
## 과업
Java 21과 Spring Boot 3으로 작성된 주문 금액 계산 API의 기존 구현을 보수해 주세요. 현재 구현에는 할인 적용 순서, 원 단위 반올림, 주문 단위 할인 배분, 배송비 계산과 입력 검증 오류가 섞여 있습니다.
공개 API 계약은 변경하지 않고 모든 계산 규칙을 만족하도록 구현을 수정한 뒤, 경계 조건을 검증하는 자동화 테스트를 추가하세요.
## 비교 실행 격리 규칙
이 과업을 여러 Codex 모델에 맡겨 비교할 때는 아래 절차를 반드시 지킵니다. `<run-id>`는 해당 실행에 지정한 출력 파일명에서 `.md`를 뺀 값입니다. 예: `gpt-5.6-luna-high`.
### 실행 준비
1. 각 모델 실행을 시작하기 전에 새 대화나 새 세션을 열어 이전 모델의 컨텍스트를 완전히 비웁니다.
2. 편집자는 수정되지 않은 원본 하네스를 모델별 전용 작업 디렉터리에 복사합니다.
```bash
pricing_run_dir="verification/work/12-order-pricing-engine/<run-id>"
if [ -e "$pricing_run_dir" ]; then
echo "작업 디렉터리가 이미 존재합니다: $pricing_run_dir"
exit 1
fi
mkdir -p "$pricing_run_dir"
rsync -a \
--exclude .gradle \
--exclude build \
verification/harnesses/spring-boot-3-java21-pricing/ \
"$pricing_run_dir/"
```
3. 기존 작업 디렉터리를 재사용하거나 그 위에 다시 복사하지 않습니다. 같은 모델을 재실행할 때도 새 `<run-id>`를 사용합니다.
4. 모델의 작업 디렉터리는 `verification/work/12-order-pricing-engine/<run-id>`로 고정합니다.
5. 모든 모델에 이 `brief.md` 전문과 자신의 `<run-id>`만 전달합니다. 프롬프트, reasoning effort와 사용 가능한 도구 조건도 모델마다 같게 유지합니다.
### 모델이 지켜야 할 범위
- `verification/harnesses/spring-boot-3-java21-pricing` 원본을 직접 수정하지 않습니다.
- 자신에게 지정된 `verification/work/12-order-pricing-engine/<run-id>` 안의 코드와 테스트만 수정합니다.
- 다른 모델의 작업 디렉터리, `content/comparisons/12-order-pricing-engine/outputs`, 게시용 `index.md`, 편집자용 채점 자료와 이전 모델의 응답을 열람하거나 참고하지 않습니다.
- 다른 모델이 만든 코드, 테스트, 설명을 복사하거나 이어서 작업하지 않습니다.
- 구현과 자체 테스트가 끝나기 전에는 결과 Markdown을 작성하지 않습니다.
- 자체 작업 사본에서 `./gradlew test`를 실제로 실행한 뒤, 변경 파일·테스트 결과·발견한 오류·해결 방식과 트레이드오프를 마지막 응답에 정리합니다.
### 실행 완료 후
1. 편집자는 모델의 마지막 응답을 `content/comparisons/12-order-pricing-engine/outputs/<run-id>.md`에 저장합니다.
2. 모델별 작업 사본은 서로 덮어쓰지 않고 채점이 끝날 때까지 보존합니다.
3. 독립 채점은 모델 실행과 컨텍스트가 종료된 뒤 편집자가 동일한 채점 스위트로 각각 수행합니다.
4. 다음 모델을 실행하기 전에 다시 새 컨텍스트를 시작합니다.
5. 세 결과와 독립 채점 기록이 모두 준비된 뒤에만 게시용 `index.md`를 만듭니다.
## 실행 환경
- Java 21
- Gradle 8.x
- Spring Boot 3.x, Spring Web MVC
- JUnit 5, MockMvc
- 금액 단위는 대한민국 원
- 외부 데이터베이스, 메시지 브로커, 캐시, 파일 시스템과 외부 API는 사용하지 않음
- 실행 명령은 `./gradlew test`
## 제공되는 시작 코드 계약
시작 저장소에는 실행 가능한 Gradle 애플리케이션과 아래 구성요소가 들어 있습니다.
```text
com.samebrief.pricing
├── PricingApplication.java
├── api
│ ├── PricingController.java
│ ├── PricingErrorResponse.java
│ ├── PricingRequest.java
│ └── PricingResponse.java
├── domain
│ ├── Category.java
│ ├── CouponType.java
│ ├── MemberLevel.java
│ └── Region.java
└── service
└── OrderPricingService.java
```
요청·응답 DTO와 enum의 공개 필드, JSON 이름, 컨트롤러 경로는 기존 API 계약이므로 변경할 수 없습니다. `PricingErrorResponse`는 오류 응답 형식을 고정하기 위해 제공되지만 시작 코드에서는 사용되지 않습니다. 계산 서비스와 내부 구현은 자유롭게 고칠 수 있고, 필요한 내부 helper와 테스트를 추가할 수 있습니다.
`build.gradle.kts`, Gradle Wrapper와 애플리케이션 시작 클래스는 변경하지 마세요. 새로운 외부 의존성도 추가하지 마세요. JDK 표준 라이브러리는 제한 없이 사용할 수 있습니다.
## API 계약
`POST /api/v1/orders/price`는 다음 형식의 JSON을 받습니다.
```json
{
"lines": [
{
"productId": "food-1",
"category": "FOOD",
"unitPrice": 10000,
"quantity": 2
}
],
"memberLevel": "REGULAR",
"couponType": "NONE",
"region": "LOCAL"
}
```
정상 응답은 HTTP 200과 다음 형식의 JSON을 반환합니다.
```json
{
"itemSubtotal": 20000,
"itemDiscount": 0,
"memberDiscount": 0,
"couponDiscount": 0,
"merchandiseTotal": 20000,
"shippingFee": 3000,
"grandTotal": 23000,
"lines": [
{
"productId": "food-1",
"grossAmount": 20000,
"itemDiscount": 0,
"memberDiscount": 0,
"couponDiscount": 0,
"finalAmount": 20000
}
]
}
```
모든 응답 금액은 소수점 없는 0 이상의 정수여야 합니다.
## 계산 규칙
아래 순서를 바꾸지 않습니다.
### 1. 상품 합계
- 각 주문 행의 `grossAmount`는 `unitPrice × quantity`입니다.
- `itemSubtotal`은 모든 `grossAmount`의 합입니다.
- 같은 `productId`가 여러 행에 있어도 합치지 않고 입력 순서대로 각각 계산합니다.
### 2. 상품 할인
- `FOOD`: 상품 할인이 없습니다.
- `BOOK`: 수량과 관계없이 해당 행 `grossAmount`의 10%를 할인합니다.
- `FASHION`: 해당 행의 `quantity`가 3개 이상일 때만 `grossAmount`의 15%를 할인합니다.
- 각 행에서 할인액의 소수점 이하는 버립니다.
- `itemDiscount`는 각 행의 상품 할인 합계입니다.
### 3. 회원 할인
- `REGULAR`은 회원 할인이 없습니다.
- 회원 할인 대상은 `BOOK`이 아닌 행이고, 대상 금액은 각 행의 상품 할인 후 금액입니다.
- `GOLD`는 회원 할인 대상 금액 합계의 5%를 할인합니다.
- 주문 전체 회원 할인액의 소수점 이하는 한 번만 버립니다. 행별로 반올림하거나 버리지 않습니다.
- 계산된 회원 할인액을 대상 행의 상품 할인 후 금액에 비례해 배분합니다.
- 회원 할인 대상 행이 없거나 대상 금액 합계가 0원이면 회원 할인과 행별 배분액은 모두 0원입니다.
### 4. 쿠폰 할인
쿠폰은 상품 할인과 회원 할인을 모두 적용한 뒤 사용합니다.
- `NONE`: 할인이 없습니다.
- `FIXED_5000`: 쿠폰 적용 직전 상품 금액이 30,000원 이상이면 5,000원을 할인하고, 미만이면 할인하지 않습니다.
- `RATE_10`: 쿠폰 적용 직전 상품 금액이 50,000원 이상이면 10%를 할인합니다. 소수점 이하는 버리고 최대 할인액은 10,000원입니다.
- 쿠폰 할인은 남아 있는 상품 금액을 초과할 수 없습니다.
- 계산된 쿠폰 할인액을 모든 행의 회원 할인 후 금액에 비례해 배분합니다.
- 쿠폰 적용 직전 상품 금액이 0원이면 쿠폰 할인과 행별 배분액은 모두 0원입니다.
### 5. 주문 단위 할인의 행별 배분
회원 할인과 쿠폰 할인은 각각 아래 방식으로 배분합니다.
1. 각 대상 행에 `전체 할인액 × 행의 대상 금액 ÷ 모든 대상 행의 금액 합계`를 계산하고 소수점 이하를 버립니다.
2. 버림 때문에 남은 금액은 버리기 전 소수 부분이 큰 행부터 1원씩 배분합니다.
3. 소수 부분이 같으면 원래 요청에서 앞에 있는 행을 우선합니다.
4. 어느 행도 해당 단계 직전 금액보다 많은 할인을 받을 수 없습니다.
5. 행별 배분액의 합은 주문 전체 할인액과 정확히 같아야 합니다.
6. 대상 금액 합계가 0원이면 배분하지 않고 모든 행에 0원을 기록합니다. 0으로 나누어 서버 오류가 발생해서는 안 됩니다.
부동소수점 오차가 결과에 영향을 주지 않게 구현하세요.
### 6. 상품 결제액과 배송비
- 각 행의 `finalAmount`는 `grossAmount - itemDiscount - memberDiscount - couponDiscount`입니다.
- `merchandiseTotal`은 모든 행 `finalAmount`의 합입니다.
- 배송비는 모든 할인을 적용한 `merchandiseTotal`을 기준으로 계산합니다. `itemSubtotal`을 기준으로 하지 않습니다.
- `merchandiseTotal`이 50,000원 이상이면 기본 배송비는 0원, 미만이면 3,000원입니다.
- `REMOTE` 지역은 `merchandiseTotal`과 관계없이 5,000원의 추가 배송비가 붙습니다. `LOCAL` 지역에는 추가 배송비가 없습니다.
- 응답의 `shippingFee`는 기본 배송비와 추가 배송비의 합입니다. 기본 배송비가 0원인 `REMOTE` 주문의 `shippingFee`는 5,000원입니다.
- `grandTotal`은 `merchandiseTotal + shippingFee`입니다.
## 입력 오류
다음 입력은 HTTP 400과 아래 오류 형식으로 처리합니다.
```json
{
"code": "INVALID_PRICING_REQUEST",
"message": "주문 정보를 확인해 주세요."
}
```
- `lines`가 `null`이거나 비어 있음
- 주문 행, `productId`, `category`, `unitPrice`, `quantity`, `memberLevel`, `couponType`, `region` 중 하나가 `null`
- `productId`가 빈 문자열이거나 공백 문자열
- `unitPrice`가 음수
- `quantity`가 1 미만
- 곱셈, 합산 또는 최종 계산이 `long` 범위를 벗어남
- 요청 JSON의 enum 값이 정의되지 않은 문자열
`unitPrice`가 0인 행은 유효한 입력이며 `grossAmount` 0원으로 계산합니다. 0원 행이 섞여 있어도 할인 배분과 배송비 계산은 위 규칙 그대로 동작해야 합니다.
`long` 범위를 벗어나는 경우는 세 가지를 모두 포함합니다. 첫째는 `unitPrice × quantity`처럼 개별 곱셈이 넘치는 경우, 둘째는 여러 행의 금액을 합산할 때 넘치는 경우, 셋째는 요청 JSON의 숫자 자체가 `long`으로 변환되지 않는 경우입니다. 세 경우 모두 같은 400 응답으로 처리합니다.
잘못된 입력이 서버 오류나 음수 금액 응답으로 이어져서는 안 됩니다.
## 필수 검증 시나리오
JUnit 5와 MockMvc 테스트로 최소한 다음 시나리오를 검증하세요. 각 시나리오는 `memberLevel`·`couponType`·`region`을 표기한 순서로 고정합니다. 금액은 모두 `unitPrice` × `quantity` 형태로 적었습니다.
1. `REGULAR`·`NONE`·`LOCAL`에서 `FOOD` 10,000원 2개와 `BOOK` 15,000원 1개를 주문하면 `itemSubtotal` 35,000원, `itemDiscount` 1,500원, `merchandiseTotal` 33,500원, `shippingFee` 3,000원, `grandTotal` 36,500원이고 `BOOK` 행의 `finalAmount`는 13,500원이다.
2. `REGULAR`·`NONE`·`LOCAL`에서 `FASHION` 10,001원 3개를 주문하면 상품 할인은 버림이 적용된 4,500원이고 해당 행의 `finalAmount`는 25,503원이다.
3. `GOLD`·`NONE`·`LOCAL`에서 `FOOD` 10,001원 1개와 `FOOD` 10,010원 1개를 주문하면, 대상 금액 합계 20,011원의 5%인 1,000.55원을 주문 단위로 한 번만 버린 1,000원이 전체 회원 할인이다. 버림 소수 부분이 큰 첫 행이 잔여 1원을 받아 두 행에 각각 500원씩 배분되고, `finalAmount`는 9,501원과 9,510원, `grandTotal`은 22,011원이다.
4. `REGULAR`·`FIXED_5000`·`LOCAL`에서 각각 10,000원인 `FOOD` 세 행을 주문하면 쿠폰 할인 5,000원이 입력 순서대로 1,667원, 1,667원, 1,666원 배분되고 `grandTotal`은 28,000원이다.
5. `REGULAR`·`RATE_10`·`LOCAL`에서 각각 40,000원인 `FOOD` 세 행을 주문하면 쿠폰 적용 직전 금액이 120,000원이므로 쿠폰 할인은 상한이 적용된 10,000원이고, 3,334원·3,333원·3,333원으로 배분되며 `shippingFee`는 0원, `grandTotal`은 110,000원이다.
6. `REGULAR`·`FIXED_5000`·`LOCAL`에서 `FOOD` 52,000원 1개를 주문하면 `merchandiseTotal`이 47,000원이 되어 기본 배송비 3,000원이 붙고 `grandTotal`은 50,000원이다.
7. `REGULAR`·`NONE`·`REMOTE`에서 `FOOD` 60,000원 1개를 주문하면 기본 배송비는 0원이지만 추가 배송비가 붙어 `shippingFee` 5,000원, `grandTotal` 65,000원이다.
8. 회원 할인 대상 제외를 두 입력으로 각각 검증한다.
- `GOLD`·`NONE`·`LOCAL`에서 `BOOK` 10,000원 1개와 `FOOD` 20,000원 1개를 주문하면 전체 회원 할인 1,000원이 모두 `FOOD` 행에 배분되고 `BOOK` 행의 `memberDiscount`는 0원이며, `finalAmount`는 9,000원과 19,000원, `grandTotal`은 31,000원이다.
- `GOLD`·`NONE`·`LOCAL`에서 `BOOK` 10,000원 1개만 주문하면 회원 할인 대상 금액 합계가 0원이므로 `memberDiscount`는 0원이고 `grandTotal`은 12,000원이다.
9. `REGULAR`·`FIXED_5000`·`LOCAL`에서 `FOOD` 29,999원 1개를 주문하면 쿠폰 적용 직전 금액이 임계값 미만이므로 쿠폰 할인은 0원이고 `grandTotal`은 32,999원이다.
10. `REGULAR`·`RATE_10`·`LOCAL`에서 `FOOD` 49,999원 1개를 주문하면 쿠폰 적용 직전 금액이 임계값 미만이므로 쿠폰 할인은 0원이고 `grandTotal`은 52,999원이다.
11. `REGULAR`·`NONE`·`LOCAL`에서 `FASHION` 10,000원 2개를 주문하면 수량 조건을 만족하지 않아 상품 할인은 0원이고 `grandTotal`은 23,000원이다.
12. `REGULAR`·`NONE`·`LOCAL`에서 `FOOD` 25,000원 2개를 주문하면 `merchandiseTotal`이 정확히 50,000원이므로 `shippingFee`는 0원이고 `grandTotal`은 50,000원이다.
13. 다음 입력이 모두 계약된 400 응답을 반환한다.
- `lines`가 빈 배열
- `lines`가 `null`
- `productId`가 공백 문자열
- `unitPrice`가 `-1`
- `quantity`가 `0`
- `region`이 `null`
- `memberLevel`이 `"PLATINUM"`처럼 정의되지 않은 문자열
- `unitPrice` `9223372036854775807`과 `quantity` `2`의 곱셈 오버플로
- `unitPrice`가 각각 `9223372036854775807`인 두 행의 합산 오버플로
테스트는 최종 합계만 확인하지 말고 주문 전체 할인액, 행별 할인 배분액과 각 행의 최종 금액도 함께 검증해야 합니다. 위 시나리오 중 3·5·8은 행별 배분액을 확인하지 않으면 통과 여부를 판정할 수 없습니다.
## 구현 조건
- 금액 계산에 `double`과 `float`을 사용하지 않습니다.
- 공개 API 계약을 테스트에 맞춰 임의로 변경하지 않습니다.
- 검증을 우회하기 위한 입력별 하드코딩을 하지 않습니다.
- 요청 DTO를 계산 중 변경하거나 요청의 `lines` 순서를 바꾸지 않습니다.
- Controller에는 HTTP 요청·응답 변환만 두고 계산 규칙은 서비스 또는 도메인 코드에 둡니다.
- 계산 규칙마다 별도의 클래스 계층이나 디자인 패턴을 만들 필요는 없습니다. 현재 요구사항에 필요한 수준으로 구현합니다.
- 데이터베이스, 영속 엔티티, 비동기 처리와 동시성 제어는 이 과업 범위에 포함하지 않습니다.
## 제출물
- 시작 저장소의 코드를 직접 수정하고 테스트를 추가하세요.
- 변경 이유를 설명하기 전에 구현과 테스트를 완료하고 `./gradlew test`를 실제로 실행하세요.
- 마지막 응답에는 변경한 파일, 테스트 결과, 발견한 기존 오류, 선택한 해결 방식과 주요 트레이드오프를 간략히 정리하세요.
- 명세 밖 기능이나 외부 인프라는 추가하지 마세요.
## 최종 요청
주어진 코드베이스를 조사해 주문 금액 계산 API를 위 계약에 맞게 보수하고, 모든 필수 시나리오와 경계 조건을 로컬 자동화 테스트로 검증해 주세요.
- Java 21 · Gradle 8.14.3
- Spring Boot 3.5.16 · Spring Web MVC
- JUnit 5 · MockMvc · 대한민국 원 단위
- 고정 메모리 계산 · 외부 인프라 없음