近期,一篇名為“Postgres 可以替代 Redis 作為緩存嗎?”的文章在Medium迅速出圈,這一新穎的話題,似乎能帶來不少實(shí)際項(xiàng)目的啟示,下面跟隨著作者Raphael De Lio來解讀這一疑問。
先說結(jié)論:不能替代,還差得遠(yuǎn)。
我在Twitter上詢問大家了一個(gè)問題:你想到的第一個(gè)消息隊(duì)列是什么?
其中一個(gè)回答引起我的注意:Postgres“使用 Postgres 作為消息隊(duì)列,并使用 SKIP LOCKED 代替 Kafka(如果你只需要一個(gè)消息隊(duì)列的話)”?!?Stephan Schmid更令我驚訝的是,還有提出使用Postgres作為緩存來替代 Redis的觀點(diǎn)。“使用 Postgres 進(jìn)行緩存,而不是 Redis。使用 UNLOGGED 表和 TEXT 作為 JSON 數(shù)據(jù)類型。存儲過程可以使用 ChatGPT 編寫,添加和強(qiáng)制執(zhí)行數(shù)據(jù)的到期日期,就像在 Redis一樣”?!?Stephan Schmidt在我學(xué)習(xí) Redis 的過程中,我經(jīng)常聽到很多人(來自 Redis)提倡:Redis可以成為你的主要數(shù)據(jù)庫。這可能是一個(gè)好主意。Redis是一個(gè)真正的數(shù)據(jù)庫,只是因?yàn)樗俣确浅??,可以在一秒鐘?nèi)執(zhí)行數(shù)百萬次操作,被大家常用來作為緩存。而當(dāng)我看到最喜歡的關(guān)系型數(shù)據(jù)庫 Postgres 可以取代我最喜歡的非關(guān)系型數(shù)據(jù)庫 Redis 時(shí),我的世界發(fā)生了翻天覆地的變化。我應(yīng)該用Postgres取代 Redis,還是用Redis取代Postgres?在考慮這個(gè)問題之前,我想先搞清楚:Postgres作為緩存真的是個(gè)好主意嗎?它真的可以取代 Redis 嗎?Stephan Schmidt主張用 Postgres 替換 Redis(實(shí)際上他主張用 Postgres 替換一切),他認(rèn)為這樣做可以消除一定的復(fù)雜性。(請閱讀:https://medium.com/@AmazingCTO)“一切都用 Postgres 吧(如何降低復(fù)雜性并加快速度)” — Stephan Schmid然而,他并不是唯一一個(gè)主張更換 Redis 的人,也有人做了同樣的事情:
但首先,我為什么要用 Postgres 替換 Redis?
Stephan 已經(jīng)給出了兩個(gè)理由:復(fù)雜性更低和變化更快。是否還有其他驅(qū)動因素呢?使用 Postgres 作為緩存雖不是常見的選擇,但在某些情況下具有一定的優(yōu)勢:Postgres 是最流行的數(shù)據(jù)庫之一,且開源免費(fèi),將其用作緩存可以減少管理和維護(hù)多個(gè)數(shù)據(jù)庫系統(tǒng)的工作,從而簡化技術(shù)堆棧。Postgres 支持復(fù)雜的查詢和索引,特別是對于精通 SQL的人來說,直接在緩存層內(nèi)處理高級數(shù)據(jù)檢索和轉(zhuǎn)換任務(wù)會更加容易。某些情況下,使用現(xiàn)有的 Postgres 資源進(jìn)行緩存,可能比部署單獨(dú)的緩存解決方案(如 Redis)更具成本效益。尤其是在基礎(chǔ)設(shè)施預(yù)算有限的環(huán)境中,將 Postgres 同時(shí)用作主存儲和緩存可以提高資源利用率。
傳統(tǒng)緩存服務(wù)(例如 Redis)具有一系列可增強(qiáng)應(yīng)用程序性能和可擴(kuò)展性的功能,Postgres 是否真的可以取代 Redis,需要從以下幾個(gè)關(guān)鍵層面考量:緩存服務(wù)的主要目標(biāo),是通過加快數(shù)據(jù)訪問速度,來提高應(yīng)用程序的性能。 高性能緩存解決方案可以處理高吞吐量工作負(fù)載,并提供亞毫秒級的響應(yīng)時(shí)間,從而顯著加快檢索數(shù)據(jù)的進(jìn)程。通過設(shè)置緩存數(shù)據(jù)的過期時(shí)間,讓過期數(shù)據(jù)在指定時(shí)間后自動從緩存中刪除。確保過期數(shù)據(jù)不會提供給應(yīng)用程序。 緩存服務(wù)通常將其數(shù)據(jù)保存在內(nèi)存中,而內(nèi)存一般是有限的。因此,需要設(shè)置逐出策略讓我們自動刪除不常用的數(shù)據(jù),為新數(shù)據(jù)騰出空間。大多數(shù)緩存服務(wù)的核心都是以鍵值對的形式存儲數(shù)據(jù)。這種簡單但功能強(qiáng)大的模型可以快速檢索數(shù)據(jù),從而輕松高效地存儲和訪問常用數(shù)據(jù)。簡而言之,緩存服務(wù)需要更快地訪問數(shù)據(jù)并返回盡可能最新的數(shù)據(jù)。
Stephan 和 Martin 都表示,我們可以通過使用 UNLOGGED 表將 Postgres 變成緩存服務(wù)。結(jié)合Martin Heinz《你不需要專用的緩存服務(wù) - PostgreSQL 作為緩存》這篇文章內(nèi)容(鏈接:https://martinheinz.dev/blog/105),得到了這些答案:Postgres 中的未記錄表是一種防止特定表生成 WAL(預(yù)寫日志)的方法。 反言之,WAL可確保對數(shù)據(jù)庫所做的所有更改,在實(shí)際寫入數(shù)據(jù)庫文件之前都已記錄。在系統(tǒng)崩潰和斷電等極端情況的時(shí)候,就有助于維護(hù)數(shù)據(jù)完整性。補(bǔ)充說明:Redis提供了一種類似的機(jī)制,稱為僅附加文件 (AOF) ,它不僅提供了一種在 Redis中持久保存數(shù)據(jù)的機(jī)制,而且還以類似的方式運(yùn)行,即記錄在 Redis 中執(zhí)行的所有操作。如果使用 Redis 作為主數(shù)據(jù)庫,我們會啟用 AOF ,而如果使用 Postgres 作為緩存,我們會關(guān)閉(在特定表上)WAL。對于每次數(shù)據(jù)修改,Postgres 必須更改寫入 WAL 和數(shù)據(jù)文件。這使所需的寫入操作數(shù)量加倍。除此之外,為了確保每個(gè)已提交的事務(wù)都物理寫入磁盤,WAL被設(shè)計(jì)為強(qiáng)制執(zhí)行磁盤刷新 (fsync)。頻繁的磁盤刷新操作會影響性能,因?yàn)樗鼈儠氲却疟P確認(rèn)數(shù)據(jù)已安全寫入的延遲。Postgres會使用WAL來重放和應(yīng)用自上次檢查點(diǎn)以來所做的任何更改,如果我 們沒有此日志記錄,則無法通過重放WAL記錄將數(shù)據(jù)庫恢復(fù)到一致狀態(tài)。但這也是緩存的一大特點(diǎn)。
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 都表示,可以使用存儲過程來實(shí)現(xiàn)過期,這會導(dǎo)致一定的復(fù)雜性。因此,Stephan甚至更進(jìn)一步建議我們使用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
然而事實(shí)是,大多數(shù)現(xiàn)代應(yīng)用程序不再依賴存儲過程,而且現(xiàn)在很多軟件開發(fā)人員都反對使用存儲過程,以此避免把業(yè)務(wù)邏輯泄露到數(shù)據(jù)庫中,且隨著存儲數(shù)據(jù)的增加,管理和理解會變得更為麻煩。此外,我們還需要按計(jì)劃調(diào)用這些存儲過程。為此,我們需要使用一個(gè)擴(kuò)展 pg_cron 。安裝擴(kuò)展后,我們?nèi)匀恍枰獎(jiǎng)?chuàng)建調(diào)度程序:
-- 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則表示,由于過期可以保持存儲大小,因此也可以作為一個(gè)選擇。但是,如果仍然想要啟用逐出策略,Martin建議在我們的表中添加一個(gè)名為 last_read_timestamp的列,并偶爾運(yùn)行另一個(gè)存儲過程來實(shí)現(xiàn)“最近使用”(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 提供了八種現(xiàn)成的逐出策略(官方文檔:https://redis.io/docs/latest/develop/reference/eviction/)。如果想要為“Postgres Cache”設(shè)置另一種逐出策略?問ChatGPT即可。
性能表現(xiàn)是緩存服務(wù)選型的決定性因素,因?yàn)槲覀冃枰彺娣?wù)的主要原因是想更快地訪問的數(shù)據(jù)。這就是最大的問題:Postsgres 性能優(yōu)化策略依賴于共享緩沖區(qū)。共享緩沖區(qū)將經(jīng)常訪問的數(shù)據(jù)和索引直接存儲在內(nèi)存中,使其可以快速訪問,并減少從磁盤讀取的需要,提高已記錄和未記錄表的查詢性能和數(shù)據(jù)訪問能力。未記錄表可能留在這些緩沖區(qū)中,但如果它們變得太大或內(nèi)存有限,它們則會被寫入磁盤。因此,未記錄表主要提高寫入速度,而不是讀取速度。為了證明這一點(diǎn),我使用進(jìn)行了快速實(shí)驗(yàn) pgbench (具體操作請見:GitHub - raphaeldelio/redis-postgres-cache-benchmark)結(jié)果表明,記錄表和未記錄表的性能實(shí)際上非常相似,讀取這兩種類型的表平均需要大約 0.650 ms。具體數(shù)據(jù)如下:這一結(jié)果測試進(jìn)一步驗(yàn)證:未記錄表主要增強(qiáng)了寫入性能。對于讀取操作,未記錄表的性能優(yōu)勢并不明顯,因?yàn)橛涗洷砗臀从涗洷矶纪瑯邮芤嬗?Postgres 的緩存和優(yōu)化策略。除了對 Postgres 進(jìn)行基準(zhǔn)測試之外,我還對 Redis 進(jìn)行了實(shí)驗(yàn)。(具體操作請見:GitHub - raphaeldelio/redis-postgres-cache-benchmark)。結(jié)果顯示,Redis在讀寫操作方面具有顯著的性能優(yōu)勢: 每秒請求數(shù) (RPS) :892.857,12 每秒請求數(shù) (RPS) :892857,12 性能比較顯示,Redis 在寫入和讀取操作方面都明顯優(yōu)于 Postgres:Redis只有 0.095ms的延遲, Postgres未記錄表有0.679ms。Redis還能處理更高的請求率,每秒 892,857.12 個(gè)請求,而 Postgres 每秒只能處理 15,946.025 個(gè)請求。 在寫入操作方面,我們也可以看到Redis提供了更優(yōu)異的性能,吞吐量明顯更高,延遲也更低。如果我在 RAM 中運(yùn)行 Postgres 會怎樣?在審查本文的過程中,Xebia的同事Maksym Fedorov表示:“ 如果現(xiàn)在在與內(nèi)存映射文件對應(yīng)的表空間中創(chuàng)建未記錄表會怎么樣?我猜我們會看到完全不同的數(shù)字?!?/span>為了測試這一點(diǎn),我使用保存在 RAM 中的 Postgres 數(shù)據(jù)運(yùn)行了基準(zhǔn)測試。 令人驚訝的是,結(jié)果沒有任何改善。基準(zhǔn)測試顯示:每秒請求數(shù) (TPS) :15329,776954經(jīng)過進(jìn)一步研究,我了解到,即使數(shù)據(jù)存儲在 RAM 中,在Postgres 的共享緩沖區(qū)內(nèi)訪問數(shù)據(jù)也會產(chǎn)生額外成本,這些成本來自管理鎖,以及數(shù)據(jù)完整性和并發(fā)訪問所需的其他內(nèi)部進(jìn)程。而且,Postgres總是先檢查數(shù)據(jù)是否在共享緩沖區(qū)中,如果不在,它會先將數(shù)據(jù)從tmpfs文件系統(tǒng)復(fù)制到共享緩沖區(qū)中,然后再提供服務(wù),即使數(shù)據(jù)庫保存在 RAM中。
我應(yīng)該用 Postgres 替換 Redis 嗎?
綜上所述,如果您需要緩存服務(wù)來提高寫入性能,可以使用未記錄表優(yōu)化 Postgres。但是,雖然未記錄表比記錄表提供更好的寫入性能,但與 Redis 相比仍然不足。使用緩存服務(wù)的主要原因是縮短數(shù)據(jù)檢索時(shí)間。未記錄的表不會提高讀取性能,而Redis則以極快的讀取優(yōu)勢作為更優(yōu)選擇。此外,Redis有助于防止大量低成本查詢訪問數(shù)據(jù)庫,這是未記錄表無法提供的優(yōu)勢。Redis還提供內(nèi)置功能,如過期、逐出策略等,這些功能在 Postgres 中很難實(shí)現(xiàn)。盡管對某些人來說,管理 Postgres 似乎更容易,但將 Postgres 變成緩存并不能提供專用緩存服務(wù)的優(yōu)勢。同時(shí),Redis 的學(xué)習(xí)、部署和使用都很簡單,而且很有趣。所以為了獲得更快的性能和簡單性,選擇像Redis這樣真正的緩存服務(wù)似乎才是更明智的選擇。
作者丨Raphael De Lio 編譯丨Rio
來源丨h(huán)ttps://medium.com/redis-with-raphael-de-lio/can-postgres-replace-redis-as-a-cache-f6cba13386dc
該文章在 2024/7/23 20:47:56 編輯過