cosmo/DATA_STRATEGY_OPTIMIZATION.md

9.2 KiB
Raw Permalink Blame History

数据请求策略优化总结

📋 问题发现

原始问题

根据最初的规划,为了减少数据量:

  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)

修改前:

// 无参数请求,后端返回最近 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万 -

总结

关键改进

  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