日历系统开发(九):性能优化与上线部署

这篇记录日历系统最后阶段的性能优化和部署方案,给这个系列画个句号。

查询性能优化

上线前做压测时,事件查询接口在数据量上去后可能出现明显的延迟。主要瓶颈有两个。

索引优化

最常见的查询是「获取某个时间范围内的所有事件」,对应 SQL 是 WHERE start_time <= ? AND end_time >= ?。建议同时建立合理的联合索引来优化查询性能。

另外,按用户查询的场景非常多,(user_id, start_time) 联合索引是必须的。

缓存策略

对于日视图/周视图这种高频查询,可以加 Redis 缓存:

  • 缓存 key:calendar:events:{user_id}:{date_range_hash}
  • 过期时间:5 分钟
  • 事件变更时主动失效相关缓存

这可以有效降低数据库查询压力。

分页优化

事件列表 API 可以考虑从 offset 分页改为 cursor 分页(基于 start_time),避免大 offset 时的性能劣化。

重复事件展开的性能问题

这是整个优化过程中需要关注的部分。一个「每天重复,永不结束」的事件,查询某个月的日历视图时需要动态展开 30 个实例。如果用户有多个这样的重复事件,展开计算就变得很耗时。

解决方案:

  1. 预计算窗口:对于高频查询的未来几个月事件实例,做预展开并缓存
  2. 限制展开范围:API 层面强制限制查询范围(如不超过 90 天),避免一次展开太多
  3. 惰性展开:只在用户实际查看某天/某周时展开那部分,而不是一次返回整月所有实例

Docker 部署方案

采用 Docker Compose 部署,整体架构:

  • API 服务 x 2(负载均衡)
  • PostgreSQL(数据存储)
  • Redis(缓存 + 延迟队列)
  • Nginx(反向代理 + 静态资源)

两个 API 实例通过 Nginx upstream 做负载均衡,健康检查走 /healthz 接口。

数据库用单机 PostgreSQL,数据量不大时暂时不需要分库分表。备份用 pg_dump 每天凌晨全量备份到对象存储。

CI/CD 流水线

用 GitHub Actions 搭建:

  1. 代码推送 -> 触发 lint + 单元测试
  2. PR 合并到 main -> 构建 Docker 镜像,推送到镜像仓库
  3. 手动触发 -> 到服务器执行 docker compose pull && docker compose up -d

自动部署可以后续考虑,如果是个人项目,手动触发一下也不麻烦。如果后续上 K8s 再考虑 ArgoCD 之类的。

监控告警

  • 应用监控:Prometheus + Grafana,采集 API 延迟、QPS、错误率
  • 基础设施:node_exporter 采集 CPU/内存/磁盘指标
  • 告警:Grafana Alert -> 通知渠道
  • 日志:结构化日志输出到 stdout,Docker 层面用 json-file driver,配合 docker logs 查看

核心告警规则建议:

  • API P99 延迟超过阈值
  • 错误率超过 1%
  • Redis 连接失败
  • 磁盘使用率超过 80%

上线后可能遇到的问题

  1. 时区问题:非整数时区偏移(如 UTC+5:30 印度时区)在 RRULE 展开时可能需要特殊处理。建议补充非整数时区的测试用例。

  2. WebSocket 重连问题:前端 WebSocket 断线后的重连逻辑需要注意避免创建多个连接但不关闭旧的,导致收到重复通知。

  3. 缓存一致性:事件更新后缓存失效的范围需要考虑完整——修改一个重复事件的规则,需要失效的不只是这一天的缓存,而是这个重复事件影响的所有日期范围。

系列回顾

这个日历系统从需求分析到上线,完整经历了一个项目的生命周期。技术上最有收获的几个点:

  • RFC 5545 / iCalendar 标准的深度理解
  • RRULE 重复规则的解析与展开
  • 时区处理的复杂性(比预想的难很多)
  • Redis 延迟队列在实际场景中的应用