# 缓存配额

## Directory-Based Cluster Quota Overview

Alluxio 允许管理员对目录设置配额，以限制该目录中的文件在 Alluxio 集群所有worker上使用的总缓存空间 。

<figure><img src="https://3373003307-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwnevsZwPNckCUenYadxu%2Fuploads%2Fgit-blob-83b6d54b256c849e1d9cab238d29c18f86205f51%2Fquota-architecture.png?alt=media" alt=""><figcaption></figcaption></figure>

ETCD 仅存储配额定义。当前的配额使用情况不会存储在 ETCD 中，也不会在 ETCD 中更新。

Worker 通过查询 ETCD 来同步最新的配额定义。 Worker 根据配额定义来追踪目录中文件的当前缓存使用情况。

Coordinator 负责定期向 worker 轮询最新的配额使用情况，并通过汇总每个 worker 的使用情况形成集群范围的配额使用视图。 如果所有 worker 的总使用量超出了某个配额定义的限制，Coordinator 会检测到，并向所有 worker 发送缓存控制命令，来处理配额超限的情况。 有关缓存控制命令的更多详细信息，请参见 [此处](#当配额超限时)。

请确保在启用基于目录的集群配额功能时，coordinator 处于运行中。 如果 coordinator 未运行，添加/删除/更新/列出配额的命令将会失败。 Worker 将继续运行，但配额定义不会被强制执行。因此，某些配额可能会被无限地超额使用。

要启用基于目录的集群配额功能，请在 `conf/alluxio-site.properties` 文件中设置以下属性：

```properties
# 启用所有组件的基于目录的集群配额功能 
alluxio.quota.enabled=true

# 配置 Coordinator 地址
alluxio.coordinator.address=<host>:<port>
```

## 基础操作

### 添加配额定义

你可以通过指定目录路径和配额大小在 Alluxio 中添加配额定义。 请注意，路径是一个不带协议（scheme)的 Alluxio 路径，而非 UFS 路径（如`s3://bucket`）。

当添加配额定义时，请确保目录在 Alluxio 中是可解析的。 也就是说，目标目录必须位于现有的 Alluxio 挂载点下，并且该目录必须存在于 UFS 中。 在下面的示例中，Alluxio 命名空间中有两个已有挂载点，只能对 `/s3` 或 `/local`下的目录添加配额定义。

```console
# Alluxio 命名空间中已有两个已有挂载点
$ bin/alluxio mount list
Listing all mount points
s3a://alluxio-jliu/                           on  /s3/    properties={...}
file:///Documents/Alluxio/underFSStorage/     on  /local/ properties={...}

# 在不存在的目录上设置配额将会失败
$ bin/alluxio quota add --directory /another --quota-size 10GB
The specified quota path /another is not under any mount point! Existing mount points are:
[/s3/, /local/].
```

可以对现有挂载点下的任何目录设置配额定义。例如：

```console
# /s3/ 是 Alluxio 命名空间中的已有挂载点
# 你可以对 /s3/ 下的目录设置配额定义
$ bin/alluxio quota add --directory /s3/data/ --quota-size 10GB
Successfully added quota definition for path /s3/data/ with size 10GB.

$ bin/alluxio quota list
Alluxio path                    	        Capacity	            Used	State
/local                          	       1024.00MB	        900.10MB	Available
/s3/data                    	             10.00GB	     Calculating	Available
```

Alluxio 还支持嵌套配额定义。例如，您可以在现有的父级配额定义下添加子级配额定义，将配额进一步拆分成更小的配额。

```console
$ bin/alluxio quota add --directory /s3/data/ --quota-size 10GB
Successfully added quota definition for path /s3/data/ with size 10GB.

$ bin/alluxio quota add --directory /s3/data/team1 --quota-size 5GB
Successfully added quota definition for path /s3/data/team1 with size 5GB.

$ bin/alluxio quota add --directory /s3/data/team2 --quota-size 4GB
Successfully added quota definition for path /s3/data/team2 with size 4GB.

$ bin/alluxio quota add --directory /s3/data/team1/user1 --quota-size 2GB
Successfully added quota definition for path /s3/data/team1/user1 with size 2GB.
```

子级配额定义的总和不得超过父级配额定义。我们还强烈建议在父级配额定义中留出足够的缓冲区，这部分配额不分配给任何子级配额定义。

```
# Suppose you have an existing parent and a child quota definition
$ bin/alluxio quota add --directory /s3/data/ --quota-size 10GB
$ bin/alluxio quota add --directory /s3/data/user1 --quota-size 5GB

# Adding the new child quota definition below will fail, as the sum of children would exceed the parent
$ bin/alluxio quota add --directory /s3/data/user2 --quota-size 6GB
Failed to add quota definition: 
INVALID_ARGUMENT: Invalid quota definition:
Parent quota: (/s3/data : 10.00GB)
Current children quotas: [(/s3/data/user1 : 5.00GB)]
Adding this new quota will exceed the parent quota.
```

您可以在现有的子级配额定义之上添加父级配额定义。

```
$ bin/alluxio quota add --directory /s3/data/team1 --quota-size 5GB
Successfully added quota definition for path /s3/data/team1 with size 5GB.

$ bin/alluxio quota add --directory /s3/data/team2 --quota-size 4GB
Successfully added quota definition for path /s3/data/team2 with size 4GB.

# The parent quota must be larger than sum of all children quotas
$ bin/alluxio quota add --directory /s3/data/ --quota-size 10GB
Successfully added quota definition for path /s3/data/ with size 10GB.
```

您也可以在不相交的层级添加子级配额定义。换句话说，不是每个配额定义中的目录层级都需要有配额定义。

```
$ bin/alluxio quota add --directory /s3/data/shared/past-records --quota-size 512MB
Successfully added quota definition for path /s3/data/shared/past-records with size 512MB.

# The sum of all children quota definitions still does not exceed the parent quota definition
$ bin/alluxio quota list
Alluxio path                    	        Capacity	            Used	State
/s3/data/                          	         10.00GB	        464.00MB	Available
/s3/data/shared/past-records           	    512.00MB	              0B	Available
/s3/data/team1                    	          3.00GB	              0B	Available
/s3/data/team2                    	          3.00GB	              0B	Available
```

配额定义的目录层级数量没有上限。然而，考虑到配额规则数量过多可能导致手工运维变困难，因此当前配额规则数量的上限被设置为50个。 用户无法改变此数量上限。因此，父级配额定义最多可以拥有49个子级配额定义。 然而，我们不建议定义超过3层嵌套的配额规则。这将对集群性能和集群管理员的操作带来额外的负担。

### 删除配额定义

你可以通过指定目录路径来删除 Alluxio 中某个目录的配额定义。

```console
$ bin/alluxio quota remove --directory /s3/data
Successfully removed quota definition for path /s3/data.
```

你可以不需要首先移除子级，而删除掉一个父级配额定义。

### 更新配额定义

更新配额定义使用的参数与添加配额定义时相同。

```console
$ bin/alluxio quota update --directory /local/data/ --quota-size 100GB
```

但是，请注意，如果将配额大小更新为小于当前使用量的值，则更新会失败。 在这种情况下，我们建议在减少配额大小之前释放一些空间。

```console
$ bin/alluxio quota update --directory /local --quota-size 1MB
Loading the latest quota definitions from ETCD
Latest quota definitions loaded: {
/local=alluxioPath: "/local/"
quota: 11534336
ufsPath: "file:///Documents/Alluxio/underFSStorage/"
}
Reducing quota size for path /local from 11534336 to 1048576.
Failed to update quota: RESOURCE_EXHAUSTED: The target quota size 1048576 is smaller than the current usage 10486645. Please free up some space before reducing the quota size. Or use the --force option.
```

在这种情况下，也可使用 `--force` 选项来强制更新。但是，该做法并不推荐。 如果强制将配额容量更新为小于当前使用量的值，coordinator 将会检测到配额超限。 然后，它将触发所有 worker 上的驱逐操作，来驱逐该配额下的一些缓存。 如果这些请求试图在该目录下创建新的缓存, 根据配置，它还可能要求 worker 停止缓存或拒绝 I/O 请求。请参考在[此处](#当配额超限时) 中的更多配置操作的细节。

```console
$ bin/alluxio quota update --directory /local --quota-size 1MB --force
```

当您更新配额定义时，所有子级配额定义的总和不得超过父级配额定义。如果更新后的配额定义违反了这一规则，更新将失败。

### 列出当前的配额定义和使用情况

你可以通过运行以下命令来列出 Alluxio 中所有现有的配额定义及其使用情况。 请注意，如果刚刚添加了新的配额定义，worker 可能需要一些时间来扫描其现有缓存并计算该目录下的当前配额使用情况。 在此期间，使用情况将被标记为`Calculating`(正在计算）。

```console
$ bin/alluxio quota list
Alluxio path                    	        Capacity	            Used	State
/local                          	       1024.00MB	              0B	Available
/s3/data                    	             10.00GB	              0B	Available
/s3/data/user1                    	          5.00GB	              0B	Available
/s3/data/user2                    	          4.00GB	              0B	Available
```

Alluxio 还提供可持续向 coordinator 轮询最新配额使用情况的命令：

```console
# Keeps running until interrupted by user by pressing ctrl+C
$ bin/alluxio quota list --interval 5s
Polling quota usage summary from the master every 5s
Alluxio path                    	        Capacity	            Used	State
/local                          	       1024.00MB	              0B	Available
/s3/data                    	             10.00GB	              0B	Available
/s3/data/user1                    	          5.00GB	              0B	Available
/s3/data/user2                    	          4.00GB	              0B	Available
Alluxio path                    	        Capacity	            Used	State
/local                          	       1024.00MB	         10.00MB	Available
/s3/data                    	             10.00GB	        100.00MB	Available
/s3/data/user1                    	          5.00GB	        100.00MB	Available
/s3/data/user2                    	          4.00GB	              0B	Available
...
```

## 进阶配置

### 当配额超限时

在当前的配额使用量超过配额定义时，coordinator 将指示集群中的所有 worker 驱逐该配额下的一些缓存，从而让总使用量再次低于配额限制。例如， 如果在 Alluxio 路径 `/s3/` 上已有一个 10GB 配额定义，而集群中有 2 个 worker：

There are 2 workers in the cluster.

* Worker A 当前在 `/s3/` 下持有 12GB 缓存。
* Worker B 当前在 `/s3/` 下持有 4GB 缓存。

Coordinator 将汇总两个 worker 的缓存总使用量，观察到当前总量为 16GB，超过了 10GB 的限制。 Coordinator 将向worker A 和 B 发送命令，要求它们各自驱逐占比 `(16 - 10) / 16 = 0.375` 的当前缓存。 如果驱逐成功，两台 worker 的缓存使用量将在短时间内变为：

* Worker A 目前在 `/s3/`下持有 7.5GB 缓存。
* Worker B 目前在 `/s3/`下持有 2.5GB 缓存。

可以观察到如前所述，当配额超限时，coordinator 会要求每个 worker 驱逐相同比例的缓存。这是目前支持的唯一驱逐策略。

除了驱逐之外，`alluxio.quota.limit.exceeded.action` 控制配额超限时的行为。 There are 3 supported modes:

1. `NO_CACHE` (默认): 当配额超限时，所有 worker 都会避免在该路径下添加新的缓存。 如果读取请求发生缓存未命中，worker 将通过读取 UFS 来满足请求，但不会进行缓存。 该模式阻止新缓存被添加到集群，同时在无报错的情况下完成请求。
2. `REJECT`: 当配额超限时，所有 worker 都会拒绝在该路径下的新缓存请求。 如果请求要在该路径下创建新缓存，worker将返回异常。 此模式使得 Alluxio 集群的缓存行为更类似于磁盘，即磁盘已满时会拒绝写入并报错。
3. `NOOP`: 当配额超限时，仍允许在该路径下创建新的缓存。 在这种模式下，新缓存将继续添加到worker 中，而 coordinator 会持续指示 worker 驱逐缓存以恢复配额空间。 请注意，如果新缓存的写入速度快于驱逐速度，则空间使用量可能永远无法恢复到设置的配额限制之内。

### 嵌套配额定义的逐出机制

当配额定义下的缓存使用量超过限制时，将触发缓存逐出以恢复配额限制。当存在嵌套配额定义时，逐出的行为变得更加复杂。

在父级和子级配额定义的一组中，配额使用量可能会在以下情况超过限制：

**情况 1：仅子级配额被过度使用** 一个或多个子级配额被过度使用，但父级配额的使用量仍在限额之内。在这种情况下，只有过度使用的子级配额会触发逐出。因此，相关子级配额目录中的部分缓存将被逐出。

**情况 2：仅父级配额被过度使用** 没有子级配额被过度使用，但父级配额的使用量超出了限额。在这种情况下，将在父级配额目录上触发逐出。逐出将根据逐出策略考虑父级目录下的所有缓存，包括子级配额目录中的缓存。因此，某些子目录中的配额使用量可能会减少，尽管它们的配额容量未超过限制。

**情况 3：由于子级配额的过度使用导致父级配额被过度使用** 一个或多个子级配额被过度使用，导致父级配额的使用量超出了容量。在这种情况下，只有过度使用的子级配额会触发逐出。当这些子级配额的使用量恢复到容量以下时，父级配额的使用量也会回到限额之下。

**情况 4：父级和子级配额都被过度使用** 一个或多个子级配额被过度使用，但父级配额的过度使用更为严重。在这种情况下，逐出的评估从最内层的配额定义开始，父级配额的逐出只有在其子级配额的配额使用量达到限制时才会考虑。例如，父级配额使用量超过容量100GB，且一个子级配额超过容量50GB。那么，首先会从该子级配额中逐出50GB，接着再从父级配额中逐出50GB。

### 配额 coordinator 心跳间隔

`alluxio.quota.worker.heartbeat.interval.ms` 控制 coordinator 从集群 worker 汇总当前配额使用情况的频率。 由于coordinator 定期向 worker 轮询配额使用情况，因此它观察到的配额使用情况（也反映在 `bin/alluxio quota list` 命令中）会有一定的延迟。

```properties
# 默认值是1秒
alluxio.quota.worker.heartbeat.interval.ms=1s
```

### 在Prometheus 指标中跟踪配额使用情况

除了 `bin/alluxio quota list` 命令外，Alluxio 还在协调器进程中通过 Prometheus 指标暴露当前的配额使用情况。这些指标使用配额定义的 Alluxio 路径进行标签化。集群管理员可以在 Grafana 仪表盘中跟踪这些 Prometheus 指标，实时监控当前的配额使用情况，而无需运行命令。

```
# HELP alluxio_quota_size_capacity_bytes Capacity of the given quota scope as defined in the quota rules
# TYPE alluxio_quota_size_capacity_bytes gauge
alluxio_quota_size_capacity_bytes{dir="/s3"} 3.145728E8
alluxio_quota_size_capacity_bytes{dir="/s3/data"} 8.388608E7
alluxio_quota_size_capacity_bytes{dir="/local"} 1.048576E8
# HELP alluxio_quota_size_used_bytes Bytes used in the given quota scope
# TYPE alluxio_quota_size_used_bytes gauge
alluxio_quota_size_used_bytes{dir="/s3"} 3.145728E8
alluxio_quota_size_used_bytes{dir="/s3/data"} 0.0
alluxio_quota_size_used_bytes{dir="/local"} 0.0
# HELP alluxio_eviction_by_quota_bytes_total Total number of bytes evicted from Alluxio workers by quota rules.
# TYPE alluxio_eviction_by_quota_bytes_total counter
alluxio_eviction_by_quota_bytes_total{dir="/local"} 8388608.0
```

## 加载作业中的配额

[Load 命令](https://documentation.alluxio.io/ee-ai-cn/ai-3.6/cache/cache-preloading) 会将目录或索引文件中指定的大量文件加载到 Alluxio 集群缓存中。 加载操作有可能超过配额并导致不希望出现的缓存驱逐。为避免这种情况，可以启用check（检查），从而在加载操作超出配额时拒绝该作业。

该检查将计算并比较以下三项信息：

1. UFS 中文件的总大小
2. 这些文件中已经在 Alluxio worker 中缓存了多少
3. 当前配额定义的可用容量

例如，如果索引文件指定了 100 个文件，总大小为 100GB，其中 50GB 已经在 worker 上缓存，而配额当前仅剩 10GB 可用，则 Alluxio 应该拒绝这个加载作业。 管理员应先手动释放至少 40GB 的配额空间，然后再尝试提交加载作业。

### 限制和配置

#### 启用加载作业中的配额检查步骤

加载作业中的配额检查步骤是默认禁用的，以下限制不会被强制执行。 要启用配额检查步骤，请设置以下属性：

```properties
# 默认false
alluxio.dora.load.job.must.check.quota=true
```

在Load指令上添加 `--skip-quota-check` 将覆盖由配置属性启用的配额检查

```console
# 跳过配额检查，直接加载作业
 
$ ./bin/alluxio job load --path s3://alluxio-test/data/ --submit --skip-quota-check
```

> 配额检查必须先启用才能强制执行以下的任何限制。如果配额检查被配置或命令行标志禁用，则不会进行任何检查。

#### 避免从多个有配额定义的路径下加载文件

如果加载作业涉及来自多个配额定义的文件，配额检查将拒绝该加载作业。 这适用于加载目录或加载索引文件中指定的文件列表。 如果启用了配额检查，将始终执行该限制。

#### 强制对所有加载作业应用配额定义

对配额检查进行配置后可在加载非配额定义下的路径时拒绝加载作业。此限制可用于确保所有加载作业都受到配额控制。 可以通过设置以下选项来选择性地关闭配额检查：

```properties
# 默认true
alluxio.dora.load.job.without.quota.allowed=false
```

#### 避免在一个配额定义下并发加载作业

由于配额检查仅发生加载作业开始时，如果在同一个配额下同时运行多个加载作业，可能会导致配额使用超限。 为避免这种情况，可以通过配置配额检查，只允许在设置了配额的路径中一次运行一个加载作业。 要选择开启这一限制，请设置以下属性：

```properties
# 默认 false
alluxio.dora.load.job.quota.mutual.exclusive=true
```
