9.2 KiB
9.2 KiB
数据请求策略优化总结
📋 问题发现
原始问题
根据最初的规划,为了减少数据量:
- 时间轴: 只显示每天 00:00:00 的位置数据
- 首页: 只显示用户打开时当前小时的位置数据
但实际实现中存在以下问题:
- ❌ 时间轴请求了范围数据(从 day_start 到 day_end),导致返回多个时间点
- ❌ 首页请求了最近 24 小时的所有数据,而不是单个时间点
- ❌ positions 表中存储了大量冗余数据(每天多个时间点)
✅ 解决方案
核心策略
所有请求都改为单个时间点查询:start_time = end_time
这样 NASA Horizons API 只返回单个时间点的数据,而不是时间范围内的多个点。
🔧 具体修改
1. 前端修改
1.1 首页数据请求 (frontend/src/hooks/useSpaceData.ts)
修改前:
// 无参数请求,后端返回最近 24 小时的数据
const data = await fetchCelestialPositions();
修改后:
// 请求当前小时的单个时间点
const now = new Date();
now.setMinutes(0, 0, 0); // 圆整到小时
const data = await fetchCelestialPositions(
now.toISOString(),
now.toISOString(), // start = end,单个时间点
'1h'
);
API 请求示例:
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)
修改前:
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'
);
修改后:
// 圆整到 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 请求示例:
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 小时的所有数据
- 修改后: 只加载当前小时最接近的单个时间点
实现逻辑:
# 当前小时
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 这个时间点
实现逻辑:
# 目标时间: 当天的午夜 (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 表(推荐)
原因:
- 当前表中可能有大量冗余数据
- 重新开始可以确保数据质量
步骤:
-- 1. 清空 positions 表
TRUNCATE TABLE positions;
-- 2. 清空 nasa_cache 表(可选)
TRUNCATE TABLE nasa_cache;
重新获取数据:
# 1. 清空 Redis 缓存
curl -X POST "http://localhost:8000/api/celestial/cache/clear"
# 2. 访问首页触发数据获取(当前小时)
# 打开 http://localhost:5173
# 3. 访问时间轴触发数据获取(过去 3 天的午夜数据)
# 点击"时间轴"按钮
策略 2: 定期更新数据(管理后台实现)
2.1 每小时更新当前位置
@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 每天凌晨更新历史数据(时间轴)
@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: 数据清理(可选)
定期清理旧数据以节省存储空间:
@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万 | - |
✅ 总结
关键改进
- ✅ 单点查询策略: 所有请求都改为
start_time = end_time - ✅ 首页优化: 只请求当前小时的单个时间点
- ✅ 时间轴优化: 只请求每天 00:00:00 的单个时间点
- ✅ 缓存预热优化: 预热逻辑匹配单点查询策略
- ✅ 数据量减少: 减少 50-96% 的数据传输和存储
数据规范
| 场景 | 时间点 | 频率 | 用途 |
|---|---|---|---|
| 首页 | 每小时整点(XX:00:00) | 每小时更新 1 次 | 显示当前位置 |
| 时间轴 | 每天午夜(00:00:00) | 每天更新 1 次 | 显示历史轨迹 |
下一步操作
- ⚠️ 重启前端和后端,应用新的请求逻辑
- ⚠️ 清空 positions 表(可选但推荐),确保数据干净
- ✅ 测试首页和时间轴,验证数据正确性
- ✅ 在管理后台实现定时任务,每小时/每天更新数据
文档版本: v1.0 最后更新: 2025-11-29 作者: Cosmo Team