cosmo/DATA_STRATEGY_OPTIMIZATION.md

348 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 数据请求策略优化总结
## 📋 问题发现
### 原始问题
根据最初的规划,为了减少数据量:
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