# S3-API 写入优化

{% hint style="warning" %}
此功能自 AI-3.8 起为实验性功能。
{% endhint %}

本指南介绍如何在 [S3 API](/ee-ai-cn/ai-3.8-15.1.x-cn/data-access/s3-api.md) 之上启用写缓存，将 `PUT` 请求缓冲至本地 NVMe，并在后台异步持久化至 UFS，实现毫秒级写入延迟。

## 架构概述

S3 API 支持两种部署模式：

|                  | 标准模式（读缓存）              | 写缓存模式                                |
| ---------------- | ---------------------- | ------------------------------------ |
| **适用场景**         | 加速从远程对象存储的读取           | 低延迟写入并异步持久化                          |
| **FoundationDB** | 不需要                    | 需要                                   |
| **写策略**          | WRITE\_THROUGH         | WRITE\_THROUGH、WRITE\_BACK、TRANSIENT |
| **部署复杂度**        | 低                      | 中等 — 需要 FDB 集群和路径级别的策略配置             |
| **典型工作负载**       | AI 模型加载、数据分析、基于 S3 的读取 | 训练检查点、ETL 管道、混合云写缓冲                  |

> 如果您的工作负载以读取为主、偶尔写入，标准读缓存模式已满足需求 — 参见 [S3 API](/ee-ai-cn/ai-3.8-15.1.x-cn/data-access/s3-api.md)。

### 写缓存工作原理

写缓存在标准 S3 API 部署基础上引入 **FoundationDB (FDB)** 以提供并发写入场景下的强一致性元数据管理。FDB 处于所有元数据操作的关键路径上。

* **写路径** — `PUT` 请求和 MPU 上传先写入 FDB（元数据），再写入 Worker 本地 NVMe（数据），后台持久化线程随后将数据异步上传至 UFS。
* **读路径** — `GET` 请求查询 FDB 定位数据所在 Worker，从本地 NVMe 读取。缓存未命中时，Worker 从 UFS 拉取数据并本地缓存。

## 开始之前

请在开始前完成以下检查，跳过此步骤是部署失败最常见的原因。

* [ ] **S3 API 已配置并正常运行** — 写缓存在其基础上构建：

  ```shell
  kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- \
    alluxio conf get alluxio.worker.s3.api.enabled
  ```

  预期输出：`true`
* [ ] **Alluxio Operator 正在运行**：

  ```shell
  kubectl get pods -n alluxio-operator
  ```

  预期输出：所有 Pod 均为 `Running`

## 部署步骤

### 1. 安装 FDB CRD

在安装或升级前，先在 `alluxio-operator.yaml` 中启用 FDB Operator：

```yaml
fdb-operator:
  enabled: true
```

如果是升级现有 Operator，需手动应用 FDB CRD（Helm 在升级时不会自动安装 CRD）：

```shell
# 幂等操作，可重复执行
kubectl apply -f alluxio-operator/charts/fdb-operator/crds/
```

验证 FDB Operator 正在运行：

```shell
kubectl get pods -n alluxio-operator -l app.kubernetes.io/name=fdb-operator
```

**✅ 成功：** FDB Operator Pod 显示 `READY 1/1`，`STATUS = Running`。

```console
NAME                                          READY   STATUS    RESTARTS   AGE
alluxio-fdb-controller-6cbd5c7c45-xk2pq      1/1     Running   0          2m
```

> 如果找不到该 Pod，请在 `alluxio-operator.yaml` 中设置 `fdb-operator.enabled=true` 后重新执行 `helm upgrade`。

### 2. 启用写缓存

在 `alluxio-cluster.yaml` 中添加以下配置（在 [S3 API 基础配置](/ee-ai-cn/ai-3.8-15.1.x-cn/data-access/s3-api.md#di-yi-bu-qi-yong-s3-api) 之上）：

```yaml
apiVersion: k8s-operator.alluxio.com/v1
kind: AlluxioCluster
spec:
  properties:
    alluxio.worker.s3.api.enabled: "true"
    alluxio.write.cache.enabled: "true"
  fdb:
    enabled: true
```

应用配置：

```shell
# 幂等操作，可重复执行
kubectl apply -f alluxio-cluster.yaml
```

**✅ 成功：** Helm/kubectl 无报错，集群进入协调状态。

> 如果出现 `unable to recognize "alluxio-cluster.yaml": no matches for kind "AlluxioCluster"`，说明 Alluxio Operator CRD 未安装，请先重新安装 Operator。

### 3. 验证部署

等待 Worker 就绪（启动通常需要 2–3 分钟）：

```shell
kubectl wait --for=condition=Ready pod \
  -l app.kubernetes.io/component=worker \
  -n <NAMESPACE> --timeout=120s
```

**✅ 成功：** 输出显示所有 Worker Pod 已达到 `Ready` 状态。

```console
pod/alluxio-cluster-worker-0 condition met
```

确认 FDB Pod 正在运行：

```shell
kubectl get pods -n <NAMESPACE> -l foundationdb.org/fdb-cluster-name=alluxio-cluster-fdb-meta
```

**✅ 成功：** `cluster_controller`、`log`、`storage` Pod 均显示 `Running`。

```console
NAME                                                   READY   STATUS    RESTARTS   AGE
alluxio-cluster-fdb-meta-cluster-controller-1-...      2/2     Running   0          2m
alluxio-cluster-fdb-meta-log-1-...                     2/2     Running   0          2m
alluxio-cluster-fdb-meta-storage-1-...                 2/2     Running   0          2m
```

> 如果 FDB Pod 卡在 `Pending` 状态，请检查 PVC 可用性：`kubectl get pvc -n <NAMESPACE>`。FDB 需要支持动态供给的 StorageClass。

确认写缓存在 Coordinator 上已激活：

```shell
kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- \
  alluxio conf get alluxio.write.cache.enabled
```

**✅ 成功：** 返回 `true`。

### 4. 测试写入与写后读

```shell
# 创建测试文件
echo "write cache test" > /tmp/test.txt

# 通过 Alluxio S3 API 写入对象
# 将 <LOAD_BALANCER_ADDRESS> 替换为负载均衡器的主机名或 IP。
# 如无负载均衡器，可使用 port-forward 进行测试：
#   kubectl port-forward -n <NAMESPACE> svc/alluxio-cluster-worker 29998:29998
aws s3 cp /tmp/test.txt s3://<BUCKET>/test.txt \
  --endpoint-url http://<LOAD_BALANCER_ADDRESS>:29998 \
  --no-sign-request
```

**✅ 成功：**

```console
upload: /tmp/test.txt to s3://<BUCKET>/test.txt
```

立即读回（从本地缓存响应，非 UFS）：

```shell
aws s3 cp s3://<BUCKET>/test.txt /tmp/verify.txt \
  --endpoint-url http://<LOAD_BALANCER_ADDRESS>:29998 \
  --no-sign-request

diff /tmp/test.txt /tmp/verify.txt
```

**✅ 成功：** `diff` 无输出（文件内容一致）。

> 如果写入返回 `NoSuchBucket`：请通过 `kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- alluxio mount list` 确认挂载是否已生效。

## 写入策略

写缓存默认使用 `WRITE_THROUGH`（同步写入缓存和 UFS）。如需低延迟写入，可对特定路径切换为 `WRITE_BACK`。

| 策略              | 行为                        | 适用场景                   |
| --------------- | ------------------------- | ---------------------- |
| `WRITE_THROUGH` | （默认）同时写入缓存和 UFS，两者均成功才返回。 | 数据安全优先；写入延迟受限于 UFS     |
| `WRITE_BACK`    | 立即写入缓存并返回，后台异步持久化至 UFS。   | 低延迟写入，最终一致性            |
| `TRANSIENT`     | 仅缓存，不持久化至 UFS。            | 临时/可重算数据（如 shuffle 输出） |
| `READ_ONLY`     | 禁止该路径的所有写入。               | 防止路径被意外写入              |
| `NO_CACHE`      | 绕过缓存，直接读写 UFS。            | 不需要缓存的路径               |

### 路径级配置

写入策略按路径配置，同一集群内不同路径可使用不同策略。

交互式编辑策略配置（在 Coordinator Pod 内执行）：

```shell
kubectl exec -it -n <NAMESPACE> alluxio-cluster-coordinator-0 -- alluxio pathconfig edit
```

示例配置 — 全局默认 `WRITE_THROUGH`，checkpoint 路径使用 `WRITE_BACK`，shuffle 使用 `TRANSIENT`：

```json
{
  "apiVersion": "v1.0",
  "defaultRule": {
    "description": "Global default",
    "policyMode": "WRITE_THROUGH",
    "properties": {
      "writeReplicas": 1
    }
  },
  "pathRules": [
    {
      "alluxioPath": "/checkpoints/**",
      "description": "Low-latency checkpoint writes",
      "policyMode": "WRITE_BACK",
      "properties": {
        "writeReplicas": 2
      }
    },
    {
      "alluxioPath": "/shuffle/**",
      "description": "Temporary shuffle data",
      "policyMode": "TRANSIENT",
      "properties": {
        "writeReplicas": 2
      }
    }
  ]
}
```

验证路径是否命中预期策略：

```shell
kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- \
  alluxio pathconfig test --path /checkpoints/epoch-1/model.pt
```

**✅ 成功：** 输出包含 `"policyMode": "WRITE_BACK"`。

通过 REST API（适合程序化集成）：

```shell
curl -X PUT \
  -H "Content-Type: application/json" \
  -d @pathconfig.json \
  http://<COORDINATOR_HOST>:<COORDINATOR_PORT>/api/v1/conf
```

### 多副本写入

对于 `WRITE_BACK` 和 `TRANSIENT` 路径，设置 `writeReplicas > 1` 可在不同 Worker 上保留多份未持久化数据的副本，降低 UFS 持久化完成前的数据丢失风险。

**权衡**：副本数越高，容错能力和读并发能力越强，但集群内网络带宽消耗和写入延迟略有上升。

推荐设置：

* `WRITE_BACK` — 生产环境 `writeReplicas: 2`；追求最大写入吞吐时用 `1`
* `TRANSIENT` — `writeReplicas: 2` 或更高，因为此类数据不会持久化至 UFS

## 运维与调优

### 关键配置

| 配置项                                                          | 默认值                               | 描述                                                         |
| ------------------------------------------------------------ | --------------------------------- | ---------------------------------------------------------- |
| `alluxio.write.cache.enabled`                                | `false`                           | 启用写缓存。                                                     |
| `alluxio.foundationdb.cluster.file.path`                     | `${alluxio.conf.dir}/fdb.cluster` | FDB cluster 文件路径。通过 Operator 部署 FDB 时自动注入；使用外部 FDB 时需手动指定。 |
| `alluxio.write.cache.async.check.orphan.timeout`             | `1h`                              | 孤儿文件超时。超过此时间仍未提交的写入将被视为已放弃并清理。                             |
| `alluxio.write.cache.async.file.check.period`                | `10min`                           | 孤儿检测扫描周期。设置过短会增加 FDB 负载。                                   |
| `alluxio.worker.page.store.pinned.file.capacity.limit.ratio` | `0.3`                             | 未持久化（Pinned）数据占缓存总容量的最大比例。剩余容量供读缓存使用（LRU 可驱逐）。             |
| `alluxio.worker.mark.writing.files.duration`                 | `10min`                           | 若文件处于写入状态但在该时长内未收到新数据，Worker 将其视为悬空写入并纳入清理。每次写入都会重置计时器。    |

### 异步持久化重试

对于 `WRITE_BACK` 路径，UFS 上传失败时会以**指数退避**方式重试。重试在后台线程中进行，不阻塞前端写入确认。

| 配置项                                                               | 默认值  | 描述              |
| ----------------------------------------------------------------- | ---- | --------------- |
| `alluxio.worker.write.cache.async.persist.retry.initial.interval` | `1s` | 初始重试间隔。         |
| `alluxio.worker.write.cache.async.persist.retry.max.interval`     | `1h` | 最大重试间隔（指数增长上限）。 |

### 监控异步持久化 (15.1.3+)

两条 CLI 命令可用于检查持久化操作的实时状态：

```shell
# 列出指定 Worker 上所有待处理或进行中的文件
kubectl exec -i -n <NAMESPACE> alluxio-cluster-worker-0 -- \
  alluxio async-persist list

# 查询指定路径的持久化状态和重试次数
kubectl exec -i -n <NAMESPACE> alluxio-cluster-coordinator-0 -- \
  alluxio async-persist stat --path /checkpoints/epoch-1/model.pt
```

当 `alluxio fs ls` 显示文件长期处于 `NOT_PERSISTED` 状态时，可使用 `async-persist stat` 判断问题出在队列还是上传阶段。

### 缓存空间管理

Worker 缓存空间分为两个逻辑区域：

* **Pinned 空间**（写缓存）：未持久化的脏数据，不可驱逐。默认上限为总容量的 30%（`alluxio.worker.page.store.pinned.file.capacity.limit.ratio`）。
* **可驱逐空间**（读缓存）：已持久化或从 UFS 加载的数据，空间不足时按 LRU 驱逐。

若持久化吞吐落后于写入流量，Pinned 空间将被填满，Alluxio 会返回 `out-of-space` 错误。预防措施：

* 监控 Pinned 空间使用情况，按需调整 `alluxio.worker.page.store.pinned.file.capacity.limit.ratio`
* 为两个区域分配足够的 NVMe 容量

## 性能参考

以下数据基于 `WRITE_BACK` 模式，测试环境为 AWS `c5n.metal` 客户端 + `i3en.metal` Worker（100 Gbps 网络，NVMe SSD）。实际结果因硬件、对象大小和并发度而异，仅供参考。

| 工作负载                    | 写缓存                         | Direct S3 |
| ----------------------- | --------------------------- | --------- |
| 小对象 PUT（10 KB），低并发      | 3–5 ms                      | 30–60 ms  |
| 小对象 PUT（10 KB），中并发      | 4–9 ms                      | 30–60 ms  |
| 大对象 PUT（10 MB），单 Worker | 稳定 3–6 GB/s                 | 不稳定（受限速）  |
| 写后读 GET 延迟              | 3–7 ms                      | 90–130 ms |
| 异步持久化吞吐                 | \~2,000 objects/s（每 Worker） | —         |

`WRITE_BACK` 的前端写入延迟受限于**本地 NVMe**，而非 UFS。吞吐随 Worker 数量近线性扩展。

## 卸载

按部署的反向顺序移除写缓存配置和 FDB 资源：

**1. 删除 AlluxioCluster**（同时移除 Worker、Coordinator 和 FDB Pod）：

```shell
kubectl delete -f alluxio-cluster.yaml
```

**2. 确认所有 Alluxio Pod 已移除：**

```shell
kubectl get pods -n <NAMESPACE>
```

**✅ 成功：** `No resources found in <NAMESPACE> namespace.`

如需在不删除集群的情况下禁用写缓存，在 `alluxio-cluster.yaml` 中设置 `alluxio.write.cache.enabled: "false"` 后重新应用：

```shell
kubectl apply -f alluxio-cluster.yaml  # 幂等操作
```

## 故障排查

**FDB 启动时连接失败** — FDB Pod 无法从 Worker 访问。

```shell
kubectl get pods -n <NAMESPACE> -l foundationdb.org/fdb-cluster-name=alluxio-cluster-fdb-meta
```

确认 `alluxio.foundationdb.cluster.file.path` 指向有效的 FDB cluster 文件。通过 Operator 部署时会自动注入。

***

**FDB Operator OOM / 内存占用过高** — 默认的 `globalMode: enabled: true` 会使 FDB Operator 监听整个集群的所有 Pod、PVC、ConfigMap 和 Service，在大规模集群中可能导致内存飙升至数 GB。

修复：将 Alluxio Operator、FDB Operator 和 AlluxioCluster 放入**同一 namespace**，在 `alluxio-operator.yaml` 中设置 `globalMode.enabled: false`，然后重启 FDB Operator Pod。

***

**写入时出现 out-of-space 错误** — Pinned（未持久化）数据已填满写缓存。

修复：增大 `alluxio.worker.page.store.pinned.file.capacity.limit.ratio`，扩容 NVMe，或增大 `alluxio.write.cache.async.persist.thread.pool.size` 使持久化速度跟上写入。

***

**WRITE\_BACK 数据未出现在 UFS** — 验证异步持久化线程是否在运行：

```shell
kubectl logs -n <NAMESPACE> -l app.kubernetes.io/component=worker --tail=100 | grep -i persist
```

同时检查 `alluxio.worker.write.cache.async.persist.retry.max.interval` — 若 UFS 不可达，重试可能正处于长时间退避周期中。

***

**孤儿文件持续累积** — 客户端崩溃遗留的未提交写入。缩短 `alluxio.write.cache.async.check.orphan.timeout` 可加快清理，或手动执行：

```shell
kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- alluxio job free
```

***

**目录删除返回 DEADLINE\_EXCEEDED** — 对 `WRITE_BACK` 路径执行 `alluxio fs rm -R` 时可能因超时返回 `DEADLINE_EXCEEDED`。尽管出现该错误，文件**可能已在超时前从 UFS 删除**。重试前请直接验证 UFS 状态：

```shell
aws s3 ls s3://<BUCKET>/<path>/ --recursive | head -20
```

如果 S3 中文件已不存在，则删除已成功。重新执行 `rm -R` 将返回 `Path does not exist`。Pagestore 磁盘空间不会立即收缩 — 孤儿页将在下一轮驱逐周期中被回收。

***

**pathconfig 配置未生效** — 验证策略是否正确解析：

```shell
kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- \
  alluxio pathconfig test --path <YOUR_PATH>
```

如果路径仍显示旧策略，请检查 Coordinator 日志中的配置重载记录。

***

**持久化后数据未被驱逐** — 驱逐仅在缓存空间承压时触发。如需主动释放空间：

```shell
kubectl exec -n <NAMESPACE> alluxio-cluster-coordinator-0 -- alluxio job free
```

## 参考资源

* [FUSE 写入优化](/ee-ai-cn/ai-3.8-15.1.x-cn/performance/fuse-write-cache.md) — 通过 POSIX 文件系统接口使用同一写缓存后端
* [S3 API](/ee-ai-cn/ai-3.8-15.1.x-cn/data-access/s3-api.md) — 基础端点、认证、负载均衡和客户端兼容性（启用写缓存前必须完成）
* [S3 UFS 集成](/ee-ai-cn/ai-3.8-15.1.x-cn/ufs/s3.md) — 调优底层 S3 持久化层（上传线程、分片设置）
* [S3 API 基准测试](/ee-ai-cn/ai-3.8-15.1.x-cn/benchmark/s3-api.md) — 参考基线、工具选型（COSBench / Warp / httpbench）与 S3 API 工作负载调优


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://documentation.alluxio.io/ee-ai-cn/ai-3.8-15.1.x-cn/performance/s3-write-cache.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
