|
18 | 18 |
|
19 | 19 | `EndpointDetailPage`의 Auto Scaling Rules 테이블에서 조건을 항상 `<` 방향으로 표시한다. |
20 | 20 |
|
| 21 | +**Strawberry API (>=26.4.0)**: `comparator` 필드 없이 `minThreshold`/`maxThreshold`로 조건을 표현한다. |
| 22 | + |
| 23 | +| 저장된 상태 | 표시 형태 | |
| 24 | +|---|---| |
| 25 | +| `maxThreshold`만 설정 (상한) | `[metric_name] < [maxThreshold]` | |
| 26 | +| `minThreshold`만 설정 (하한) | `[minThreshold] < [metric_name]` | |
| 27 | +| 둘 다 설정 (범위) | `[minThreshold] < [metric_name] < [maxThreshold]` | |
| 28 | + |
| 29 | +**Graphene API (<26.4.0)**: `comparator` + 단일 `threshold`로 조건을 표현한다. |
| 30 | + |
21 | 31 | | 저장된 comparator | 표시 형태 | |
22 | 32 | |---|---| |
23 | 33 | | LESS_THAN | `[metric_name] < [threshold]` | |
24 | 34 | | GREATER_THAN | `[threshold] < [metric_name]` (반전) | |
25 | | -| 범위 (minThreshold + maxThreshold) | `[minThreshold] < [metric_name] < [maxThreshold]` | |
26 | 35 |
|
27 | | -에디터 모달의 비교 연산자 드롭다운은 `<` (LESS_THAN)과 `>` (GREATER_THAN) 두 가지만 표시한다. |
| 36 | +에디터 모달의 비교 연산자 드롭다운은 **Legacy 단일 모드에서만** 표시하며, `<` (LESS_THAN)과 `>` (GREATER_THAN) 두 가지만 제공한다. 스키마에는 `LESS_THAN_OR_EQUAL`, `GREATER_THAN_OR_EQUAL`도 존재하지만, UI에서는 사용하지 않는다. |
28 | 37 |
|
29 | 38 | #### 2. 조건 모드 선택 (단일/범위) |
30 | 39 |
|
31 | 40 | Auto Scaling Rule 에디터 모달에 Ant Design `Segmented` 컴포넌트를 추가하여 "단일" / "범위" 모드를 전환한다. |
32 | 41 |
|
33 | | -- **단일 모드** (기본): 비교 연산자(`<`, `>`) 하나와 threshold 값을 입력 |
34 | | -- **범위 모드**: `[하한값]` ~ `[상한값]` 범위를 지정 (`minThreshold`/`maxThreshold` 사용) |
| 42 | +- **단일 모드** (기본): "상한" 또는 "하한" 방향을 선택하고 threshold 값을 입력 |
| 43 | + - 상한 (`metric < threshold`): `maxThreshold`만 설정, `minThreshold`는 null |
| 44 | + - 하한 (`threshold < metric`): `minThreshold`만 설정, `maxThreshold`는 null |
| 45 | +- **범위 모드**: `[하한값]` ~ `[상한값]` 범위를 지정 (`minThreshold` + `maxThreshold` 동시 사용) |
35 | 46 | - 하한값 >= 상한값이면 validation 에러를 표시 |
36 | 47 |
|
| 48 | +> **참고**: Strawberry API (>=26.4.0) 전용. Legacy (<26.4.0)에서는 Graphene `comparator` + 단일 `threshold`를 그대로 사용하며, 범위 모드는 지원하지 않는다. |
| 49 | +
|
37 | 50 | #### 3. Prometheus Preset 기반 메트릭 선택 |
38 | 51 |
|
39 | 52 | - `baiClient.supports('prometheus-auto-scaling-rule')` (또는 >=26.4.0 대응 키)로 기능 게이팅 |
40 | | -- **>=26.4.0**: 새로운 metric source가 추가될 예정. Condition 영역에서 `prometheusQueryPresets` 기반 Select로 metric name을 선택할 수 있도록 함. Preset 선택 시 `queryTemplate`과 `timeWindow`가 자동 적용 |
41 | | -- **<26.4.0**: 기존 방식 유지 (metric source 선택 + metric name 수동 입력) |
42 | | -- **TODO(needs-backend)**: 새로운 metric source의 구체적인 enum 값과 동작은 백엔드 PR 머지 후 확정 |
| 53 | +- **>=26.4.0**: `AutoScalingMetricSource.PROMETHEUS`가 추가됨. Condition 영역에서 metric source로 `PROMETHEUS`를 선택하면, `prometheusQueryPresets` 기반 Select로 preset을 선택할 수 있도록 함. Preset 선택 시 `queryTemplate`과 `timeWindow`가 자동 적용되고, `prometheusQueryPresetId`가 Rule에 저장됨 |
| 54 | +- **<26.4.0**: 기존 방식 유지 (metric source `KERNEL`/`INFERENCE_FRAMEWORK` 선택 + metric name 수동 입력) |
| 55 | + |
| 56 | +##### Metric Source별 UI 분기 (>=26.4.0 전용) |
| 57 | + |
| 58 | +`<26.4.0`에서는 `KERNEL`/`INFERENCE_FRAMEWORK`만 표시되며, `PROMETHEUS`는 노출하지 않는다. |
| 59 | + |
| 60 | +| metricSource | Preset 선택 UI | prometheusQueryPresetId | metric name | |
| 61 | +|---|---|---|---| |
| 62 | +| `KERNEL` | 숨김 | `null` | AutoComplete (자동완성 목록: `cpu_util`, `mem`, `net_rx`, `net_tx` + 자유 입력 가능) | |
| 63 | +| `INFERENCE_FRAMEWORK` | 숨김 | `null` | AutoComplete (자동완성 목록 없음, 자유 입력) | |
| 64 | +| `PROMETHEUS` | 표시 (필수) | 필수 | preset의 `metricName`에서 자동 채움 (입력 불필요) | |
| 65 | + |
| 66 | +##### Preset 선택 UI 동작 |
| 67 | + |
| 68 | +1. metric source 드롭다운에서 `PROMETHEUS` 선택 시 Prometheus Query Preset Select가 추가로 노출 |
| 69 | +2. Preset 목록은 `prometheusQueryPresets` query로 로드하여 `name`을 드롭다운에 표시 |
| 70 | +3. Preset 선택 시: |
| 71 | + - `prometheusQueryPresetId`에 선택된 preset의 UUID 저장 (Relay global ID를 디코딩한 raw UUID) |
| 72 | + - `metricName`은 preset의 `metricName`에서 자동으로 채움 (사용자 입력 불필요) |
| 73 | + - `queryTemplate`을 읽기 전용으로 표시하여 사용자가 쿼리 내용 확인 가능 |
| 74 | + - `timeWindow`가 자동 적용 (preset의 `timeWindow`가 null이면 사용자 입력) |
| 75 | + |
| 76 | +##### Rule 목록에서 Prometheus preset 표시 |
| 77 | + |
| 78 | +`metricSource`가 `PROMETHEUS`인 rule은 목록에서 preset 이름을 표시한다. `prometheusQueryPresetId`로 `prometheusQueryPreset(id)` query를 호출하거나, preset 목록을 한 번 로드하여 클라이언트 사이드에서 매칭한다. |
| 79 | + |
| 80 | +##### Rule 수정 시 주의사항 |
| 81 | + |
| 82 | +- `updateAutoScalingRule`은 partial update — 변경할 필드만 전달 |
| 83 | +- `prometheusQueryPresetId: null`은 "미변경"(UNSET) 의미이며, 명시적으로 null로 설정할 수 없음 |
| 84 | +- `metricSource`를 `KERNEL`로 변경해도 `prometheusQueryPresetId`가 자동 클리어되지 않음 — UI에서 `KERNEL`/`INFERENCE_FRAMEWORK`일 때 preset 필드를 숨기는 것으로 처리 |
43 | 85 |
|
44 | 86 | ### 선택 (Nice to Have) |
45 | 87 |
|
46 | 88 | #### 쿼리 결과값 미리보기 |
47 | 89 |
|
48 | 90 | Prometheus Preset 모드(>=26.4.0)에서 메트릭 설정 form item의 `extra` 영역에 `prometheusQueryPresetResult` API 호출 결과를 표시한다. |
49 | 91 |
|
| 92 | +- `timeRange` 없이 instant query로 호출하여 현재 값 1개를 표시 |
50 | 93 | - 로딩 스피너 → 결과값 텍스트 + 새로고침 아이콘 버튼 |
51 | 94 | - `options` 파라미터(`ExecuteQueryDefinitionOptionsInput`)로 `filterLabels`/`groupLabels` 전달 |
| 95 | +- `groupLabels`는 빈 배열 `[]`이라도 반드시 전달해야 함 (생략 시 validation error) |
| 96 | +- `filterLabels`의 `key`는 preset의 `options.filterLabels`에 정의된 것만 허용됨 |
| 97 | +- range query (시계열 차트)는 이번 범위에 포함하지 않음 |
52 | 98 |
|
53 | 99 | ## 인수 조건 (Acceptance Criteria) |
54 | 100 |
|
| 101 | +**공통 (양쪽 모두)** |
55 | 102 | - [ ] Rules 목록 테이블에서 모든 조건이 `<` 방향으로 통일되어 표시된다 |
56 | | -- [ ] GREATER_THAN으로 저장된 Rule은 threshold와 metric_name이 교환되어 표시된다 |
57 | | -- [ ] 범위 Rule(minThreshold + maxThreshold)은 `[min] < [metric] < [max]` 형태로 표시된다 |
58 | | -- [ ] 에디터 모달의 비교 연산자 드롭다운에 `<`와 `>`만 표시된다 |
| 103 | + |
| 104 | +**Legacy (<26.4.0, Graphene)** |
| 105 | +- [ ] GREATER_THAN으로 저장된 Rule은 threshold와 metric_name이 교환되어 `[threshold] < [metric]`로 표시된다 |
| 106 | +- [ ] 에디터 모달에서 비교 연산자 드롭다운(`<`, `>`)과 단일 threshold 입력이 동작한다 |
| 107 | +- [ ] 기존 KERNEL/INFERENCE_FRAMEWORK metric source 수동 입력이 동작한다 |
| 108 | + |
| 109 | +**Strawberry (>=26.4.0)** |
59 | 110 | - [ ] 에디터 모달에서 Segmented로 "단일"/"범위" 모드를 전환할 수 있다 |
| 111 | +- [ ] 단일 모드에서 "상한"(`maxThreshold`만) 또는 "하한"(`minThreshold`만) 방향을 선택할 수 있다 |
| 112 | +- [ ] 범위 모드에서 `minThreshold` + `maxThreshold`를 동시에 입력하고, `[min] < [metric] < [max]`로 표시된다 |
60 | 113 | - [ ] 범위 모드에서 하한값 >= 상한값이면 validation 에러가 표시된다 |
61 | | -- [ ] 범위 모드는 `minThreshold`/`maxThreshold` 필드를 사용하여 하나의 Rule로 저장된다 |
62 | | -- [ ] >=26.4.0에서 Condition 영역에 Prometheus Preset 기반 metric name Select가 표시되고, 선택 시 queryTemplate/timeWindow가 자동 반영된다 |
63 | | -- [ ] <26.4.0에서는 기존 KERNEL/INFERENCE_FRAMEWORK 수동 입력이 동작한다 |
| 114 | +- [ ] metric source에 `PROMETHEUS` 옵션이 추가되고, 선택 시 Prometheus Preset Select가 표시된다 |
| 115 | +- [ ] Prometheus preset 선택 시 `metricName`이 자동으로 채워지고, `queryTemplate`이 읽기 전용으로 표시된다 |
| 116 | +- [ ] Prometheus preset 선택 시 `prometheusQueryPresetId` (raw UUID)가 Rule에 저장된다 |
| 117 | +- [ ] `KERNEL`/`INFERENCE_FRAMEWORK` 선택 시 preset 선택 UI가 숨겨진다 |
| 118 | +- [ ] Rule 목록에서 `PROMETHEUS` source인 rule은 preset 이름이 표시된다 |
| 119 | +- [ ] Rule 목록은 `ModelDeployment.autoScalingRules` nested connection으로 조회된다 |
64 | 120 |
|
65 | 121 | ## 버전 게이팅 전략 |
66 | 122 |
|
67 | | -| 기능 | <26.4.0 | >=26.4.0 | |
| 123 | +| 기능 | <26.4.0 (Legacy) | >=26.4.0 (Strawberry) | |
68 | 124 | |---|---|---| |
69 | | -| 비교 연산자 목록 정규화 | 적용 | 적용 | |
70 | | -| 조건 모드 (단일/범위) | 적용 | 적용 | |
71 | | -| Prometheus Preset 메트릭 선택 | 기존 수동 입력 유지 | Preset 기반 Select | |
| 125 | +| 비교 연산자 표시 정규화 | 적용 (LESS_THAN/GREATER_THAN 반전) | 적용 (min/maxThreshold 기반) | |
| 126 | +| 조건 모드 (단일/범위) | 단일만 지원 (Graphene에 범위 필드 없음) | 단일 + 범위 모두 지원 | |
| 127 | +| Metric Source: PROMETHEUS | 미표시 (KERNEL/INFERENCE_FRAMEWORK만) | PROMETHEUS 추가, Preset 기반 Select | |
72 | 128 | | 쿼리 결과값 미리보기 | 미표시 | 표시 (Nice to Have) | |
73 | 129 |
|
| 130 | +## 구현 전략: 파일 분리 |
| 131 | + |
| 132 | +Strawberry API 마이그레이션과 UX 개선을 동시에 진행하므로, 기존 Graphene 기반 컴포넌트를 legacy로 이동하고 새 파일을 정식 이름으로 생성한다. |
| 133 | + |
| 134 | +### 파일 구조 |
| 135 | + |
| 136 | +| 파일 | API | 설명 | |
| 137 | +|---|---|---| |
| 138 | +| `AutoScalingRuleEditorModal.tsx` | Strawberry (>=26.4.0) | **신규**. 범위 모드, Prometheus preset, 정규화 포함 | |
| 139 | +| `AutoScalingRuleEditorModalLegacy.tsx` | Graphene (<26.4.0) | **기존 파일 rename**. 비교 연산자 정규화만 적용 | |
| 140 | +| `AutoScalingRuleList.tsx` | Strawberry (>=26.4.0) | **신규**. EndpointDetailPage에서 추출. 정규화된 조건 표시, 범위 표시 포함 | |
| 141 | +| `AutoScalingRuleListLegacy.tsx` | Graphene (<26.4.0) | **EndpointDetailPage에서 추출**. 비교 연산자 정규화만 적용 | |
| 142 | + |
| 143 | +### 분기 방식 |
| 144 | + |
| 145 | +`EndpointDetailPage.tsx`에서 `baiClient.supports('prometheus-auto-scaling-rule')` (또는 대응 키)로 분기: |
| 146 | +- **>=26.4.0**: `AutoScalingRuleList` + `AutoScalingRuleEditorModal` (Strawberry query/mutation) |
| 147 | +- **<26.4.0**: `AutoScalingRuleListLegacy` + `AutoScalingRuleEditorModalLegacy` (Graphene query/mutation) |
| 148 | + |
| 149 | +### Strawberry 마이그레이션 범위 |
| 150 | + |
| 151 | +이 spec에서 함께 처리하는 마이그레이션 항목: |
| 152 | +- Query: `endpoint_auto_scaling_rule_nodes(endpoint)` → `ModelDeployment.autoScalingRules` (nested connection) |
| 153 | +- Mutation: `create_endpoint_auto_scaling_rule_node` → `createAutoScalingRule`, `modify_endpoint_auto_scaling_rule_node` → `updateAutoScalingRule`, `delete_endpoint_auto_scaling_rule_node` → `deleteAutoScalingRule` |
| 154 | +- 필드: snake_case → camelCase, `threshold`+`comparator` → `minThreshold`/`maxThreshold`, `cooldown_seconds` → `timeWindow`, `endpoint` → `modelDeploymentId` |
| 155 | + |
74 | 156 | ## 참조 |
75 | 157 |
|
76 | 158 | ### 현재 코드 |
77 | 159 |
|
78 | 160 | | 파일 | 설명 | |
79 | 161 | |---|---| |
80 | | -| `react/src/components/AutoScalingRuleEditorModal.tsx` | Rule 추가/편집 모달. `COMPARATOR_LABELS` (line 53-58), `METRIC_NAMES_MAP` (line 60-65) | |
| 162 | +| `react/src/components/AutoScalingRuleEditorModal.tsx` | 기존 Rule 추가/편집 모달 (Graphene). rename → `AutoScalingRuleEditorModalLegacy.tsx` | |
81 | 163 | | `react/src/pages/EndpointDetailPage.tsx` | Rules 목록 테이블 (line 697-926), `baiClient.supports('auto-scaling-rule')` (line 170) | |
82 | 164 |
|
83 | | -### GraphQL API (모든 인증된 사용자) |
| 165 | +### GraphQL API |
| 166 | + |
| 167 | +#### Auto Scaling Rule (Strawberry, >=26.4.0) |
| 168 | + |
| 169 | +| 쿼리/뮤테이션 | 설명 | |
| 170 | +|---|---| |
| 171 | +| `ModelDeployment.autoScalingRules(filter, orderBy, ...)` | Rule 목록 조회 — `ModelDeployment` type의 nested connection | |
| 172 | +| `createAutoScalingRule(input: CreateAutoScalingRuleInput!)` | Rule 생성 (`modelDeploymentId` 필수) | |
| 173 | +| `updateAutoScalingRule(input: UpdateAutoScalingRuleInput!)` | Rule 수정 (partial update) | |
| 174 | +| `deleteAutoScalingRule(input: DeleteAutoScalingRuleInput!)` | Rule 삭제 | |
| 175 | + |
| 176 | +#### Prometheus Query Preset (Read only, 모든 인증된 사용자) |
84 | 177 |
|
85 | 178 | | 쿼리 | 설명 | |
86 | 179 | |---|---| |
87 | 180 | | `prometheusQueryPresets(filter, orderBy, limit, offset)` | Preset 목록 조회 | |
88 | | -| `prometheusQueryPresetResult(id, timeRange, options, timeWindow)` | Preset 기반 쿼리 실행 결과 조회 | |
| 181 | +| `prometheusQueryPreset(id: ID!)` | Preset 단건 조회 | |
| 182 | +| `prometheusQueryPresetResult(id, timeRange, options, timeWindow)` | Preset 기반 쿼리 실행 결과 (Nice to Have) | |
| 183 | + |
| 184 | +#### ID 변환 주의사항 |
| 185 | + |
| 186 | +- Preset 목록 조회 (`prometheusQueryPresets`) 응답의 `id`는 Relay global ID (base64 인코딩) |
| 187 | +- Rule 생성/수정 시 `prometheusQueryPresetId`에는 `toLocalId(globalId)`로 디코딩한 raw UUID를 전달 |
| 188 | +- Rule 조회 응답의 `prometheusQueryPresetId`는 raw UUID로 반환됨 |
| 189 | +- Preset 목록의 Relay global ID에서 `toLocalId()`로 변환한 값과 매칭하여 preset 정보를 표시 |
| 190 | + |
| 191 | +### 주요 타입 (스키마 기준) |
| 192 | + |
| 193 | +#### AutoScalingMetricComparator (enum) |
| 194 | +`LESS_THAN`, `LESS_THAN_OR_EQUAL`, `GREATER_THAN`, `GREATER_THAN_OR_EQUAL` |
| 195 | + |
| 196 | +#### AutoScalingMetricSource (enum) |
| 197 | +`KERNEL`, `INFERENCE_FRAMEWORK`, `PROMETHEUS` (PROMETHEUS는 >=26.4.0) |
| 198 | + |
| 199 | +#### AutoScalingRule (type, added 25.19.0) |
| 200 | +`id`, `metricSource`, `metricName`, `minThreshold: Decimal`, `maxThreshold: Decimal`, `stepSize`, `timeWindow`, `minReplicas`, `maxReplicas`, `prometheusQueryPresetId: ID` (added 26.4.1), `createdAt`, `lastTriggeredAt` |
| 201 | + |
| 202 | +#### CreateAutoScalingRuleInput |
| 203 | +`modelDeploymentId: ID!`, `metricSource: AutoScalingMetricSource!`, `metricName: String!`, `minThreshold: Decimal`, `maxThreshold: Decimal`, `stepSize: Int!`, `timeWindow: Int!`, `minReplicas: Int`, `maxReplicas: Int`, `prometheusQueryPresetId: ID` |
| 204 | + |
| 205 | +#### UpdateAutoScalingRuleInput |
| 206 | +`id: ID!`, `metricSource`, `metricName`, `minThreshold`, `maxThreshold`, `stepSize`, `timeWindow`, `minReplicas`, `maxReplicas`, `prometheusQueryPresetId: ID` |
| 207 | + |
| 208 | +#### QueryDefinition (type, added 26.3.0) |
| 209 | +`id`, `name`, `metricName`, `queryTemplate`, `timeWindow: String`, `options: QueryDefinitionOptions!` (`filterLabels: [String!]!`, `groupLabels: [String!]!`), `createdAt`, `updatedAt` |
| 210 | + |
| 211 | +#### QueryDefinitionExecuteResult (type, added 26.3.0) |
| 212 | +`status: String!`, `resultType: String!`, `result: [QueryDefinitionMetricResult!]!` |
| 213 | +- **QueryDefinitionMetricResult**: `metric: [MetricLabelEntry!]!`, `values: [QueryDefinitionMetricResultValue!]!` |
| 214 | +- **QueryDefinitionMetricResultValue**: `timestamp: Float!`, `value: String!` |
89 | 215 |
|
90 | | -### 주요 타입 |
| 216 | +#### ExecuteQueryDefinitionOptionsInput |
| 217 | +`filterLabels: [MetricLabelEntryInput!]`, `groupLabels: [String!]` |
91 | 218 |
|
92 | | -- **QueryDefinition**: `id`, `name`, `metricName`, `queryTemplate`, `timeWindow`, `options`(`filterLabels`, `groupLabels`) |
93 | | -- **ExecuteQueryDefinitionOptionsInput**: `filterLabels`(`[MetricLabelEntryInput!]`), `groupLabels`(`[String!]`) |
94 | | -- **CreateAutoScalingRuleInput**: `metricSource`, `metricName`, `minThreshold`, `maxThreshold`, `stepSize`, `timeWindow`, `minReplicas`, `maxReplicas` |
| 219 | +#### QueryTimeRangeInput (added 26.3.0) |
| 220 | +`start: DateTime!`, `end: DateTime!`, `step: String!` |
95 | 221 |
|
96 | 222 | ## 범위 외 (Out of Scope) |
97 | 223 |
|
98 | | -- Admin Serving 페이지 Prometheus Query Preset CRUD (별도 Spec) |
99 | | -- Auto Scaling Rule CRUD 자체 (이미 구현됨) |
100 | | -- `metric_source` 필드의 백엔드 변경 대응 (TODO, 백엔드 PR 머지 후 처리) |
| 224 | +- Admin Prometheus Query Preset CUD (`adminCreatePrometheusQueryPreset`, `adminModifyPrometheusQueryPreset`, `adminDeletePrometheusQueryPreset`) — 별도 Spec |
| 225 | +- Auto Scaling Rule CRUD 자체 (이미 구현됨, 이 spec에서는 Strawberry API로 마이그레이션) |
| 226 | +- Graphene legacy 코드 완전 제거 (백엔드 최소 버전 올린 후 별도 진행) |
0 commit comments