Postgres 可以替代 Redis 作为缓存吗?
|
admin
2024年7月22日 23:1
本文热度 211
|
近期,一篇名为“Postgres 可以替代 Redis 作为缓存吗?”的文章在Medium迅速出圈,这一新颖的话题,似乎能带来不少实际项目的启示,下面跟随着作者Raphael De Lio来解读这一疑问。
先说结论:不能替代,还差得远。
我在Twitter上询问大家了一个问题:你想到的第一个消息队列是什么?
“使用 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,需要从以下几个关键层面考量:缓存服务的主要目标,是通过加快数据访问速度,来提高应用程序的性能。 高性能缓存解决方案可以处理高吞吐量工作负载,并提供亚毫秒级的响应时间,从而显著加快检索数据的进程。通过设置缓存数据的过期时间,让过期数据在指定时间后自动从缓存中删除。确保过期数据不会提供给应用程序。 缓存服务通常将其数据保存在内存中,而内存一般是有限的。因此,需要设置逐出策略让我们自动删除不常用的数据,为新数据腾出空间。大多数缓存服务的核心都是以键值对的形式存储数据。这种简单但功能强大的模型可以快速检索数据,从而轻松高效地存储和访问常用数据。简而言之,缓存服务需要更快地访问数据并返回尽可能最新的数据。
Stephan 和 Martin 都表示,我们可以通过使用 UNLOGGED 表将 Postgres 变成缓存服务。结合Martin Heinz《你不需要专用的缓存服务 - PostgreSQL 作为缓存》这篇文章内容(链接:https://martinheinz.dev/blog/105),得到了这些答案:Postgres 中的未记录表是一种防止特定表生成 WAL(预写日志)的方法。 反言之,WAL可确保对数据库所做的所有更改,在实际写入数据库文件之前都已记录。在系统崩溃和断电等极端情况的时候,就有助于维护数据完整性。补充说明:Redis提供了一种类似的机制,称为仅附加文件 (AOF) ,它不仅提供了一种在 Redis中持久保存数据的机制,而且还以类似的方式运行,即记录在 Redis 中执行的所有操作。如果使用 Redis 作为主数据库,我们会启用 AOF ,而如果使用 Postgres 作为缓存,我们会关闭(在特定表上)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 hour
SELECT cron.schedule('0 * * * *', $$CALL expire_rows('1 hour');$$);
-- List all scheduled jobs
SELECT * 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 rows
CALL lru_eviction(10); -- This will remove the 10 least recently accessed rows
Redis 提供了八种现成的逐出策略(官方文档:https://redis.io/docs/latest/develop/reference/eviction/)。如果想要为“Postgres Cache”设置另一种逐出策略?问ChatGPT即可。
性能表现是缓存服务选型的决定性因素,因为我们需要缓存服务的主要原因是想更快地访问的数据。这就是最大的问题:Postsgres 性能优化策略依赖于共享缓冲区。共享缓冲区将经常访问的数据和索引直接存储在内存中,使其可以快速访问,并减少从磁盘读取的需要,提高已记录和未记录表的查询性能和数据访问能力。未记录表可能留在这些缓冲区中,但如果它们变得太大或内存有限,它们则会被写入磁盘。因此,未记录表主要提高写入速度,而不是读取速度。为了证明这一点,我使用进行了快速实验 pgbench (具体操作请见:GitHub - raphaeldelio/redis-postgres-cache-benchmark)结果表明,记录表和未记录表的性能实际上非常相似,读取这两种类型的表平均需要大约 0.650 ms。具体数据如下:这一结果测试进一步验证:未记录表主要增强了写入性能。对于读取操作,未记录表的性能优势并不明显,因为记录表和未记录表都同样受益于 Postgres 的缓存和优化策略。除了对 Postgres 进行基准测试之外,我还对 Redis 进行了实验。(具体操作请见:GitHub - raphaeldelio/redis-postgres-cache-benchmark)。结果显示,Redis在读写操作方面具有显著的性能优势: 性能比较显示,Redis 在写入和读取操作方面都明显优于 Postgres:Redis只有 0.095ms的延迟, Postgres未记录表有0.679ms。Redis还能处理更高的请求率,每秒 892,857.12 个请求,而 Postgres 每秒只能处理 15,946.025 个请求。 在写入操作方面,我们也可以看到Redis提供了更优异的性能,吞吐量明显更高,延迟也更低。如果我在 RAM 中运行 Postgres 会怎样?在审查本文的过程中,Xebia的同事Maksym Fedorov表示:“ 如果现在在与内存映射文件对应的表空间中创建未记录表会怎么样?我猜我们会看到完全不同的数字。”为了测试这一点,我使用保存在 RAM 中的 Postgres 数据运行了基准测试。 令人惊讶的是,结果没有任何改善。基准测试显示:每秒请求数 (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
该文章在 2024/7/23 20:47:56 编辑过