# CacheBasedPgsql **Repository Path**: marsdonne/cache-based-pgsql ## Basic Information - **Project Name**: CacheBasedPgsql - **Description**: 基于pgsql-unlogged-table的领域实体模型(via JPA)缓存应用示例 - **Primary Language**: Kotlin - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-23 - **Last Updated**: 2026-03-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Cache based on PostgreSQL UNLOGGED Table > 🎯 **用 PostgreSQL 原生能力实现轻量级缓存,保持 DDD 架构整洁** 一个基于 PostgreSQL `UNLOGGED TABLE` + `PL/pgSQL Function` + `Spring Data JPA` 的缓存方案示例。 **定位**:在适度场景下,作为 Redis 的替代方案,减少技术栈复杂度,保持领域模型纯净。 --- ## ✨ 核心价值 ``` ┌─────────────────────────────────────┐ │ 传统架构 │ │ Domain → RedisTemplate + JPA Repo │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 本方案 │ │ Domain → Single Repository (JPA) │ │ ↓ │ │ Cache Logic in PostgreSQL │ └─────────────────────────────────────┘ ``` - **✅ 架构整洁**:领域层仅依赖一个 `Repository`,缓存逻辑下沉至数据库层 - **✅ 事务友好**:缓存读写可与业务数据在同一事务中完成(查询场景) - **✅ 技术栈简化**:无需维护 Redis 集群,复用现有 PostgreSQL 运维体系 - **✅ 读写分离适配**:每个数据库节点独立维护缓存,自适应流量热点 --- ## 🔧 技术原理 ### 1. UNLOGGED TABLE 作为缓存存储 ```sql CREATE UNLOGGED TABLE users_unlogged ( id INT PRIMARY KEY, name TEXT, expires_at TIMESTAMPTZ -- 模拟 TTL ); -- ✅ 不写 WAL,写入性能更高 -- ✅ 重启后自动清空,天然"缓存失效" -- ❌ 不复制到从库(但 DDL/函数会复制) ``` ### 2. 缓存逻辑封装在 PG Function ```sql -- 核心函数:读取时检查 + 过期回写 + TTL 管理 SELECT * FROM get_user_with_cache(:id, :ttl_minutes); ``` - 优先查询 `users_unlogged`(缓存) - 未命中或过期 → 查询 `users_logged`(源数据)→ 回写缓存 - 支持参数化 TTL,懒删除 + 后台清理双策略 ### 3. Spring Data JPA 透明调用 ```java @Query(value = "SELECT * FROM get_user_with_cache(:id)", nativeQuery = true) Optional findByIdWithCache(@Param("id") Long id); // 领域服务无感知缓存存在,保持 DDD 纯净 ``` ### 4. 读写分离架构下的行为 | 节点 | 表结构 | 缓存数据 | 源数据 | |------|--------|----------|--------| | 主库 | ✅ | 🔄 独立维护 | ✅ 源 | | 从库 1 | ✅ (复制) | 🔄 **独立预热** | 🔄 异步复制 | | 从库 2 | ✅ (复制) | 🔄 **独立预热** | 🔄 异步复制 | > 💡 **关键认知**:每个数据库节点的 `UNLOGGED` 缓存是**本地化、最终一致**的,类似"进程内缓存",但可通过 SQL 直接操作和监控。 --- ## 🚀 快速开始 ### 1. 初始化数据库 启动应用通过Flyway实现自动化数据库迁移,或手动执行psql ```bash # 执行迁移脚本(创建表 + 函数 + 索引) psql -U your_user -d your_db -f src/main/resources/db/migration/V1__cache_setup.sql ``` ### 2. 配置应用 ```yaml # application.yml spring: datasource: url: jdbc:postgresql://localhost:5432/app # 读写分离时可配置多个数据源 cache: pg-unlogged: enabled: true default-ttl-minutes: 10 cleanup-cron: "0 */5 * * * *" # 后台清理频率 warmup-on-startup: false # 启动时是否预热(建议懒加载) ``` ### 3. 调用示例 ```java @Service public class UserService { private final UserRepository userRepository; public User getUser(Long id) { // 缓存逻辑完全透明 return userRepository.findByIdWithCache(id) .orElseThrow(() -> new UserNotFoundException(id)); } } ``` --- ## 📊 监控与运维 ### 关键指标视图 ```sql -- 缓存状态(每节点独立) SELECT * FROM local_cache_metrics; -- 命中率统计(应用层埋点推荐) -- cache.hit.ratio, cache.miss.count, cache.expired.count ``` ### 后台清理 - 默认通过 Spring `@Scheduled` 定期执行 `cleanup_expired_cache()` - 也可使用 `pg_cron` 在数据库层调度(需安装扩展) ### 表膨胀监控 ```sql -- 检查 unlogged 表大小 SELECT pg_size_pretty(pg_relation_size('users_unlogged')); -- 如增长过快,考虑:1) 缩短 TTL 2) 调整 VACUUM 策略 ``` --- ## ⚠️ 适用场景与边界 ### ✅ 推荐使用 - 读多写少,数据更新频率中等 - 缓存结构与主表一致,无需复杂数据结构 - 团队希望减少技术栈,复用 PostgreSQL 能力 - 可接受节点间缓存最终一致(非强一致) - 单表数据量可控(建议 < 500 万行,依硬件调整) ### ❌ 不推荐场景 - 超高并发(>10k QPS/节点),缓存竞争严重 - 需要跨服务/跨应用共享缓存 - 缓存数据结构复杂(Hash/Set/Sorted Set 等) - 对缓存可用性要求 99.99%+,无法容忍重启穿透 - 严格读写分离且从库复制延迟较大(>缓存 TTL) --- ## 🔍 常见问题 **Q: 数据库重启后缓存丢了怎么办?** A: 这是设计预期。缓存穿透会触发自动回写,数据会逐渐"热"起来。如需加速恢复,可开启 `warmup-on-startup` 预热热点数据。 **Q: 从库查询会拿到旧数据吗?** A: 有可能,因为:1) 主从复制有延迟 2) 各节点缓存独立。缓解方案:1) 关键查询路由主库 2) 设置较短 TTL 3) 监控复制延迟,延迟大时自动降级直查。 **Q: 缓存和源数据不一致怎么办?** A: 缓存本质是"副本",接受最终一致。业务强一致场景请直查 `users_logged` 或更新时同步删除缓存(双写策略)。 **Q: 能和多级缓存(Caffeine + Redis + PG)共存吗?** A: 可以。本方案定位为"数据库层缓存",可与应用层本地缓存组合,形成分级缓存体系。 --- ## 📚 参考资料 - [PostgreSQL UNLOGGED Tables](https://www.postgresql.org/docs/current/unlogged-tables.html) - [PL/pgSQL Function Best Practices](https://www.postgresql.org/docs/current/plpgsql.html) - [Spring Data JPA Native Queries](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation) --- ## 🤝 贡献与反馈 欢迎提交 Issue 或 PR! 如果你有生产环境的使用数据(命中率、性能对比、踩坑经验),非常期待分享 🙏 --- > 💡 **设计哲学**: > *"缓存是优化手段,不是架构核心。在适度场景下,用更简单的方案解决 80% 的问题,把复杂度留给真正需要的地方。"*