# Premium Reserved Capacity Template 计算公式

本文档总结 Management Portal UI 当前 Premium Reserved Capacity
Template 计算器的前端计算逻辑。来源代码：

- `src/pages/Tenants/TenantDetail/PremiumReservedCapacity/calculator.ts`
- `src/pages/Tenants/TenantDetail/PremiumReservedCapacity/TemplateCalculator.tsx`
- `src/pages/Tenants/TenantDetail/PremiumReservedCapacity/api.ts`
- `src/pages/Tenants/TenantDetail/PremiumReservedCapacity/index.tsx`

## 可编辑项

创建 template 前，用户可以在计算器里编辑以下字段：

| 字段                              | 是否可编辑               | 约束/取值                                                                                              |
| --------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
| Name                              | 是                       | 必填，保存为 `template_code`                                                                           |
| Description                       | 是                       | 必填，保存为 `description`，同时作为创建 reason                                                        |
| QPS                               | 是                       | 最小 1000，必须是 1000 的整数倍，保存为 `workload.qps_max`；进入 topology edit 后 disabled             |
| Workload Pattern                  | 是                       | `read` / `write` / `mixed`，保存为 `READ` / `WRITE` / `MIXED`；进入 topology edit 后 disabled          |
| Latency Requirement               | 是                       | `P99 100ms` / `P99 300ms`，保存为 `workload.p99_response_latency_max`；进入 topology edit 后 disabled  |
| Customer Data Size                | 是                       | 最小 100 GiB，必须是 100 的整数倍，保存为 `workload.data_size_gib_max`；进入 topology edit 后 disabled |
| Compression Factor                | 否                       | 固定为 `0.33`                                                                                          |
| Replica                           | 否                       | 固定为 `3`                                                                                             |
| Storage Water Level               | 否                       | 固定为 `0.7`                                                                                           |
| Component spec/node count/storage | 部分可编辑               | 默认由计算器根据输入自动生成；具体可编辑字段见下方各 component 表格                                    |
| Purchased vCPU                    | 否                       | 由所有 component 的 vCPU 汇总自动计算                                                                  |
| Scope                             | 否                       | 创建时固定为 `ORG_PRIVATE`                                                                             |
| Status                            | 创建时否，创建后部分可改 | 创建时固定为 `ACTIVE`；非 default template 可在 `ACTIVE` / `DEPRECATED` 间切换                         |

说明：QPS、workload、latency、customer data size 是计算器输入项；component
spec、node count、storage 是计算器产出的 topology 默认值，部分字段支持进入 edit
后覆盖。覆盖后应以编辑后的 topology 为准，公式只代表自动生成默认 topology 的逻辑。
`scope === DEFAULT` 的 template 不支持修改 status 或删除。

## 输入与固定假设

输入：

- `qps`
- `workloadPattern`: `read` / `write` / `mixed`
- `latencyRequirement`: `p99_100ms` / `p99_300ms`
- `customerDataSizeGb`: UI 与 API 含义为 GiB

固定假设：

```text
compressionFactor = 0.33
replica = 3
storageWaterLevel = 0.7
dataDiskCapacityStepGib = 100
dataDiskThroughputMbpsMax = 1000
tikvDataDiskLimit = 4096 GiB for 8c32g, 8192 GiB for 16c64g
tikvDataDiskIopsMin = 3000
tikvDataDiskIopsMax = 16000
tiflashDataDiskDefaultGib = 500
tiflashDataDiskMinGib = 200
tiflashDataDiskMaxGib = 4096
tiflashDataDiskIops = 6000
tiflashDataDiskIopsMin = 3000
tiflashDataDiskIopsMax = 16000
tiflashDataDiskThroughputMbps = 625
```

## 各 Component 支持规格

下面列的是当前计算器会生成、且 topology edit 应按组件暴露的规格范围。

### TIDB

| 支持 spec | Node count         | Spec 可编辑 | Node count 可编辑 |
| --------- | ------------------ | ----------- | ----------------- |
| `8c32g`   | 由公式生成，最小 2 | 是          | 是                |
| `16c64g`  | 由公式生成，最小 2 | 是          | 是                |

### TIKV

| 支持 spec | Node count                                           | Spec 可编辑 | Node count 可编辑 | DATA size 可编辑 | DATA IOPS 可编辑 | DATA throughput 可编辑 |
| --------- | ---------------------------------------------------- | ----------- | ----------------- | ---------------- | ---------------- | ---------------------- |
| `8c32g`   | 由数据量和 QPS 共同计算，最小 3，按 3 的倍数向上取整 | 是          | 是                | 是               | 是               | 是                     |
| `16c64g`  | 由数据量和 QPS 共同计算，最小 3，按 3 的倍数向上取整 | 是          | 是                | 是               | 是               | 是                     |

TIKV 默认 storage：

| Storage | API name  |                     默认 Size GiB |                     默认 IOPS |         默认 Throughput Mbps | Size 可编辑                                         | IOPS 可编辑    | Throughput 可编辑 |
| ------- | --------- | --------------------------------: | ----------------------------: | ---------------------------: | --------------------------------------------------- | -------------- | ----------------- |
| DATA    | `DATA`    | 由数据量计算，按 100 GiB 向上取整 | 由 size 计算，范围 3000-16000 | 由 IOPS/size 计算，最大 1000 | 是，100 GiB 到 spec 上限，按 100 GiB 对齐或等于上限 | 是，3000-16000 | 是，1-1000        |
| RAFT    | `RAFTLOG` |                                20 |                          3000 |                          125 | 否                                                  | 否             | 否                |

### TIPROXY

| 支持 spec | Node count                   | Spec 可编辑 | Node count 可编辑 |
| --------- | ---------------------------- | ----------- | ----------------- |
| `2c8g`    | 由 TiDB 总 vCPU 计算，最小 2 | 是          | 是                |
| `4c16g`   | 由 TiDB 总 vCPU 计算，最小 2 | 是          | 是                |

### PD

| 支持 spec | Node count | Spec 可编辑 | Node count 可编辑 |
| --------- | ---------- | ----------- | ----------------- |
| `4c8g`    | 固定为 3   | 是          | 否                |
| `8c16g`   | 固定为 3   | 是          | 否                |
| `16c32g`  | 固定为 3   | 是          | 否                |

### Worker

| Component     | 支持 spec                  | Node count                                                                         | Spec 可编辑 | Node count 可编辑 |
| ------------- | -------------------------- | ---------------------------------------------------------------------------------- | ----------- | ----------------- |
| `TIDB_WORKER` | `2c8g` / `4c16g` / `8c32g` | 小规格 TiKV 默认为 1；大规格 TiKV node=3 时为 1，否则 `ceil(tikvNodeQuantity / 9)` | 是          | 是                |
| `TIKV_WORKER` | `2c8g` / `4c16g` / `8c32g` | 小规格 TiKV 默认为 1；大规格 TiKV node=3 时为 1，否则 `ceil(tikvNodeQuantity / 9)` | 是          | 是                |

### TiFlash

| Component         | 支持 spec          | Node count     | Spec 可编辑 | Node count 可编辑 | DATA size 可编辑 | DATA IOPS 可编辑 | DATA throughput 可编辑 |
| ----------------- | ------------------ | -------------- | ----------- | ----------------- | ---------------- | ---------------- | ---------------------- |
| `TIFLASH_COMPUTE` | `8c32g` / `16c64g` | 默认 0，最小 0 | 是          | 是                | 是               | 是               | 是                     |
| `TIFLASH_WRITE`   | `8c32g` / `16c64g` | 默认 0，最小 0 | 是          | 是                | 是               | 是               | 是                     |

TiFlash 默认 storage：

| Storage | API name | 默认 Size GiB | 默认 IOPS | 默认 Throughput Mbps | Size 可编辑                                      | IOPS 可编辑    | Throughput 可编辑 |
| ------- | -------- | ------------: | --------: | -------------------: | ------------------------------------------------ | -------------- | ----------------- |
| DATA    | `DATA`   |           500 |      6000 |                  625 | 是，200-4096 GiB，按 100 GiB 对齐或等于 4096 GiB | 是，3000-16000 | 是，1-1000        |

## QPS 能力表

TiKV 单节点 QPS：

| Spec     | Workload | P99 100ms | P99 300ms |
| -------- | -------- | --------: | --------: |
| `8c32g`  | read     |      7700 |     15400 |
| `8c32g`  | write    |    5437.5 |     10875 |
| `8c32g`  | mixed    |      5785 |     11570 |
| `16c64g` | read     |     15400 |     30800 |
| `16c64g` | write    |     10875 |     21750 |
| `16c64g` | mixed    |     11570 |     23140 |

TiDB 单节点 QPS：

| Spec     | Workload | P99 100ms | P99 300ms |
| -------- | -------- | --------: | --------: |
| `8c32g`  | read     |      6300 |      9450 |
| `8c32g`  | write    |      6000 |      9000 |
| `8c32g`  | mixed    |      5200 |      7750 |
| `16c64g` | read     |     12600 |     18900 |
| `16c64g` | write    |     12000 |     18000 |
| `16c64g` | mixed    |     10400 |     15500 |

TiDB QPS 数值沿用之前 `8c16g` / `16c32g` 的表；spec 扩内存主要用于降低
OOM 风险。

## 计算公式

辅助函数：

```text
roundUpToMultiple(value, multiple) = ceil(value / multiple) * multiple
roundUpToMultipleOfThree(value) = roundUpToMultiple(value, 3)
```

### TiKV

先根据 QPS 选择 TiKV spec：

```text
smallTikvQpsPerNode = TiKV_QPS_TABLE["8c32g"][workloadPattern][latencyRequirement]
largeTikvQpsPerNode = TiKV_QPS_TABLE["16c64g"][workloadPattern][latencyRequirement]

rawSmallTikvNodeCount = ceil(qps / smallTikvQpsPerNode)
roundedSmallTikvNodeCount =
  max(3, roundUpToMultipleOfThree(rawSmallTikvNodeCount))

rawLargeTikvNodeCount = ceil(qps / largeTikvQpsPerNode)
roundedLargeTikvNodeCount =
  max(3, roundUpToMultipleOfThree(rawLargeTikvNodeCount))

useLargeTikvSpec = roundedSmallTikvNodeCount >= 6

tikvSpec = useLargeTikvSpec ? "16c64g" : "8c32g"
maxDiskPerTikv = tikvSpec == "16c64g" ? 8192 : 4096
```

再按数据量和 QPS 分别计算节点数，最终取较大值：

```text
estimatedStoredDataGib =
  customerDataSizeGb * compressionFactor * replica / storageWaterLevel

nodeCountByData =
  max(3, roundUpToMultipleOfThree(estimatedStoredDataGib / maxDiskPerTikv))

nodeCountByQps =
  useLargeTikvSpec ? roundedLargeTikvNodeCount : roundedSmallTikvNodeCount

tikvNodeQuantity = max(nodeCountByData, nodeCountByQps)
```

TiKV data disk：

```text
dataDiskSizeGib =
  roundUpToMultiple(estimatedStoredDataGib / tikvNodeQuantity, 100)

while dataDiskSizeGib > maxDiskPerTikv:
  tikvNodeQuantity = roundUpToMultipleOfThree(tikvNodeQuantity + 1)
  dataDiskSizeGib =
    roundUpToMultiple(estimatedStoredDataGib / tikvNodeQuantity, 100)

dataDiskIops =
  min(max(3000, 4 * dataDiskSizeGib), 16000)

dataDiskThroughputMbps =
  min(max(dataDiskIops * 0.046875, dataDiskSizeGib * 0.5), 1000)
```

写入 template 时，TiKV 有两个 storage：

| Storage | API name  |                                                                Size GiB |                 IOPS |                Throughput Mbps |
| ------- | --------- | ----------------------------------------------------------------------: | -------------------: | -----------------------------: |
| DATA    | `DATA`    | `ceil to 100 GiB` 后的 `dataDiskSizeGib`，且不超过当前 TiKV spec 的上限 | `ceil(dataDiskIops)` | `ceil(dataDiskThroughputMbps)` |
| RAFT    | `RAFTLOG` |                                                                      20 |                 3000 |                            125 |

### TiDB

TiDB spec 跟随 TiKV spec：

```text
tidbSpec = tikvSpec == "16c64g" ? "16c64g" : "8c32g"
```

节点数：

```text
if tikvSpec == "16c64g":
  tidbNodeQuantity =
    max(2, ceil(qps / TiDB_QPS_TABLE["16c64g"][workloadPattern][latencyRequirement]))
else:
  tidbNodeQuantity =
    max(2, ceil(qps / TiDB_QPS_TABLE["8c32g"][workloadPattern][latencyRequirement]))
```

TiDB 不带 storage。

### TiProxy

先计算 TiDB 总 vCPU：

```text
tidbVcpuCount = tidbNodeQuantity * vcpu(tidbSpec)
```

再选择 TiProxy spec 和节点数：

```text
tiproxySpec = tidbVcpuCount > 60 ? "4c16g" : "2c8g"

if tiproxySpec == "4c16g":
  tiproxyNodeQuantity = max(2, ceil(tidbVcpuCount / 60))
else:
  tiproxyNodeQuantity = max(2, ceil(tidbVcpuCount / 30))
```

TiProxy 不带 storage。

### PD

PD 节点数固定为 3：

```text
pdCount = 3
```

PD spec：

```text
if tikvSpec != "16c64g":
  pdSpec = "4c8g"
else if tikvNodeQuantity <= 12:
  pdSpec = "4c8g"
else if tikvNodeQuantity <= 33:
  pdSpec = "8c16g"
else:
  pdSpec = "16c32g"
```

PD 不带 storage。

### Worker

Worker component 默认都会写入；规格大小统一按 TiKV spec 和 node count 判断。小规格
TiKV 集群的默认 node count 为 1；大规格 TiKV 集群中，TiKV node count 等于 3
时 worker 使用 1 个 `4c16g`，其余大规格 TiKV 集群按 TiKV 节点数计算：

```text
isLargeCluster = tikvSpec == "16c64g"
isMinimumLargeCluster = isLargeCluster && tikvNodeQuantity == 3

if isMinimumLargeCluster:
  tikvWorkerCount = 1
  tidbWorkerCount = tikvWorkerCount
  workerSpec = "4c16g"
else if isLargeCluster:
  tikvWorkerCount = ceil(tikvNodeQuantity / 9)
  tidbWorkerCount = tikvWorkerCount
  workerSpec = "8c32g"
else:
  tikvWorkerCount = 1
  tidbWorkerCount = tikvWorkerCount
  workerSpec = "2c8g"

tidbWorkerSpec = workerSpec
tikvWorkerSpec = workerSpec
```

默认写入 template 时：

| Component     | Spec             |        Node count | Storage |
| ------------- | ---------------- | ----------------: | ------- |
| `TIDB_WORKER` | `tidbWorkerSpec` | `tidbWorkerCount` | 无      |
| `TIKV_WORKER` | `tikvWorkerSpec` | `tikvWorkerCount` | 无      |

### TiFlash

TiFlash 不参与 QPS sizing，默认写入两个 node count 为 0 的 component。进入
topology edit 后，可以手工调整 spec、node count 和 DATA disk。

```text
tiflashSpec = "8c32g"
tiflashNodeQuantity = 0
tiflashDataDisk = {
  sizeGib: 500,
  iops: 6000,
  throughputMbps: 625
}
```

## Component 汇总

未手工 edit 时，默认写入 `fixed_topology.components` 的 component 顺序如下。
如果 topology 被手工 edit，最终保存应以编辑后的 component spec、node count 和
storage 为准。

| Component         | Spec                                                      | Node count                                                        | Storage        |
| ----------------- | --------------------------------------------------------- | ----------------------------------------------------------------- | -------------- |
| `TIDB`            | `tidbSpec`                                                | `tidbNodeQuantity`                                                | 无             |
| `TIKV`            | `tikvSpec`                                                | `tikvNodeQuantity`                                                | DATA + RAFTLOG |
| `TIPROXY`         | `tiproxySpec`                                             | `tiproxyNodeQuantity`                                             | 无             |
| `PD`              | `pdSpec`                                                  | `3`                                                               | 无             |
| `TIDB_WORKER`     | 小规格为 `2c8g`；大规格 node=3 为 `4c16g`，否则为 `8c32g` | 小规格为 1；大规格 node=3 为 1，否则 `ceil(tikvNodeQuantity / 9)` | 无             |
| `TIKV_WORKER`     | 小规格为 `2c8g`；大规格 node=3 为 `4c16g`，否则为 `8c32g` | 小规格为 1；大规格 node=3 为 1，否则 `ceil(tikvNodeQuantity / 9)` | 无             |
| `TIFLASH_COMPUTE` | `8c32g`                                                   | 0                                                                 | DATA           |
| `TIFLASH_WRITE`   | `8c32g`                                                   | 0                                                                 | DATA           |

每个 component 会转成 API topology：

```text
component_type = componentType
node_quota.node_count = nodeQuantity
node_quota.node_spec.cpu = vcpu(spec)
node_quota.node_spec.memory_gi = memoryGib(spec)
storages = disks, if any
```

总购买 vCPU 应根据最终保存的 topology 汇总：

```text
purchased_vcpu =
  sum(component.nodeQuantity * component.vcpu for component in components)
```

## 创建请求映射

保存 template 时，前端构造的关键字段如下：

```text
organization_id = 当前 tenant/org id
template_code = Name
description = Description
scope = "ORG_PRIVATE"
status = "ACTIVE"
purchased_vcpu = 最终 topology 汇总得到的 purchased_vcpu
fixed_topology.components = 最终 topology components；未 edit 时为计算器默认值
workload.qps_max = qps
workload.workload_type = READ / WRITE / MIXED
workload.p99_response_latency_max = 100 或 300
workload.data_size_gib_max = customerDataSizeGb
topology_summary = ""
```

注意：计算器内部会生成一份 `topologySummary` 文本，但当前创建请求里
`topology_summary` 固定传空字符串。
