# 容量规划

夜莺的资源消耗由规则数、序列数、事件量和数据库负载决定；量出四个数就知道该往哪个方向扩，这里不提供规格表。

最后校对于 2026-10-10

这页不给「X 核 Y 内存能跑 Z 条规则」这样的表——最后一节说明为什么。 它给的是**四个可以直接量出来的数**，以及每个数逼近上限时该往哪个方向扩。 量完你自己的环境，比抄别人的规格准。

## 什么在消耗资源[​](#什么在消耗资源 "什么在消耗资源的直接链接")

| 消耗什么 | 主要由什么决定                                                                |
| -------- | ----------------------------------------------------------------------------- |
| CPU      | 规则条数 × 判定频率，以及每次判定返回的序列数                                 |
| 内存     | 内存里缓存的配置（规则、设备、用户）+ 判定返回的序列 + 队列里积压的样本和事件 |
| 元数据库 | 事件写入、通知记录写入、各缓存每 9 秒一次的同步查询                           |
| 磁盘     | 内置时序库的数据块、判定记录、日志                                            |
| 出向网络 | 每次判定对数据源的查询、每次转发给时序库的样本                                |

注意**判定的开销主要不在夜莺这一侧**：一条规则每分钟对 Prometheus 发一次查询， 压力大头在 Prometheus。规则多了以后先扛不住的往往是数据源，不是夜莺。

## 先量这四个数[​](#先量这四个数 "先量这四个数的直接链接")

都在 `/metrics` 上，`curl --noproxy '*' http://n9e:17000/metrics` 就能取。

### 1. 活跃序列数[​](#1-活跃序列数 "1. 活跃序列数的直接链接")

```
prometheus_tsdb_head_series
```

只有开着内置时序库的 Center 有这个指标，它是当前内存里活跃序列的真实条数。 用外部时序库时看外部库自己的对应指标。

**上限参考**：内置时序库适合 10 万活跃序列以内，再往上要换外部库。 这是产品自己给出的边界，写在 `etc/config.toml` 的 `[EmbeddedTSDB]` 注释里。

### 2. 判定耗时[​](#2-判定耗时 "2. 判定耗时的直接链接")

```
n9e_alert_rule_eval_total                        # 总判定次数，不带标签

n9e_alert_rule_eval_duration_ms{rule_id="..."}   # 每条规则最近一次判定耗时
```

按 `rule_id` 排序看 `rule_eval_duration_ms` 的 top 10，那几条就是你的成本中心。 **单条规则的判定耗时接近它的执行频率**（默认 `@every 60s`）时，这条规则已经排不开了。

处理办法通常是改规则，不是加机器：降低执行频率、缩小查询范围、把返回上万条序列的 查询先做聚合。

### 3. 数据库连接[​](#3-数据库连接 "3. 数据库连接的直接链接")

```
n9e_db_pool_in_use_connections     # 正在用的连接数

n9e_db_pool_wait_count_total       # 等过连接的次数

n9e_db_pool_wait_duration_seconds_total
```

`in_use` 长期贴着 `[DB] MaxOpenConns`（默认 150）、并且 `wait_count_total` 在涨， 就是数据库这一层排队了。先看是不是有慢查询，再考虑调大连接池。

### 4. 写入量与队列[​](#4-写入量与队列 "4. 写入量与队列的直接链接")

```
n9e_pushgw_samples_received_total{channel="prometheus"}   # 每秒进来多少样本

n9e_pushgw_sample_queue_size{queueid="..."}               # 各队列积压

n9e_pushgw_push_queue_error_total{queueid="..."}          # 被丢的样本
```

队列个数默认等于 CPU 核数，全局准入上限是 `队列数 × QueueMaxSize × 0.1`。 `push_queue_error_total` 一旦非 0，就是真的在掉数据了，详见 [Pushgw 队列、写入器与双写](https://n9e.github.io/zh/docs/operations/pushgw.md)。

## 磁盘要单独算[​](#磁盘要单独算 "磁盘要单独算的直接链接")

三份数据都落在本地盘上，各有各的上限：

| 数据       | 配置项                                                            | 默认上限                              |
| ---------- | ----------------------------------------------------------------- | ------------------------------------- |
| 内置时序库 | `[EmbeddedTSDB] RetentionDuration` / `MaxBytes`                   | 15 天 / 10 GiB，**哪个先到删哪个**    |
| 判定记录   | `[Alert.EvalLog] RetentionHours` / `MaxDiskGB` / `PerRuleDailyMB` | 192 小时 / 20 GB / 单规则每天 1024 MB |
| 日志       | `[Log] KeepHours` / `RotateNum` / `RotateSize`                    | 默认输出到 stdout，不占盘             |

`MaxBytes` 留空或写 0 表示不限制内置时序库的磁盘用量——那就变成磁盘写满为止， 生产上不要这么配。

元数据库这边随时间增长的是三张表，清理任务的默认行为不一样：

| 表             | 配置项                               | 默认                                                 |
| -------------- | ------------------------------------ | ---------------------------------------------------- |
| 历史告警事件   | `[Center] CleanAlertHisEventDay`     | **永久保留**（`<= 0` 表示不清理），每天凌晨 2 点执行 |
| 通知记录       | `[Center] CleanNotifyRecordDay`      | 7 天，每天凌晨 1 点执行                              |
| 工作流执行记录 | `[Center] CleanPipelineExecutionDay` | 7 天，每天早上 6 点执行                              |

**历史告警事件默认不清理**，事件量大的环境上线前就该显式设一个保留天数， 否则这张表会一直长。

## 按什么维度扩[​](#按什么维度扩 "按什么维��度扩的直接链接")

先定位是哪个数到顶了，再决定扩什么。加机器不能解决所有问题。

| 到顶的是                     | 该做的                                                                                                         |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| 判定耗时（规则排不开）       | 加 `n9e` 实例——规则会自动重新分派到新实例上                                                                    |
| 判定耗时（少数几条规则很慢） | 改那几条规则，加机器没用                                                                                       |
| 活跃序列数                   | 换外部时序库，见[内置时序库的单 Center 限制](https://n9e.github.io/zh/docs/operations/embedded-tsdb-limits.md) |
| 写入量                       | 拆出 `n9e-pushgw`，或者加 `n9e` 实例并在前面做负载均衡                                                         |
| 数据库排队                   | 先查慢查询，再调 `[DB] MaxOpenConns`，最后才是加数据库规格                                                     |
| 磁盘                         | 调保留期，或者把内置时序库换掉                                                                                 |
| Web 卡但判定正常             | 拆出 `n9e-alert`，让判定和 Web 不抢 CPU                                                                        |

还有一项容易忽略的成本：Pushgw 会为**每一台被监控的机器**在自己的 `/metrics` 上暴露 一条 `n9e_pushgw_sample_received_by_ident{host_ident="..."}`。机器数量上万时， 光这一个指标就是上万条序列，而且没有开关能关掉它——采集夜莺自身指标时要把这份成本算进去。

## 为什么这里没有规格表[​](#为什么这里没有规格表 "为什么这里没有规格表的直接链接")

「N 核 M 内存支持 K 条规则」这种数字，只有在写清楚「规则查的是什么数据源、 每次返回多少条序列、执行频率多少」之后才有意义，而这三项在不同环境里能差两个数量级： 一条 `up == 0` 的规则和一条扫一万条序列再做 `topk` 的规则，成本不在一个量级上。

给一个没有前提条件的数字，读者照着买机器，出问题的时候这份文档要负责任。 所以这里给的是量法和扩的方向：**先在你自己的规模上跑起来，量上面四个数，再按需要扩**。

## 相关[​](#相关 "相关的直接链接")

* [生产拓扑](https://n9e.github.io/zh/docs/operations/topology.md)
* [高可用与故障域](https://n9e.github.io/zh/docs/operations/high-availability.md)
* [Pushgw 队列、写入器与双写](https://n9e.github.io/zh/docs/operations/pushgw.md)
* [内置指标与健康检查](https://n9e.github.io/zh/docs/operations/health.md)
