这篇记录日历系统最后阶段的性能优化和部署方案,给这个系列画个句号。
查询性能优化
上线前做压测时,事件查询接口在数据量上去后可能出现明显的延迟。主要瓶颈有两个。
索引优化
最常见的查询是「获取某个时间范围内的所有事件」,对应 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 个实例。如果用户有多个这样的重复事件,展开计算就变得很耗时。
解决方案:
- 预计算窗口:对于高频查询的未来几个月事件实例,做预展开并缓存
- 限制展开范围:API 层面强制限制查询范围(如不超过 90 天),避免一次展开太多
- 惰性展开:只在用户实际查看某天/某周时展开那部分,而不是一次返回整月所有实例
Docker 部署方案
采用 Docker Compose 部署,整体架构:
- API 服务 x 2(负载均衡)
- PostgreSQL(数据存储)
- Redis(缓存 + 延迟队列)
- Nginx(反向代理 + 静态资源)
两个 API 实例通过 Nginx upstream 做负载均衡,健康检查走 /healthz 接口。
数据库用单机 PostgreSQL,数据量不大时暂时不需要分库分表。备份用 pg_dump 每天凌晨全量备份到对象存储。
CI/CD 流水线
用 GitHub Actions 搭建:
- 代码推送 -> 触发 lint + 单元测试
- PR 合并到 main -> 构建 Docker 镜像,推送到镜像仓库
- 手动触发 -> 到服务器执行
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%
上线后可能遇到的问题
-
时区问题:非整数时区偏移(如 UTC+5:30 印度时区)在 RRULE 展开时可能需要特殊处理。建议补充非整数时区的测试用例。
-
WebSocket 重连问题:前端 WebSocket 断线后的重连逻辑需要注意避免创建多个连接但不关闭旧的,导致收到重复通知。
-
缓存一致性:事件更新后缓存失效的范围需要考虑完整——修改一个重复事件的规则,需要失效的不只是这一天的缓存,而是这个重复事件影响的所有日期范围。
系列回顾
这个日历系统从需求分析到上线,完整经历了一个项目的生命周期。技术上最有收获的几个点:
- RFC 5545 / iCalendar 标准的深度理解
- RRULE 重复规则的解析与展开
- 时区处理的复杂性(比预想的难很多)
- Redis 延迟队列在实际场景中的应用