Postgres 可以替代 Redis 作为缓存吗?

Raphael De Lio 2024-07-22 17:19:00

 

近期,一篇名为“Postgres 可以替代 Redis 作为缓存吗?”的文章在Medium迅速出圈,这一新颖的话题,似乎能带来不少实际项目的启示,下面跟随着作者Raphael De Lio来解读这一疑问。

 
 

 

图片

 

我在Twitter上询问大家了一个问题:你想到的第一个消息队列是什么?
 
其中一个回答引起我的注意:Postgres
 
“使用 Postgres 作为消息队列,并使用 SKIP LOCKED 代替 Kafka(如果你只需要一个消息队列的话)”。— Stephan Schmid
 
更令我惊讶的是,还有提出使用Postgres作为缓存来替代 Redis的观点。
 
“使用 Postgres 进行缓存,而不是 Redis。使用 UNLOGGED 表和 TEXT 作为 JSON 数据类型。存储过程可以使用 ChatGPT 编写,添加和强制执行数据的到期日期,就像在 Redis一样”。— Stephan Schmidt
 
图片
 
在我学习 Redis 的过程中,我经常听到很多人(来自 Redis)提倡:Redis可以成为你的主要数据库。
 
这可能是一个好主意。Redis是一个真正的数据库,只是因为它速度非常快,可以在一秒钟内执行数百万次操作,被大家常用来作为缓存。
 
图片
 
而当我看到最喜欢的关系型数据库 Postgres 可以取代我最喜欢的非关系型数据库 Redis 时,我的世界发生了翻天覆地的变化。我应该用Postgres取代 Redis,还是用Redis取代Postgres?
 
在考虑这个问题之前,我想先搞清楚:Postgres作为缓存真的是个好主意吗?它真的可以取代 Redis 吗?
 
Stephan Schmidt主张用 Postgres 替换 Redis(实际上他主张用 Postgres 替换一切),他认为这样做可以消除一定的复杂性。(请阅读:https://medium.com/@AmazingCTO)
 
“一切都用 Postgres 吧(如何降低复杂性并加快速度” — Stephan Schmid
 
然而,他并不是唯一一个主张更换 Redis 的人,也有人做了同样的事情:
 
图片

 

但首先,我为什么要用 Postgres 替换 Redis?

 

Stephan 已经给出了两个理由:复杂性更低和变化更快。是否还有其他驱动因素呢?
 
使用 Postgres 作为缓存虽不是常见的选择,但在某些情况下具有一定的优势:
 
 
统一技术栈     
 
Postgres 是最流行的数据库之一,且开源免费,将其用作缓存可以减少管理和维护多个数据库系统的工作,从而简化技术堆栈。
 
 
熟悉的界面
 
Postgres 支持复杂的查询和索引,特别是对于精通 SQL的人来说,直接在缓存层内处理高级数据检索和转换任务会更加容易。
 
 
成本
 
某些情况下,使用现有的 Postgres 资源进行缓存,可能比部署单独的缓存解决方案(如 Redis)更具成本效益。尤其是在基础设施预算有限的环境中,将 Postgres 同时用作主存储和缓存可以提高资源利用率。

 

我们对缓存服务有怎样的目标?

 

传统缓存服务(例如 Redis)具有一系列可增强应用程序性能和可扩展性的功能,Postgres 是否真的可以取代 Redis,需要从以下几个关键层面考量:
 
 
表现
 
缓存服务的主要目标,是通过加快数据访问速度,来提高应用程序的性能。 
 
高性能缓存解决方案可以处理高吞吐量工作负载,并提供亚毫秒级的响应时间,从而显著加快检索数据的进程。
 
 
删除策略
 
通过设置缓存数据的过期时间,让过期数据在指定时间后自动从缓存中删除。确保过期数据不会提供给应用程序。 
 
 
逐出策略
 
缓存服务通常将其数据保存在内存中,而内存一般是有限的。因此,需要设置逐出策略让我们自动删除不常用的数据,为新数据腾出空间。
 
 
键值存储
 
大多数缓存服务的核心都是以键值对的形式存储数据。这种简单但功能强大的模型可以快速检索数据,从而轻松高效地存储和访问常用数据。
 
简而言之,缓存服务需要更快地访问数据并返回尽可能最新的数据。

 

怎样才能将 Postgres 变成缓存?

 

Stephan 和 Martin 都表示,我们可以通过使用 UNLOGGED 表将 Postgres 变成缓存服务。
 
结合Martin Heinz《你不需要专用的缓存服务 - PostgreSQL 作为缓存这篇文章内容(链接:https://martinheinz.dev/blog/105),得到了这些答案:
 
 
未记录表和预写日志
 
Postgres 中的未记录表是一种防止特定表生成 WAL(预写日志)的方法。 
 
反言之,WAL可确保对数据库所做的所有更改,在实际写入数据库文件之前都已记录。在系统崩溃和断电等极端情况的时候,就有助于维护数据完整性。
 
补充说明:Redis提供了一种类似的机制,称为仅附加文件 (AOF) ,它不仅提供了一种在 Redis中持久保存数据的机制,而且还以类似的方式运行,即记录在 Redis 中执行的所有操作。如果使用 Redis 作为主数据库,我们会启用 AOF ,而如果使用 Postgres 作为缓存,我们会关闭(在特定表上)WAL。
 
 
关闭WAL提高性能
 
对于每次数据修改,Postgres 必须更改写入 WAL 和数据文件。这使所需的写入操作数量加倍。
 
除此之外,为了确保每个已提交的事务都物理写入磁盘,WAL被设计为强制执行磁盘刷新 (fsync)。频繁的磁盘刷新操作会影响性能,因为它们会引入等待磁盘确认数据已安全写入的延迟。
 
 
放弃坚持
 
未记录表不是持久的。 
 
Postgres会使用WAL来重放和应用自上次检查点以来所做的任何更改,如果我 们没有此日志记录,则无法通过重放WAL记录将数据库恢复到一致状态。但这也是缓存的一大特点。
 
  •  
  •  
  •  
  •  
  •  
  •  
  •  

		
CREATE UNLOGGED TABLE cache ( id serial PRIMARY KEY, key text UNIQUE NOT NULL, value jsonb,    inserted_at timestamp);
CREATE INDEX idx_cache_key ON cache (key);
 
 
存储过程的过期
 
Martin 和 Stephan 都表示,可以使用存储过程来实现过期,这会导致一定的复杂性。
 
因此,Stephan甚至更进一步建议我们使用ChatGPT来编写存储过程。
 
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

		
CREATE OR REPLACE PROCEDURE expire_rows (retention_period INTERVAL) AS$$BEGIN DELETE FROM cache WHERE inserted_at < NOW() - retention_period;
COMMIT;END;$$ LANGUAGE plpgsql;
CALL expire_rows('60 minutes'); -- This will remove rows older than 1 hour
 
然而事实是,大多数现代应用程序不再依赖存储过程,而且现在很多软件开发人员都反对使用存储过程,以此避免把业务逻辑泄露到数据库中,且随着存储数据的增加,管理和理解会变得更为麻烦。
 
此外,我们还需要按计划调用这些存储过程。为此,我们需要使用一个扩展 pg_cron 。安装扩展后,我们仍然需要创建调度程序:
 
  •  
  •  
  •  
  •  

		
-- Create a schedule to run the procedure every hourSELECT cron.schedule('0 * * * *', $$CALL expire_rows('1 hour');$$);-- List all scheduled jobsSELECT * FROM cron.job;
 
然而,复杂性还在持续增加。
 
 
存储过程逐出策略
 
Stephan在他的文章中没有提到逐出策略,而Martin则表示,由于过期可以保持存储大小,因此也可以作为一个选择。
 
但是,如果仍然想要启用逐出策略,Martin建议在我们的表中添加一个名为 last_read_timestamp的列,并偶尔运行另一个存储过程来实现“最近使用”(LRU)逐出策略。
 
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

		
CREATE OR REPLACE PROCEDURE lru_eviction(eviction_count INTEGER) AS$$BEGIN DELETE FROM cache WHERE ctid IN ( SELECT ctid FROM cache ORDER BY last_read_timestamp ASC LIMIT eviction_count );
COMMIT;END;$$ LANGUAGE plpgsql;-- Call the procedure to evict a specified number of rowsCALL lru_eviction(10); -- This will remove the 10 least recently accessed rows
 
Redis 提供了八种现成的逐出策略(官方文档:https://redis.io/docs/latest/develop/reference/eviction/)。如果想要为“Postgres Cache”设置另一种逐出策略?问ChatGPT即可。

 

性能表现如何?

 

性能表现是缓存服务选型的决定性因素,因为我们需要缓存服务的主要原因是想更快地访问的数据。
 
Greg Sabino Mullane在他的文章《PostgreSQL Unlogged Tables — Look Ma, No WAL! 中展示了测试结果(原文链接:https://www.crunchydata.com/blog/postgresl-unlogged-tables),比较了 Postgres 中 UNLOGGED 和 LOGGED 表的性能。数据显示, 写入 UNLOGGED 表的性能是在 LOGGED 表中执行相同操作的两倍。具体数据如下:
 
  • 未记录表
延迟:2.059 ms
TPS:485,706168
 
  • 记录表
延迟:5.949 ms
TPS:168,087557
 
 
读写表现如何呢?
 
这就是最大的问题:Postsgres 性能优化策略依赖于共享缓冲区。
 
共享缓冲区将经常访问的数据和索引直接存储在内存中,使其可以快速访问,并减少从磁盘读取的需要,提高已记录和未记录表的查询性能和数据访问能力。
 
未记录表可能留在这些缓冲区中,但如果它们变得太大或内存有限,它们则会被写入磁盘。因此,未记录表主要提高写入速度,而不是读取速度。
 
为了证明这一点,我使用进行了快速实验 pgbench (具体操作请见:GitHub - raphaeldelio/redis-postgres-cache-benchmark)
 
结果表明,记录表和未记录表的性能实际上非常相似,读取这两种类型的表平均需要大约 0.650 ms。具体数据如下:
 
  • 未记录表 
延迟:0.679 ms
TPS:14.724,204
 
  • 记录表
延迟:0.627ms
TPS :15.946,025
 
这一结果测试进一步验证:未记录表主要增强了写入性能。
 
对于读取操作,未记录表的性能优势并不明显,因为记录表和未记录表都同样受益于 Postgres 的缓存和优化策略。
 
 
与 Redis 相比性能如何?
 
除了对 Postgres 进行基准测试之外,我还对 Redis 进行了实验。(具体操作请见:GitHub - raphaeldelio/redis-postgres-cache-benchmark)。结果显示,Redis在读写操作方面具有显著的性能优势: 
 
  • 读取
延迟 (p50) :0.095ms
每秒请求数 (RPS) :892.857,12 
 
  • 写入
延迟 (p50) :0.103ms
每秒请求数 (RPS) :892857,12 
 
性能比较显示,Redis 在写入和读取操作方面都明显优于 Postgres:Redis只有 0.095ms的延迟, Postgres未记录表有0.679ms。
 
Redis还能处理更高的请求率,每秒 892,857.12 个请求,而 Postgres 每秒只能处理 15,946.025 个请求。 
 
在写入操作方面,我们也可以看到Redis提供了更优异的性能,吞吐量明显更高,延迟也更低。
 
 
如果我在 RAM 中运行 Postgres 会怎样?
 
在审查本文的过程中,Xebia的同事Maksym Fedorov表示:
 
“ 如果现在在与内存映射文件对应的表空间中创建未记录表会怎么样?我猜我们会看到完全不同的数字。”
 
为了测试这一点,我使用保存在 RAM 中的 Postgres 数据运行了基准测试。 令人惊讶的是,结果没有任何改善。基准测试显示:
 
  • 读取
延迟:0.652ms
每秒请求数 (TPS) :15329,776954
 
经过进一步研究,我了解到,即使数据存储在 RAM 中,在Postgres 的共享缓冲区内访问数据也会产生额外成本,这些成本来自管理锁,以及数据完整性和并发访问所需的其他内部进程。
 
而且,Postgres总是先检查数据是否在共享缓冲区中,如果不在,它会先将数据从tmpfs文件系统复制到共享缓冲区中,然后再提供服务,即使数据库保存在 RAM中。

 

我应该用 Postgres 替换 Redis 吗?

 

综上所述,如果您需要缓存服务来提高写入性能,可以使用未记录表优化 Postgres。但是,虽然未记录表比记录表提供更好的写入性能,但与 Redis 相比仍然不足。
 
使用缓存服务的主要原因是缩短数据检索时间。未记录的表不会提高读取性能,而Redis则以极快的读取优势作为更优选择。
 
此外,Redis有助于防止大量低成本查询访问数据库,这是未记录表无法提供的优势。Redis还提供内置功能,如过期、逐出策略等,这些功能在 Postgres 中很难实现。
 
尽管对某些人来说,管理 Postgres 似乎更容易,但将 Postgres 变成缓存并不能提供专用缓存服务的优势。同时,Redis 的学习、部署和使用都很简单,而且很有趣。
 
所以为了获得更快的性能和简单性,选择像Redis这样真正的缓存服务似乎才是更明智的选择。

 

作者丨Raphael De Lio   编译丨Rio

来源丨https://medium.com/redis-with-raphael-de-lio/can-postgres-replace-redis-as-a-cache-f6cba13386dc
 
*本文为dbaplus社群编译整理,如需转载请取得授权并标明出处!欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
 
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告