# OpenZFS 事务延迟

当后端存储无法容纳传入写入的速率时，OpenZFS 写操作会被延迟。此延迟过程称为 OpenZFS 写入节流。由于不同硬件和工作负载具有不同的性能特性，需针对具体硬件和工作负载进行写入节流的调优。

写入被分组为事务。事务被分组为事务组。当一个事务组同步到磁盘时，该组中的所有事务都被视为完成。当对某个事务应用延迟时，会延迟该事务被分配到事务组。

要检查某个存储池是否启用了写入节流，可监控计数器 kstat `dmu_tx_delay`、 `dmu_tx_dirty_delay`。

如果已经存在一个等待中的写事务，则延迟相对于该事务完成等待的时间。因此，计算得到的延迟时间与并发执行事务的线程数量无关。

如果只有一个等待者，则延迟相对于事务开始的时间，而不是当前时间。这会为该事务计入“已服役时间”。例如，如果一个写事务需要先读取间接块，那么延迟会从事务开始时计入，也就是在读取间接块之前。

事务所需的最小时间计算如下：

```
min_time = zfs_delay_scale * (dirty - min) / (max - dirty)
min_time 随后被限制在 100 毫秒以内
```

延迟具有两个可通过可调参数进行调整的自由度：

1. 开始延迟的脏数据百分比由 `zfs_delay_min_dirty_percent` 定义。该值通常应设置为大于或等于 `zfs_vdev_async_write_active_max_dirty_percent`，以便在全速写入无法跟上传入写入速率之后才发生延迟。
2. 曲线的尺度由 `zfs_delay_scale` 定义。粗略而言，该变量决定曲线中点处的延迟量。

```
延迟
 10ms +-------------------------------------------------------------*+
      |                                                             *|
  9ms +                                                             *+
      |                                                             *|
  8ms +                                                             *+
      |                                                            * |
  7ms +                                                            * +
      |                                                            * |
  6ms +                                                            * +
      |                                                            * |
  5ms +                                                           *  +
      |                                                           *  |
  4ms +                                                           *  +
      |                                                           *  |
  3ms +                                                          *   +
      |                                                          *   |
  2ms +                                              (midpoint) *    +
      |                                                  |    **     |
  1ms +                                                  v ***       +
      |             zfs_delay_scale ---------->     ********         |
    0 +-------------------------------------*********----------------+
      0%                    <- zfs_dirty_data_max ->               100%
```

请注意，由于延迟被加到最近事务剩余的未完成时间上，因此延迟实际上是 IOPS 的倒数。在此，中点 500 微秒对应 2000 IOPS。曲线的形状被设计为：在曲线前四分之三的区间内，累积脏数据量的小幅变化只会导致延迟量的相对小幅变化。

当以对数尺度表示延迟量时，其效果更易理解：

```
延迟
100ms +-------------------------------------------------------------++
      +                                                              +
      |                                                              |
      +                                                             *+
 10ms +                                                             *+
      +                                                           ** +
      |                                              (midpoint)  **  |
      +                                                  |     **    +
  1ms +                                                  v ****      +
      +             zfs_delay_scale ---------->        *****         +
      |                                             ****             |
      +                                          ****                +
100us +                                        **                    +
      +                                       *                      +
      |                                      *                       |
      +                                     *                        +
 10us +                                     *                        +
      +                                                              +
      |                                                              |
      +                                                              +
      +--------------------------------------------------------------+
      0%                    <- zfs_dirty_data_max ->               100%
```

请注意，只有当脏数据量接近其上限时，延迟才会开始迅速增加。经过正确调优的系统目标应当是将脏数据量保持在该区间之外：首先确保为 I/O 调度器设置合适的限制，使其在后端存储上达到最佳吞吐量；然后通过调整 `zfs_delay_scale` 的值来增加曲线的陡峭程度。

参考代码： [dmu\_tx.c](https://github.com/openzfs/zfs/blob/master/module/zfs/dmu_tx.c#L866)
