cosmo/DATA_STRATEGY_OPTIMIZATION.md

348 lines
9.2 KiB
Markdown
Raw Normal View History

2025-12-02 13:25:28 +00:00
# 数据请求策略优化总结
## 📋 问题发现
### 原始问题
根据最初的规划,为了减少数据量:
1. **时间轴**: 只显示每天 **00:00:00** 的位置数据
2. **首页**: 只显示用户打开时**当前小时**的位置数据
但实际实现中存在以下问题:
- ❌ 时间轴请求了**范围数据**(从 day_start 到 day_end导致返回多个时间点
- ❌ 首页请求了**最近 24 小时**的所有数据,而不是单个时间点
- ❌ positions 表中存储了大量冗余数据(每天多个时间点)
---
## ✅ 解决方案
### 核心策略
**所有请求都改为单个时间点查询**`start_time = end_time`
这样 NASA Horizons API 只返回单个时间点的数据,而不是时间范围内的多个点。
---
## 🔧 具体修改
### 1. 前端修改
#### 1.1 首页数据请求 (`frontend/src/hooks/useSpaceData.ts`)
**修改前**:
```tsx
// 无参数请求,后端返回最近 24 小时的数据
const data = await fetchCelestialPositions();
```
**修改后**:
```tsx
// 请求当前小时的单个时间点
const now = new Date();
now.setMinutes(0, 0, 0); // 圆整到小时
const data = await fetchCelestialPositions(
now.toISOString(),
now.toISOString(), // start = end单个时间点
'1h'
);
```
**API 请求示例**:
```http
GET /api/celestial/positions?start_time=2025-11-29T12:00:00Z&end_time=2025-11-29T12:00:00Z&step=1h
```
**返回数据**: 每个天体 1 个位置点(当前小时)
---
#### 1.2 时间轴数据请求 (`frontend/src/hooks/useHistoricalData.ts`)
**修改前**:
```tsx
const startDate = new Date(date);
const endDate = new Date(date);
endDate.setDate(endDate.getDate() + 1); // +1 天
const data = await fetchCelestialPositions(
startDate.toISOString(), // 2025-01-15T00:00:00Z
endDate.toISOString(), // 2025-01-16T00:00:00Z ❌ 多了1天
'1d'
);
```
**修改后**:
```tsx
// 圆整到 UTC 午夜
const targetDate = new Date(date);
targetDate.setUTCHours(0, 0, 0, 0);
// start = end只请求午夜这个时间点
const data = await fetchCelestialPositions(
targetDate.toISOString(), // 2025-01-15T00:00:00Z
targetDate.toISOString(), // 2025-01-15T00:00:00Z ✅ 单个时间点
'1d'
);
```
**API 请求示例**:
```http
GET /api/celestial/positions?start_time=2025-01-15T00:00:00Z&end_time=2025-01-15T00:00:00Z&step=1d
```
**返回数据**: 每个天体 1 个位置点2025-01-15 00:00:00
---
### 2. 后端缓存预热修改
#### 2.1 当前位置预热 (`backend/app/services/cache_preheat.py`)
**策略变更**:
- **修改前**: 加载最近 24 小时的所有数据
- **修改后**: 只加载当前小时最接近的单个时间点
**实现逻辑**:
```python
# 当前小时
now = datetime.utcnow()
current_hour = now.replace(minute=0, second=0, microsecond=0)
# 搜索窗口: 当前小时 ± 1 小时
start_window = current_hour - timedelta(hours=1)
end_window = current_hour + timedelta(hours=1)
# 找到最接近当前小时的位置
closest_pos = min(
recent_positions,
key=lambda p: abs((p.time - current_hour).total_seconds())
)
```
**Redis Key**:
```
positions:2025-11-29T12:00:00+00:00:2025-11-29T12:00:00+00:00:1h
```
---
#### 2.2 历史位置预热
**策略变更**:
- **修改前**: 每天加载所有时间点的数据
- **修改后**: 每天只加载 00:00:00 这个时间点
**实现逻辑**:
```python
# 目标时间: 当天的午夜 (00:00:00)
target_midnight = target_day.replace(hour=0, minute=0, second=0, microsecond=0)
# 搜索窗口: 午夜 ± 30 分钟
search_start = target_midnight - timedelta(minutes=30)
search_end = target_midnight + timedelta(minutes=30)
# 找到最接近午夜的位置
closest_pos = min(
positions,
key=lambda p: abs((p.time - target_midnight).total_seconds())
)
```
**Redis Key**:
```
positions:2025-11-26T00:00:00+00:00:2025-11-26T00:00:00+00:00:1d
positions:2025-11-27T00:00:00+00:00:2025-11-27T00:00:00+00:00:1d
positions:2025-11-28T00:00:00+00:00:2025-11-28T00:00:00+00:00:1d
```
---
## 📊 数据量对比
### 首页数据
| 指标 | 修改前 | 修改后 | 减少 |
|------|--------|--------|------|
| 时间范围 | 最近 24 小时 | 当前 1 小时 | - |
| 每个天体位置点数 | 可能 1-24 个 | 1 个 | **96%** ⬇️ |
| 总数据量20 天体) | 20-480 个点 | 20 个点 | **96%** ⬇️ |
### 时间轴数据3 天)
| 指标 | 修改前 | 修改后 | 减少 |
|------|--------|--------|------|
| 每天每个天体位置点数 | 2 个00:00 和 24:00 | 1 个00:00 | **50%** ⬇️ |
| 3 天总数据量20 天体) | 120 个点 | 60 个点 | **50%** ⬇️ |
### positions 表数据量(假设每小时更新一次)
| 场景 | 每个天体每天记录数 | 20 个天体每天总记录数 | 一年总记录数 |
|------|-------------------|---------------------|-------------|
| **首页**(每小时 1 条) | 24 | 480 | 175,200 |
| **时间轴**(每天 1 条) | 1 | 20 | 7,300 |
---
## 🎯 建议的数据管理策略
### 策略 1: 清空并重建 positions 表(推荐)
**原因**:
- 当前表中可能有大量冗余数据
- 重新开始可以确保数据质量
**步骤**:
```sql
-- 1. 清空 positions 表
TRUNCATE TABLE positions;
-- 2. 清空 nasa_cache 表(可选)
TRUNCATE TABLE nasa_cache;
```
**重新获取数据**:
```bash
# 1. 清空 Redis 缓存
curl -X POST "http://localhost:8000/api/celestial/cache/clear"
# 2. 访问首页触发数据获取(当前小时)
# 打开 http://localhost:5173
# 3. 访问时间轴触发数据获取(过去 3 天的午夜数据)
# 点击"时间轴"按钮
```
---
### 策略 2: 定期更新数据(管理后台实现)
#### 2.1 每小时更新当前位置
```python
@scheduler.scheduled_job('cron', minute=0) # 每小时整点
async def update_current_positions():
"""每小时更新一次所有天体的位置"""
now = datetime.utcnow()
current_hour = now.replace(minute=0, second=0, microsecond=0)
for body in all_bodies:
# 查询 NASA API单个时间点
positions = horizons_service.get_body_positions(
body.id,
current_hour,
current_hour, # start = end
"1h"
)
# 保存到数据库
await position_service.save_positions(
body.id, positions, "nasa_horizons", db
)
# 预热缓存
await preheat_current_positions()
```
**数据量**: 20 天体 × 1 条/小时 × 24 小时 = **480 条/天**
---
#### 2.2 每天凌晨更新历史数据(时间轴)
```python
@scheduler.scheduled_job('cron', hour=0, minute=0) # 每天凌晨
async def update_midnight_positions():
"""每天凌晨更新所有天体的午夜位置"""
now = datetime.utcnow()
midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
for body in all_bodies:
# 查询 NASA API单个时间点
positions = horizons_service.get_body_positions(
body.id,
midnight,
midnight, # start = end
"1d"
)
# 保存到数据库
await position_service.save_positions(
body.id, positions, "nasa_horizons", db
)
# 预热历史缓存3天
await preheat_historical_positions(days=3)
```
**数据量**: 20 天体 × 1 条/天 = **20 条/天**
---
### 策略 3: 数据清理(可选)
定期清理旧数据以节省存储空间:
```python
@scheduler.scheduled_job('cron', hour=3, minute=0) # 每天凌晨 3 点
async def cleanup_old_positions():
"""清理 30 天前的位置数据"""
cutoff_date = datetime.utcnow() - timedelta(days=30)
# 删除旧数据
await db.execute(
"DELETE FROM positions WHERE time < :cutoff",
{"cutoff": cutoff_date}
)
logger.info(f"Cleaned up positions older than {cutoff_date.date()}")
```
---
## 📈 性能优化效果
### 数据传输量
| 场景 | 修改前 | 修改后 | 优化 |
|------|--------|--------|------|
| 首页加载20 天体) | ~5-50KB | ~2KB | **75-95%** ⬇️ |
| 时间轴加载3 天20 天体) | ~10KB | ~6KB | **40%** ⬇️ |
### 数据库存储
| 周期 | 修改前 | 修改后 | 减少 |
|------|--------|--------|------|
| 每天新增记录 | 不确定(混乱) | 500 条(首页 480 + 时间轴 20 | - |
| 每年总记录数 | 不确定 | ~18万 | - |
---
## ✅ 总结
### 关键改进
1.**单点查询策略**: 所有请求都改为 `start_time = end_time`
2.**首页优化**: 只请求当前小时的单个时间点
3.**时间轴优化**: 只请求每天 00:00:00 的单个时间点
4.**缓存预热优化**: 预热逻辑匹配单点查询策略
5.**数据量减少**: 减少 50-96% 的数据传输和存储
### 数据规范
| 场景 | 时间点 | 频率 | 用途 |
|------|--------|------|------|
| **首页** | 每小时整点XX:00:00 | 每小时更新 1 次 | 显示当前位置 |
| **时间轴** | 每天午夜00:00:00 | 每天更新 1 次 | 显示历史轨迹 |
### 下一步操作
1. ⚠️ **重启前端和后端**,应用新的请求逻辑
2. ⚠️ **清空 positions 表**(可选但推荐),确保数据干净
3.**测试首页和时间轴**,验证数据正确性
4.**在管理后台实现定时任务**,每小时/每天更新数据
---
**文档版本**: v1.0
**最后更新**: 2025-11-29
**作者**: Cosmo Team