# Comet Design Handoff

- Change: storage-cache-refactor
- Phase: design
- Mode: compact
- Context hash: 1064fdf5186e511848878541bdad663ac6b521ee5721807d01eaf3481bb0cd5a

Generated-by: comet-handoff.sh

OpenSpec remains the canonical capability spec. This handoff is a deterministic, source-traceable context pack, not an agent-authored summary.

## openspec/changes/storage-cache-refactor/proposal.md

- Source: openspec/changes/storage-cache-refactor/proposal.md
- Lines: 1-33
- SHA256: 759de20d4e94b3228ee94d8607740de2949c24935933884584b85d3d855569ad

```md
## Why

当前系统使用 SQLite 作为唯一存储后端，无法满足行情数据高频读写的性能需求。每次刷新行情数据都需要直接读写磁盘文件，导致响应延迟高、并发能力受限。需要引入更高效的存储方案以提升系统吞吐量和用户体验。

## What Changes

- 引入 Redis 作为热数据缓存层，行情数据优先从 Redis 读取（TTL 30 天）
- 引入 MySQL 作为持久化存储层，替代 SQLite 承担主要持久化职责
- 保留 SQLite 作为最终兜底方案，当 Redis 和 MySQL 均不可用时启用
- 新增数据访问层：实现 Redis → MySQL → SQLite 的三级降级读取策略
- 刷新接口改造：数据同步写入 Redis 和 MySQL，保证双写一致性
- 启动流程改造：初始化 Redis/MySQL/SQLite 连接，检测可用性

## Capabilities

### New Capabilities

- `redis-cache-layer`: Redis 缓存读写能力，包括热数据缓存、TTL 管理、缓存失效策略
- `mysql-persistence`: MySQL 持久化存储能力，替代 SQLite 承担主要持久化职责
- `storage-fallback`: 三级降级策略（Redis → MySQL → SQLite），保证系统可用性
- `dual-write-consistency`: 刷新接口双写能力，保证 Redis 和 MySQL 数据一致性

### Modified Capabilities

（无既有 spec 需要修改）

## Impact

- **代码**: `app/database.py`、`app/models.py`、`app/api/data.py`、`app/services/collector.py`、`app/main.py`
- **依赖**: 新增 `redis`、`pymysql`（或 `aiomysql`）Python 包
- **配置**: `app/config.py` 新增 Redis/MySQL 连接配置
- **部署**: `docker-compose.yml` 新增 Redis 和 MySQL 服务
- **数据库**: 新增 MySQL 数据库初始化脚本，保留 SQLite 作为兜底
```

## openspec/changes/storage-cache-refactor/design.md

- Source: openspec/changes/storage-cache-refactor/design.md
- Lines: 1-121
- SHA256: 293fba52d3bef2adc55fa3d8667879fc7806918922f2f273d05efbc870c862ac

[TRUNCATED]

```md
## Context

当前系统使用 SQLite 作为唯一存储后端，所有行情数据（`MarketData`）以 JSON 字符串形式存储在磁盘文件中。每次刷新行情时，系统需要从数据源采集数据、写入 SQLite，读取时再反序列化。这种架构在高频读写场景下存在性能瓶颈。

系统包含以下核心实体：
- `MarketData`: 行情 K 线数据（高频读写，数据量大）
- `SymbolTimestamp`: 合约数据时间戳（中频读写）
- `ScheduledTask`: 定时任务配置（低频读写）
- 用户认证、交易复盘等低频业务表

约束条件：
- 单机部署，需要本地运行 Redis
- 需要保证系统可用性，即使部分存储组件故障
- 现有 SQLite 数据需要迁移或兼容

## Goals / Non-Goals

**Goals:**
- 引入 Redis 作为热数据缓存层，提升行情数据读取性能（TTL 30 天）
- 引入 MySQL 作为主要持久化存储，替代 SQLite 承担日常读写
- 保留 SQLite 作为最终兜底方案，保证系统可用性
- 实现三级降级策略：Redis → MySQL → SQLite
- 刷新接口双写：数据同步写入 Redis 和 MySQL
- 启动时初始化多存储后端，检测可用性

**Non-Goals:**
- 不迁移用户认证、交易复盘等低频业务表（保持 SQLite）
- 不改造前端页面
- 不统一 API 接口规范（留给后续 change）
- 不实现分布式部署或集群方案

## Decisions

### 1. 存储分层策略

**决策**: 采用 Redis（缓存）+ MySQL（持久化）+ SQLite（兜底）三级架构。

**理由**:
- Redis 提供亚毫秒级读取性能，适合行情数据高频访问
- MySQL 提供可靠的持久化能力和事务支持，替代 SQLite 成为主存储
- SQLite 保留作为最终兜底，保证极端情况下系统仍可运行
- 三级降级保证系统可用性，避免单点故障

**备选方案**:
- Redis + PostgreSQL: PostgreSQL 功能更强大，但部署复杂度更高，MySQL 更轻量
- 纯 Redis: 无法满足持久化需求，数据丢失风险高
- 纯 MySQL: 缺少缓存层，读取性能提升有限

### 2. 数据访问层设计

**决策**: 新增 `StorageManager` 抽象层，封装三级降级逻辑。

**理由**:
- 业务代码无需关心底层存储实现
- 降级策略集中管理，便于维护和测试
- 可通过配置切换存储后端，支持渐进式迁移

**备选方案**:
- 直接在业务代码中实现降级逻辑: 代码重复，难以维护
- 使用 ORM 插件: 灵活性不足，难以实现复杂的降级策略

### 3. 双写一致性策略

**决策**: 刷新接口采用同步双写，先写 MySQL 再更新 Redis。

**理由**:
- MySQL 作为持久化主存储，必须先保证数据落盘
- Redis 作为缓存，写入失败不影响数据持久化
- 同步双写保证数据一致性，避免异步延迟导致的数据不一致

**备选方案**:
- 异步双写: 性能更好，但可能出现数据不一致
- 仅写 MySQL，Redis 通过缓存未命中回填: 首次读取性能差

### 4. Redis 缓存策略

**决策**: 行情数据 TTL 30 天，缓存未命中时从 MySQL 读取并回填。

**理由**:
- 30 天覆盖大部分行情数据的活跃访问周期
```

Full source: openspec/changes/storage-cache-refactor/design.md

## openspec/changes/storage-cache-refactor/tasks.md

- Source: openspec/changes/storage-cache-refactor/tasks.md
- Lines: 1-46
- SHA256: 295733c66c5b2599cac51356e7344d17c579e674e667ec24c6b77d62ee519fcd

```md
## 1. 依赖与配置

- [ ] 1.1 添加 `redis`、`pymysql` 依赖到 `requirements.txt`
- [ ] 1.2 在 `app/config.py` 中新增 Redis 和 MySQL 连接配置项（host、port、user、password、database）
- [ ] 1.3 在 `docker-compose.yml` 中新增 Redis 和 MySQL 服务定义

## 2. 数据库模型与初始化

- [ ] 2.1 创建 `app/mysql_database.py`，实现 MySQL 连接引擎和 SessionLocal
- [ ] 2.2 创建 `app/redis_client.py`，实现 Redis 连接池和客户端封装
- [ ] 2.3 在 `app/main.py` 的 `lifespan` 中初始化 Redis 和 MySQL 连接，检测可用性
- [ ] 2.4 创建 MySQL 表结构初始化脚本（复用现有 ORM 模型）

## 3. 数据迁移

- [ ] 3.1 创建数据迁移脚本，从 SQLite 读取历史数据并写入 MySQL
- [ ] 3.2 在应用启动时检测 MySQL 表是否为空，自动触发迁移

## 4. 存储管理层

- [ ] 4.1 创建 `app/storage_manager.py`，实现 `StorageManager` 抽象层
- [ ] 4.2 实现 Redis 缓存读取逻辑（命中返回，未命中回源 MySQL 并回填）
- [ ] 4.3 实现 MySQL 持久化读写逻辑
- [ ] 4.4 实现三级降级策略（Redis → MySQL → SQLite）
- [ ] 4.5 实现降级状态检测和恢复机制

## 5. 双写与缓存一致性

- [ ] 5.1 实现刷新接口双写逻辑：先删 Redis 缓存，再写 MySQL，最后更新 Redis
- [ ] 5.2 实现双写顺序保证和错误处理（MySQL 失败回滚，Redis 失败记录日志）
- [ ] 5.3 实现缓存未命中回填逻辑（TTL 30 天）

## 6. 接口改造

- [ ] 6.1 改造 `app/api/data.py` 中的行情数据读取接口，使用 `StorageManager`
- [ ] 6.2 改造 `app/api/data.py` 中的刷新接口，使用双写逻辑
- [ ] 6.3 改造 `app/services/collector.py`，使用 `StorageManager` 写入数据

## 7. 测试与验证

- [ ] 7.1 验证 Redis 缓存命中场景
- [ ] 7.2 验证 Redis 缓存未命中回源 MySQL 场景
- [ ] 7.3 验证 Redis 不可用降级到 MySQL 场景
- [ ] 7.4 验证 Redis 和 MySQL 均不可用降级到 SQLite 场景
- [ ] 7.5 验证刷新接口双写一致性
- [ ] 7.6 验证数据迁移完整性
```

## openspec/changes/storage-cache-refactor/specs/dual-write-consistency/spec.md

- Source: openspec/changes/storage-cache-refactor/specs/dual-write-consistency/spec.md
- Lines: 1-30
- SHA256: 6fd0c00433c73329adc93f97ea5a8b744d9a74148d7db40f30c843e92480b91e

```md
## ADDED Requirements

### Requirement: 刷新接口双写
系统应当在刷新接口中实现 Redis 和 MySQL 的同步双写，保证数据一致性。

#### Scenario: 刷新接口双写成功
- **WHEN** 用户调用刷新接口更新行情数据
- **THEN** 系统先删除 Redis 缓存，再写入 MySQL，最后更新 Redis 缓存

#### Scenario: MySQL 写入失败
- **WHEN** 刷新接口写入 MySQL 失败
- **THEN** 系统返回错误响应，不更新 Redis 缓存，保证数据一致性

#### Scenario: Redis 写入失败
- **WHEN** 刷新接口写入 MySQL 成功但 Redis 写入失败
- **THEN** 系统返回成功响应（MySQL 数据已持久化），输出 Redis 写入失败警告日志

### Requirement: 双写顺序保证
系统应当保证双写的顺序：先写 MySQL，再更新 Redis。

#### Scenario: 双写顺序
- **WHEN** 刷新接口执行双写
- **THEN** 系统先写入 MySQL 并确认成功，再更新 Redis 缓存

### Requirement: 缓存回填一致性
系统应当在缓存未命中回填时保证回填数据与 MySQL 数据一致。

#### Scenario: 缓存未命中回填
- **WHEN** Redis 缓存未命中且从 MySQL 读取数据
- **THEN** 系统将 MySQL 数据回填到 Redis，TTL 设置为 30 天
```

## openspec/changes/storage-cache-refactor/specs/mysql-persistence/spec.md

- Source: openspec/changes/storage-cache-refactor/specs/mysql-persistence/spec.md
- Lines: 1-41
- SHA256: f23680f51345a871dddbf3d18eacf6a53de1a20945d02516ffc55a2eb500af75

```md
## ADDED Requirements

### Requirement: MySQL 连接管理
系统应当提供 MySQL 连接管理能力，包括连接初始化、连接池管理和表结构初始化。

#### Scenario: MySQL 连接初始化成功
- **WHEN** 应用启动且 MySQL 配置有效
- **THEN** 系统成功建立 MySQL 连接并创建必要的表结构

#### Scenario: MySQL 连接初始化失败
- **WHEN** 应用启动但 MySQL 服务不可用
- **THEN** 系统输出错误日志并标记 MySQL 为不可用状态，但不阻止应用启动

### Requirement: 行情数据持久化存储
系统应当使用 MySQL 作为行情数据的主要持久化存储，替代 SQLite。

#### Scenario: 行情数据写入 MySQL
- **WHEN** 刷新接口接收到行情数据更新请求
- **THEN** 系统将数据写入 MySQL 的 `market_data` 表

#### Scenario: 行情数据从 MySQL 读取
- **WHEN** Redis 缓存未命中且 MySQL 可用
- **THEN** 系统从 MySQL 读取行情数据并返回

### Requirement: MySQL 表结构迁移
系统应当提供从 SQLite 到 MySQL 的数据迁移能力，保证历史数据不丢失。

#### Scenario: 首次启动数据迁移
- **WHEN** 应用首次启动且 MySQL 可用但表为空
- **THEN** 系统从 SQLite 读取历史数据并迁移到 MySQL

#### Scenario: 迁移完成
- **WHEN** 数据迁移完成
- **THEN** 系统输出迁移成功日志，后续读写直接操作 MySQL

### Requirement: MySQL 事务支持
系统应当使用 MySQL 事务保证数据写入的原子性和一致性。

#### Scenario: 批量写入事务
- **WHEN** 刷新接口需要写入多个品种的行情数据
- **THEN** 系统使用事务保证所有数据要么全部写入成功，要么全部回滚
```

## openspec/changes/storage-cache-refactor/specs/redis-cache-layer/spec.md

- Source: openspec/changes/storage-cache-refactor/specs/redis-cache-layer/spec.md
- Lines: 1-41
- SHA256: a0b5c7023c88339957ce5576ad326474fd0ab913756c223573766cbc9e23f761

```md
## ADDED Requirements

### Requirement: Redis 缓存连接管理
系统应当提供 Redis 连接管理能力，包括连接初始化、健康检查和连接池管理。

#### Scenario: Redis 连接初始化成功
- **WHEN** 应用启动且 Redis 配置有效
- **THEN** 系统成功建立 Redis 连接并输出初始化成功日志

#### Scenario: Redis 连接初始化失败
- **WHEN** 应用启动但 Redis 服务不可用
- **THEN** 系统输出警告日志并标记 Redis 为不可用状态，但不阻止应用启动

### Requirement: 行情数据缓存读取
系统应当优先从 Redis 读取行情数据，缓存未命中时回源 MySQL。

#### Scenario: Redis 缓存命中
- **WHEN** 用户请求行情数据且 Redis 中存在该数据
- **THEN** 系统直接从 Redis 返回数据，不访问 MySQL

#### Scenario: Redis 缓存未命中
- **WHEN** 用户请求行情数据但 Redis 中不存在该数据
- **THEN** 系统从 MySQL 读取数据，回填到 Redis（TTL 30 天），并返回数据

### Requirement: Redis 缓存 TTL 管理
系统应当为缓存数据设置 30 天过期时间，过期后自动清理。

#### Scenario: 缓存数据过期
- **WHEN** 缓存数据超过 30 天未访问
- **THEN** Redis 自动清理该数据，下次访问时从 MySQL 重新加载

#### Scenario: 缓存数据续期
- **WHEN** 用户访问已缓存的行情数据
- **THEN** 系统返回缓存数据，TTL 保持为 30 天（不续期）

### Requirement: Redis 缓存失效
系统应当在数据更新时主动失效相关缓存，保证数据一致性。

#### Scenario: 刷新接口触发缓存失效
- **WHEN** 用户调用刷新接口更新行情数据
- **THEN** 系统先删除 Redis 中对应的缓存数据，再执行双写流程
```

## openspec/changes/storage-cache-refactor/specs/storage-fallback/spec.md

- Source: openspec/changes/storage-cache-refactor/specs/storage-fallback/spec.md
- Lines: 1-38
- SHA256: feaf531250861c109bc48d06d684ff2520cb48269397211bdcefe7cda9013714

```md
## ADDED Requirements

### Requirement: 三级降级策略
系统应当实现 Redis → MySQL → SQLite 的三级降级读取策略，保证系统可用性。

#### Scenario: 正常模式（Redis + MySQL 可用）
- **WHEN** Redis 和 MySQL 均可用
- **THEN** 系统优先从 Redis 读取，未命中时回源 MySQL

#### Scenario: Redis 不可用降级
- **WHEN** Redis 服务不可用但 MySQL 可用
- **THEN** 系统直接从 MySQL 读取数据，跳过 Redis 缓存层

#### Scenario: Redis 和 MySQL 均不可用降级
- **WHEN** Redis 和 MySQL 均不可用
- **THEN** 系统降级到 SQLite，从 SQLite 读取数据并保证基础服务可用

### Requirement: 降级状态检测
系统应当能够检测存储后端的可用性状态，并在降级时输出明确的日志。

#### Scenario: 启动时降级检测
- **WHEN** 应用启动时检测到 Redis 或 MySQL 不可用
- **THEN** 系统输出降级警告日志，说明当前使用的存储模式

#### Scenario: 运行时降级触发
- **WHEN** 运行中 Redis 或 MySQL 连接异常
- **THEN** 系统自动切换到降级模式，输出降级日志，并继续提供服务

### Requirement: 降级恢复
系统应当能够在存储后端恢复后自动恢复到正常模式。

#### Scenario: Redis 恢复
- **WHEN** Redis 从不可用状态恢复
- **THEN** 系统自动恢复到 Redis + MySQL 模式，后续读取优先使用 Redis

#### Scenario: MySQL 恢复
- **WHEN** MySQL 从不可用状态恢复
- **THEN** 系统自动恢复到 Redis + MySQL 模式，SQLite 降级结束
```

