近期,一篇名為“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是一個真正的數據庫,只是因為它速度非??欤梢栽谝幻腌妰葓绦袛蛋偃f次操作,被大家常用來作為緩存。而當我看到最喜歡的關系型數據庫 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 編輯過