# AWS 大数据深度剖析（第 8 部分）：在线特征存储 —— DynamoDB、ElastiCache 与 OpenSearch k-NN

> 推荐系统如何在推理时提供特征服务：用 DynamoDB 存用户特征、用 ElastiCache 做热点缓存、用 OpenSearch k-NN 做向量召回、用 Neptune 做图检索。

- 作者: zhuermu
- 发布: 2025-05-17
- 网页版: https://zhuermu.com/blog/bigdata-deep-dive-part-8-online-feature-stores/

---
## 为什么需要在线服务层？

一个推荐请求必须在 200ms 内返回。你不可能在这样的延迟下去查询数据仓库或数据湖 —— 你需要一个专门的**在线服务层**。

本章将介绍四种在线存储系统 —— DynamoDB、ElastiCache（Redis）、OpenSearch k-NN 和 Neptune，说明各自最擅长什么，以及如何在它们之间做选择。

---

## 整体职责划分

![在线存储](/images/blog/bigdata-deep-dive/08-online-storage.svg)

一个推荐请求（`GET /feed?user_id=123`）可能会查询四种不同的在线存储：

```
Recommendation Service (200ms budget)
  │
  ├─[10ms]── DynamoDB    : Fetch user features + recall pool cache
  ├─[2ms]─── Redis       : Fetch real-time behavior sequence + last recommendation cache
  ├─[20ms]── OpenSearch k-NN: User vector → ANN find items
  └─[50ms]── Neptune     : Second-degree friends / graph recall (on demand)
  │
  ▼
SageMaker Endpoint (ranking model scoring, 30ms)
  │
  ▼
Return Top 10
```

**关键洞察**：每种存储都有自己的“主场” —— 避免混淆职责。

---

## Amazon DynamoDB

### 它是什么

AWS 托管的 **KV / 文档数据库**。最重要的几个特性：

- **毫秒级读写**（P99 低于 10ms）
- **自动扩缩容**至数十万 QPS
- **完全 Serverless**（按读/写单元和存储计费）
- **无固定 Schema**（每一行可以有不同的字段）

### 数据模型

```
Table: user_features
  Partition Key: user_id (String)
  Sort Key: <optional>

Item:
{
  "user_id": "12345",
  "age": 25,
  "city": "Shanghai",
  "tags": ["food", "travel"],
  "last_5_clicks": ["v_001", "v_002", ...],
  "ctr_7d": 0.054,
  ...
}
```

每一行称为一个 Item，单个 Item 的最大尺寸为 400 KB。

### 计费模型

两种容量模式：

| 模式 | 计费方式 | 适用场景 |
|---|---|---|
| **On-Demand（按需）** | \$0.25 每百万 RRU（读请求单元）；\$1.25 每百万 WRU（写请求单元） | 不可预测或突发流量 |
| **Provisioned（预置）** | 按预留的 RCU / WCU 计费，支持 Auto Scaling | 稳定流量，最高可便宜 70% |

**RRU / WRU 计费细节（常见坑）**：

- 1 RRU = **一次强一致性读**，最多 4 KB；**最终一致性读 = 0.5 RRU**（每 4 KB）
- 1 WRU = 一次写入，最多 1 KB
- 大对象会向上取整（读 4 KB，写 1 KB）：读取一个 5 KB 的对象 = 2 RRU
- 事务性读/写费用翻倍

在估算时，**首先要确定你需要强一致性还是最终一致性** —— 仅这一点就能让成本相差 2 倍。对于推荐特征查询，最终一致性通常就足够了，因此按每次读取 0.5 RRU 来估算。

### 在客户场景中的三种角色

| # | 用途 | Schema | 数据来源 |
|---|---|---|---|
| 1 | 用户特征（user_features） | user_id 映射到 100+ 个特征维度 | 数据仓库 ads_user_features 每日同步 + Flink 实时更新 |
| 2 | 召回池（recall_u2u_cf） | user_id 映射到 top-K 候选列表 | 数据仓库 ads_recall_*_pool 每日或每小时同步 |
| 3 | 实时行为序列（user_realtime） | user_id 映射到最近 N 次点击 | Flink 实时维护（消费自 MSK） |

### 建模最佳实践

DynamoDB 不是 MySQL —— 它无法做 JOIN 或 GROUP BY。建模时：

1. **围绕访问模式设计**：先想清楚“我每次会怎么查这份数据？”，再据此设置 PK / SK
2. **热点分区是一大陷阱**：避免任何单个 key 承接远超平均水平的 QPS（例如某个“news”分区承接了所有查询）
3. **单表设计（Single-Table Design）**（进阶）：将多个相关实体放在一张表中，通过不同的排序键加以区分

### 局限性

- 单个 Item 限制为 400 KB（大对象必须拆分或存到 S3）
- 不适合复杂查询（聚合、范围扫描代价高昂）
- 不支持全文搜索或向量搜索 —— 那是 OpenSearch 的活儿

**官方文档**：
- 主页：https://docs.aws.amazon.com/dynamodb/
- 建模最佳实践：https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html

---

## ElastiCache (Redis)

### 它是什么

AWS 托管的 Redis 服务（也支持 Memcached，但生产环境几乎都用 Redis）。

关键特性：

- **亚毫秒级延迟**（通常低于 1ms）
- **丰富的数据结构**：String / List / Hash / Set / Sorted Set / Stream
- **弱持久化**（数据默认存放在内存中；AOF / RDB 持久化会带来开销）

### 在客户场景中的角色

| 用途 | 说明 |
|---|---|
| **推荐结果缓存** | 同一用户 5 分钟内再次访问 —— 直接返回缓存结果 |
| **行为序列（短期）** | List 数据结构天然适合维护“最近 N 次点击” |
| **限流 / 频控** | 计数器、滑动窗口 |
| **会话存储** | 用户当前会话的临时上下文 |

### ElastiCache 与 DynamoDB 对比

| | DynamoDB | ElastiCache (Redis) |
|---|---|---|
| 延迟 | 5-10ms | 低于 1ms |
| 持久化 | 强 | 弱 |
| 容量 | 几乎无限 | 受内存限制（节点级 GB 到 TB） |
| 复杂数据结构 | 弱 | 强 |
| 成本 | 按使用量付费 | 按节点付费（7x24 在线） |

**实践中**：用 DynamoDB 作为主存储，用 Redis 作为热点缓存 + 复杂数据结构（List / Sorted Set）。

### 部署模式

- **Cluster Mode Disabled（关闭集群模式）**：单主 + 多副本，简单
- **Cluster Mode Enabled（开启集群模式）**：分片以支撑大容量
- **Serverless**（2023 年起）：按使用量付费，零运维

**官方文档**：https://docs.aws.amazon.com/elasticache/

---

## OpenSearch k-NN（向量召回）

### 什么是 OpenSearch

AWS 从 Elasticsearch 分叉出来的产品（2021 年基于 ES 7.10 分叉）。它提供 ES 的全部能力：

- 全文搜索
- 日志聚合
- 地理位置查询
- **向量索引（k-NN 插件）**

### k-NN 用法

向量召回的核心：存储双塔模型产出的物品 embedding，然后用用户向量查询最近邻。

```json
PUT items
{
  "settings": {
    "index": {"knn": true}
  },
  "mappings": {
    "properties": {
      "item_id": {"type": "keyword"},
      "category": {"type": "keyword"},
      "embedding": {
        "type": "knn_vector",
        "dimension": 64,
        "method": {
          "name": "hnsw",
          "engine": "lucene",
          "parameters": {"ef_construction": 256, "m": 16}
        }
      }
    }
  }
}

POST /items/_search
{
  "size": 100,
  "query": {
    "knn": {
      "embedding": {
        "vector": [0.12, -0.85, ...],
        "k": 100
      }
    }
  }
}
```

### ANN 算法与引擎选型

OpenSearch k-NN 支持 3 种引擎（截至 2026 年）：

| 引擎 | 状态 | 适用场景 |
|---|---|---|
| **Faiss** | **生产首选** | 大规模、需要量化（PQ/SQ）、可选 GPU 加速 |
| **Lucene** | 稳定 | 中小规模、纯 JVM 部署、无原生库依赖 |
| **nmslib** | **已弃用** | 不再推荐用于新索引 |

算法层：

- **HNSW**：基于图的索引，兼顾延迟与召回率（首选）
- **IVF**：基于聚类的倒排索引，需要训练码本，量化可节省内存
- **PQ（Product Quantization，乘积量化）**：将向量压缩 4 到 16 倍

对于亿级规模的向量：**Faiss + HNSW，并配置合适的 ef_construction / m 参数**。若为超大规模做成本优化，可再叠加 PQ 量化。

### OpenSearch k-NN 与 S3 Vectors 对比

**S3 Vectors**（2025 年预览，2025 年下半年 GA）—— 向量索引直接存储在 S3 上，按存储 + 查询计费，Serverless。

| | OpenSearch k-NN | S3 Vectors |
|---|---|---|
| 延迟 | 10-30ms | **频繁查询约 100ms；冷查询亚秒级（数百 ms）** |
| 成本 | 高（节点 7x24 运行） | 低（按用量付费、按存储付费） |
| 规模 | 千万级到亿级 | 亿级到百亿级（为超大规模设计） |
| 多租户 / 隔离 | 索引级 | Bucket / 索引原生隔离 |
| 适用场景 | **推荐热路径**（要求毫秒级延迟） | RAG / 冷向量 / 长尾召回 / Bedrock Knowledge Bases 后端 |

> 官方文档写道：“对于不频繁的查询提供亚秒级延迟，对于更频繁的查询可低至 100 毫秒。”

推荐：**实时召回热路径用 OpenSearch k-NN**；**RAG / Knowledge Base / 大规模冷向量**场景用 S3 Vectors。两者可以共存：热向量放在 OpenSearch，长尾向量下沉到 S3 Vectors。

### 部署

OpenSearch Service（托管），按节点小时计费。从 3 个 m6g.large 节点起步，大约每月 \$400。

**官方文档**：
- OpenSearch k-NN：https://docs.aws.amazon.com/opensearch-service/latest/developerguide/knn.html
- S3 Vectors：https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors.html

---

## Amazon Neptune（图数据库）

### 它是什么

AWS 托管的图数据库。支持三种查询语言：

- **Gremlin**（属性图）
- **SPARQL**（RDF 图）
- **openCypher**（Neo4j 家族，2022 年起支持）

### 推荐场景中的图

社交类应用天然是图状结构：

```
(User A) -[follow]-> (User B)
(User A) -[like]-> (Post 1)
(User B) -[create]-> (Post 1)
(Post 1) -[has_tag]-> (Tag "food")
```

常见的图召回模式：

- **二度好友**：查询 A 的好友的好友，作为候选用户
- **共同兴趣**：A 和 B 都互动过同一批帖子 —— 强连接信号
- **关系传播**：在图上运行 PageRank / Random Walk（随机游走）

### Neptune ML（GNN）

Neptune ML 是 Neptune 内置的图神经网络（GNN）训练能力：

- 基于 DGL（Deep Graph Library）
- 自动从图数据构建训练样本
- 输出节点 / 边的 embedding
- embedding 可以喂给 OpenSearch k-NN 用于召回

### Neptune 成本与决策框架

Neptune 的入门成本较高：

- db.r6g.large 实例：约每月 \$330
- 增加只读副本：约 \$330 x N
- 数据量和 I/O 也要计费

**决策**：图召回是一项**进阶**能力（可在 POC 第 3 阶段考虑）。先落地协同过滤 + 双塔模型，验证业务价值，再引入图召回。

**官方文档**：
- Neptune：https://docs.aws.amazon.com/neptune/
- Neptune ML：https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.html

---

## 离线到在线的同步策略

从数据仓库 ADS 表同步到在线存储，是离线-在线协同的关键。

### 同步方式

| 方式 | 工具 | 适用场景 |
|---|---|---|
| **Glue Job 批量写入** | Spark | 用户特征 / 召回池的每日全量同步 |
| **EMR 批量写入** | Spark | 大数据量、复杂转换 |
| **Athena UNLOAD** | Athena to S3 to DynamoDB Import | 一次性批量加载 |
| **DynamoDB S3 Import** | 直接从 S3 文件导入 | 初始化 / 全量数据加载 |
| **SageMaker Feature Store** | SDK | 自动管理离线/在线一致性（见第 9 章） |

### 同步频率

| 数据 | 频率 |
|---|---|
| 长期用户画像 | 每日 |
| 物品特征（热度） | 每小时 |
| 召回池 | 每日或每小时 |
| 实时行为序列 | Flink 实时（毫秒到秒级） |

### 一致性考量

数据仓库与在线存储**无法保证强一致性** —— 这是设计上不可避免的。在做架构设计时：

- 在线特征可容忍最多 1 天的陈旧度
- 实时特征由 Flink 独立维护
- 在 A/B 测试期间，通过 feature flag 控制特征版本

---

## 在线层选型决策表

| 需求 | 选择 |
|---|---|
| 用户特征点查（KV） | **DynamoDB** |
| 召回池缓存（user 到 list） | **DynamoDB** |
| 短期推荐结果缓存 | **Redis** |
| 行为序列（短期） | **Redis (List) + DynamoDB（持久化）** |
| 向量召回（亿级规模） | **OpenSearch k-NN** |
| 图召回 / 多跳 | **Neptune** |
| 全文搜索 | **OpenSearch（标准索引）** |
| 限流 / 频控 | **Redis** |

---

## 客户场景：最终的在线层架构

```
Recommendation Service (deployed on ECS / EKS)
  │
  ├──▶ DynamoDB (primary)
  │     ├─ user_features (synced daily from data warehouse)
  │     ├─ recall_u2u_cf (synced daily from data warehouse)
  │     └─ user_realtime (Flink writes in real time)
  │
  ├──▶ ElastiCache Redis
  │     ├─ recommend_cache (TTL 5 min)
  │     └─ rate_limit_counter
  │
  ├──▶ OpenSearch k-NN
  │     └─ item_embeddings (two-tower model output, rebuilt daily in batch)
  │
  └──▶ SageMaker Endpoint
        └─ rank_model (ranking scores)

Optional Phase 3 addition:
  └──▶ Neptune
        └─ social_graph + Neptune ML embeddings
```

---

## 本章小结

| 服务 | 角色 | 延迟 |
|---|---|---|
| DynamoDB | 用户特征 / 召回池 / 实时序列（持久化） | 5-10ms |
| ElastiCache Redis | 热点缓存 / 复杂数据结构 / 限流 | 低于 1ms |
| OpenSearch k-NN | 向量召回（双塔物品 embedding） | 10-30ms |
| Neptune（+ Neptune ML） | 社交图 / 图召回 / GNN | 30-100ms |

下一章：ML 平台本身 —— 如何使用 SageMaker。

---

## 常见问题

### 在推荐系统中，为什么用户特征要用 DynamoDB 而不是 Redis？

DynamoDB 提供大规模的持久化 KV 存储（数十万 QPS，P99 为 5-10ms），内置持久化和自动扩缩容能力。Redis 更快（1-2ms），但受内存限制，更适合热点缓存和限流场景。

### OpenSearch k-NN 是如何实现向量召回的？

OpenSearch k-NN 存储双塔模型产出的物品 embedding，并构建 HNSW 索引以进行近似最近邻搜索。推理时，用户向量查询 OpenSearch，在毫秒级内找到最相似的 Top-K 物品。


---

## 参考资料

- [Feast documentation](https://docs.feast.dev/) — Feast
- [Amazon SageMaker Feature Store](https://docs.aws.amazon.com/sagemaker/latest/dg/feature-store.html) — AWS Documentation
