背景
1、产品的问题点
- meta cache (rel/catalog cache), plan cache是每个会话私有的内存.
2、问题点背后涉及的技术原理
- 正常的SQL执行过程包括sql parse, rewrite, plan, exec等几个过程, parse, rewrite, plan都比较耗费cpu, 在OLTP的短平快场景, 使用prepared statement可以避免每次调用都需要parse,rewrite,plan, 使用plan cache直接进入exec阶段(特定情况下的custom plan除外).
- 同时为了处理sql parse, rewrite, plan等, 数据库还需要一些meta cache, 例如访问过的表结构, 索引, 视图等定义.
- plan cache和meta cache都是会话进程私有的.
- PG 为每个会话分配一个backend process.
3、这个问题将影响哪些行业以及业务场景
- SaaS行业
- 分区超多的, 而且使用长连接和绑定变量
- 微服务
4、会导致什么问题?
- SaaS行业, 每个B端用户一套schema, 表超级多, 一个会话在整个生命周期内可能访问很多的数据库对象, 产生很多的plan cache、relcache, 单个会话对应的backend process占用大量内存. 进程多的话会导致内存消耗巨大, 导致OOM.
- 分区超多的, 而且使用长连接和绑定变量. 频繁更新的C端业务系统通常有这个特性, 例如共享单车, 单车数量多, 用户多, 需要通过分区提高垃圾回收和freeze的效率. 导致的问题同上.
- 微服务, 服务超级多, 导致与数据库的连接过多. 进而导致以上类似问题.
5、业务上应该如何避免这个坑
- 控制每个会话的生命周期, 从而避免长时间touch过多的relation, 导致内存爆增.
- 控制总连接数, 从而降低所有会话导致的整体内存使用.
- 使用高版本PG(大版本在逐渐优化)或pg_pathman, 避免即使只访问某个分区, 在plan过程依旧需要touch所有分区表.
- 使用pgbouncer连接池, 控制总连接数.
6、业务上避免这个坑牺牲了什么, 会引入什么新的问题
- 增加了复杂度, 很多初次使用PG的小伙伴不知道.
- 微服务很多的时候, 每个微服务至上的1个连接吧, 所以控制总连接数无解.
- 使用pgbouncer控制总连接的话必须使用statement或transaction level, 这样的话就不能使用prepared statement, 因为下次发起exec时可能已经不是之前那个backend process了.
7、数据库未来产品迭代如何修复这个坑
- 内置线程池
- global cache (rel catalog caches, plan)
- pg_backend_memory_contexts 查看内存上下文