# Tag 重要决策记录

本文档记录 Tag 模块在设计与演进过程中的关键决策，源材料来自 `core/modules/tag/ARCHITECTURE.md`。

---

## 决策 1：三层表结构设计（Scenario / Definition / Value）

**问题**：如何组织 `tag_scenario`、`tag_definition`、`tag_value` 的关系？

**决策**：采用三层表结构，清晰分离业务场景、标签定义和标签值。

**理由**：

- 场景（Scenario）描述业务逻辑单元
- 定义（Definition）描述具体标签
- 值（Value）存储具体实体在具体日期的标签值

**影响**：

- 查询时可按场景或标签维度灵活聚合
- 删除场景时需要应用层处理级联删除

---

## 决策 2：多进程而非多线程执行标签计算

**问题**：Tag 计算使用多进程还是多线程？

**决策**：使用多进程（ProcessWorker）。

**理由**：

- 标签计算通常是 CPU 密集型，多进程可以绕过 GIL，真正利用多核
- 进程级内存隔离更安全，单个进程崩溃不会影响整体
- 进程结束自动释放内存，适合大规模批处理

**影响**：

- 需要序列化 job payload（通过 pickle）
- 子进程需要在本地重新初始化 DataManager

---

## 决策 3：按 Entity 分割 Jobs（每个实体一个 Job）

**问题**：Job 粒度应该按日期、按标签还是按实体（如股票）分割？

**决策**：以实体为单位分割 Jobs，每个实体一个 Job。

**理由**：

- 内存可控：每个进程只处理一个实体的全部历史
- 失败隔离好：单个实体失败不影响其他实体
- 批量写入简单：可以一次性存储某个实体的所有标签值

**影响**：

- Job 数量 = 实体数量，进程数需根据 job 数量动态决定

---

## 决策 4：支持 Chunk 模式与全量模式两种数据加载策略

**问题**：大数据量场景下如何在性能和内存之间平衡？

**决策**：同时支持 Chunk 模式（use_chunk=True）和全量模式（use_chunk=False）。

**理由**：

- Chunk 模式允许在大数据量场景下控制内存上限（滑动窗口）
- 全量模式在数据量中等时更简单直接，性能更好

**影响**：

- TagWorkerDataManager 需要实现两套加载策略
- 配置中新增 `performance.use_chunk` 和 `performance.data_chunk_size`

---

## 决策 5：INCREMENTAL 模式的初始化策略

**问题**：增量更新模式下，如何保证有足够历史数据用于计算？

**决策**：在 INCREMENTAL 模式下，初始化时加载包含 `start_date` 的当前 chunk 以及前一个 chunk（共 2 个 chunk）。

**理由**：

- 能保证有足够的「前置历史」支撑长窗口指标或复杂标签计算
- 避免用户手动管理「历史窗口」的数据范围

**影响**：

- 初始化会有一次性的额外数据加载开销
- 需要验证 `incremental_required_records_before_as_of_date <= data_chunk_size`

---

## 决策 6：框架统一负责数据过滤，避免“上帝模式”

**问题**：如何避免标签计算时不小心看到未来数据（上帝模式）？

**决策**：在框架层（TagWorkerDataManager）统一实现 `filter_data_to_date(as_of_date)`，TagWorker 只能看到截至当日的数据。

**理由**：

- 保证所有 TagWorker 遵循相同的时序约束
- 降低用户忘记过滤数据导致的隐性 bug 风险

**影响**：

- 每次计算前都要进行过滤（但性能开销在可接受范围内）
- 用户无法访问未来数据，这是预期行为

---

## 决策 7：一个 TagWorker 支持产生多个标签

**问题**：一个 TagWorker 是否应该只负责一个 Tag？

**决策**：允许一个 TagWorker 为多个 Tag 提供计算逻辑。

**理由**：

- 很多业务场景天然产出多种标签（如市值分类的大小市值标签）
- 共享逻辑、减少重复代码，配置层再区分不同 Tag

**影响**：

- `calculate_tag()` 需要根据 `tag_definition` 中的名称或配置区分不同标签

---

## 决策 8：Settings 与 TagWorker 分离

**问题**：是否将配置写在 Worker 代码中，还是拆成独立配置文件？

**决策**：采用 `settings.py` + `tag_worker.py` 的分离设计。

**理由**：

- 配置与代码分离，有利于版本管理与热更新
- Worker 可以专注于业务逻辑，Settings 专注于元数据与参数

**影响**：

- 用户需要维护两类文件，但职责更清晰

---

## 决策 9：使用 JSON 类型存储标签值

**问题**：标签值字段采用固定列还是灵活结构？

**决策**：使用数据库 JSON 类型存储标签值。

**理由**：

- 支持结构化数据（键值对、数组等）
- 避免频繁修改表结构
- 兼容多场景、多维度标签

**影响**：

- 写入时需要 JSON 序列化
- 大多数查询场景只按主键和日期查，无需解析 JSON

---

## 决策 10：多进程执行使用 QUEUE 模式

**问题**：在 Worker 侧选择 QUEUE 还是 BATCH 执行模式？

**决策**：Tag 系统使用 `ExecutionMode.QUEUE`。

**理由**：

- 单 entity 单 job 已经很好地控制了内存
- QUEUE 模式能持续填充进程池，保持资源利用率

**影响**：

- 需要由 Worker 模块负责进程池填充与监控

