在Postgre中設置max_connections時,為什么需要使用連接池 (譯)


原文地址:https://www.enterprisedb.com/postgres-tutorials/why-you-should-use-connection-pooling-when-setting-maxconnections-postgres

PostgreSQL是“世界上最先進的開源數據庫”,我相信這一點。在我從事it工作的10多年中,它一直很穩定,向SaaS提供每秒超過1000個查詢的數據,很少出現故障,經受住了各種形式的事故(最終證明是軟件工程錯誤)和性能下降(最終證明是用戶錯誤)的指控。它有如此多的特性和擴展,可以滿足每一個需求,可能有50-60%的用戶不經常使用,或者根本沒有聽說過。不幸的是,在我最近的技術支持經驗中,我了解到Postgres通常是非常值得信任的,但是很多技術都是如此;它不會判斷您應該如何調整postgresql.conf中的參數,就像跑車上的油門踏板會影響行駛速度的一樣。僅僅因為您可以踩到油門踏板上,並不意味着您應該在高峰時間執行此操作,因此並不是說將某些參數值其設置得很高,就適合並發較高的OLTP應用程序。
 
最容易引起誤解的參數之一是max_connections。可以理解的是,在擁有大量CPU和大量RAM的現代系統上,向全球用戶群提供現代SaaS負載,一次可以看到成千上萬個用戶會話,每個會話都試圖查詢數據庫以更新用戶狀態、上載自拍或其他用戶可能做的事情。當然,DBA會希望在postgresql.conf設置與應用程序發送到數據庫的流量相匹配的值,但這是有代價的。這種代價的一個例子是生成連接/斷開連接的延遲;對於創建的每個連接,操作系統都需要為打開網絡套接字的進程分配內存,PostgreSQL需要自己進行內部計算來建立連接。將其擴展到數千個用戶會話,而僅僅為用戶准備好數據庫連接就可能浪費大量時間。將max_connections設置為高的其他成本包括磁盤爭用、操作系統調度,甚至CPU級緩存線爭用。
 

max_connections究竟該設置為多大

沒有太多科學數據可以幫助DBA將max_connections設置為其適當的值。因此,大多數用戶發現PostgreSQL的默認值max_connections = 100太低。不斷有人將其設置為4k,12k甚至30k以上(這些人都遇到了一些主要的資源爭用問題)。與那里的任何PostgreSQL專家交談,他們將給您一個范圍“ 幾百個”,或者有些人會斷然地說“不超過500個”,並且“絕對不超過1000個”。但是這些數字從何而來?他們怎么知道的,我們該如何計算呢?提出這些問題,可能只會發現自己更沮喪,因為有一種公式化的方法來確定該數字。設置此值的困難在於數據庫需要服務的應用程序。一些應用程序發送大量查詢並關閉會話,而其他應用程序可能會突然發送查詢,並且之間有很多空閑時間。此外,某些查詢可能會花費大量CPU時間來執行聯接和排序,而其他查詢則會花費大量時間順序掃描磁盤。我見過的最合理的答案是計算CPU的數量,占利用率的百分比(基於一個基准測試需要做的),然后乘以比例因子。但這甚至涉及一些“hand-waving”(譯者注:專門搜索了文獻,這里可以理解成拍腦袋做出的決定)。
 

測試tribal知識(譯者注:原文作者用tribal這個詞是類似於"江湖傳聞"的一詞的語音)

沒有一種非常簡明清晰的方法來計算max_connections,我決定至少要測試那里的tribal知識的有效性。確實應該是“幾百個”,“不超過500”和“絕對不超過1000”嗎?為此,我設置了一個AWS g3.8xlarge EC2實例( 32 CPU,244GB RAM,1TB 3K IOPS SSD)來慷慨地模仿我在那里看到的一些數據庫服務器,並使用--scale = 1000初始化了一個pgbench實例。 我還設置了10個較小的EC2實例,用作應用程序服務器,並且在每個實例上運行pgbench測試一小時,每小時將--client = NUM遞增(因此它們將總共創建100,200,300 ... 5000個連接每小時的測試)。 autovacuum已關閉,以防止任何不必要的干擾導致結果偏差(盡管我在每次測試之間都進行了清理),否則將postgresql.conf調整為一些通常可接受的值。我將max_connections設置為12k,以確保我的測試使用的不超過最終測試中要求的5000。測試運行時我走開了,結果又回來了,就像這樣:

下面是上圖的放大圖:

因此,對於改進后的設置使其類似於某些企業級計算機的服務器,最佳性能出現在300-500個並發連接的時候。在700之后,性能急劇下降(就每秒事務數和延遲而言)。 1000個以上的連接均表現不佳,並且延遲不斷增加。最后,等待時間開始是非線性的-這可能是因為我沒有將EC2實例配置為允許超過默認的~25M open filehandles,到在連接數超出3700之后,看到幾個無法fork新的連接過程: could not fork new process for connection: Resource temporarily unavailable.
有趣的是,這三中說法都匹配了:“幾百個”,“不超過500個”和“絕對不超過1000個”。這似乎太不可思議了,所以我再次進行了測試,直到增加到最大1800個並發連接。結果:

因此,對於該服務器而言,最有效的點似乎確實在300-400個連接之間,並且max_connections不應設置得比該值高很多,以免我們冒失去性能的風險。
譯者注:這個結果是匹配原文作者的服務器配置:32 CPU,244GB RAM,1TB 3K IOPS SSD,應該是沒有啟動連接池的情況下得到的一個結果,而不是一個參考值或者標准值

如果需要更多的連接數,該怎么辦?

顯然,max_connections = 400不會允許高並發的應用程序處理用戶提供給它的所有工作。某種程度上,需要擴大數據庫以滿足這些要求,但是這樣做似乎需要一些魔法。一種選擇是設置復制系統,以使讀取分布在多個服務器上,但是如果並發寫超過400個並發會話(這很有可能),則需要考慮其他選擇。通過允許多個客戶端會話共享數據庫連接池並根據需要執行讀寫事務,並在空閑時將數據庫連接的控制權移交給其他會話,連接池將滿足此需求。在PostgreSQL社區中,池化應用程序的主要參與者是pgbouncer和pgpool-兩者都經過了充分的測試,以使DBA能夠將其PostgreSQL數據庫擴展到成千上萬的並發用戶連接。
為了演示在使用連接池時提高的可伸縮性,我設置了一個類似於 Alvaro Hernandez’s concurrent-connection test的m4.large EC2實例(譯者注:上述連接在原文中也打不開,另外,m4.large配置為2 個 vCPU,8GiB 內存),
因為:1)我想使用一個不僅僅是我自己的基准數字;2)我想要節省一些成本。然后嘗試得到與他相似的圖形(中的結論):

譯者注:
上述截圖的大概的結論是:基於m4.large當前這個配置,在小於200個並發連接的時候,TPS會隨着並發連接數的增加而增加,以下是原文的作者是嘗試驗證
Alvaro的測試結論,也就是上面這個截圖的結果,從下文看,不管用不用連接池,兩種情況下都無法得到這個結論,這說明Alvaro這個人的測試結果是值得商榷的,在這種配置下,10個並發連接得到的TPS才是最高的。

但是,創建此圖時在pgbench中沒有-C /-connect標志(譯者注:為每個事務建立新的連接,而不是在一個連接中完成多個事務操作),這可能是Alvaro不是為了說明使用連接池的優點。因此,我重新運行了相同的測試,但是這次使用了-C:
如我們所見,由於每個事務都必須連接和斷開連接,所以吞吐量下降了,這說明了建立連接的需要花費成本。然后我將pgbouncer配置為max_client_conn = 10000,max_db_connections = 300,pool_mode = transaction,然后再次使用pgbouncer端口運行相同的pgbench測試(-h <hostname> -p6432 -U postgres --client=<num_clients> --progress=30 --time=3600 --jobs=2 -C bouncer):

顯然,雖然pgbouncer維護了與數據庫的開放連接並與傳入的客戶端共享它們,但連接開銷卻降低了,從而提高了吞吐量。請注意,即使使用連接池池管理器,我們也永遠無法獲得Alvaro的圖,因為在啟用連接時總會有一些開銷(即,客戶端需要告訴OS分配一些空間並打開一個套接字以實際連接到pgbouncer) 。

結論

如我們所見,max_connections應該通過一些現場基准測試和一些自定義腳本來確定(請注意,所有這些測試都使用了內置的pgbench事務,該事務包含3個UPDATE,1個SELECT和1個INSERT-a 可以通過提供自定義.sql文件並使用-f /-file標志來創建真實測試)。從根本上說就是,做一下家庭作業:(基於自己的服務器)進行基准測試,找出可以可以提供良好性能的最大並發度,四舍五入到最接近的百位數(為您留出一些余地),並相應地設置max_connections。設置后,希望通過復制或連接池的任何組合來滿足並發性的所有其余要求。連接池是任何高吞吐量數據庫系統的重要組成部分,因為它可以消除連接開銷,並為較小的數據庫連接集保留更多的內存和CPU時間,從而防止不必要的資源爭用和性能下降。
 
 
 
 
譯者的總結

1,數據庫最大連接數和最大並發數的誤區:
數據的最大連接數,也就是可支持的最大並發連接數,總有人把最大連接數和最大並發數混為一潭,聯想到多線程,多線程總給人一種牛逼轟轟的,不用多線程就上不去台面的感覺,其實除了CPU密集型的運算之外,而日常的大部分操作,都是和IO以及網絡密切相關的,處理某個單一任務,多線程或者說純粹增加線程數據並不一定提升效率,這兩者相比CPU,都會先於CPU成為瓶頸,也就是說不等你並發完全起來,IO和網絡已經成為瓶頸了,舉個最簡單的例子:Redis。
1,即使是單核CPU的計算機也能“同時”運行數百個線程。但我們都[應該]知道這只不過是操作系統用時間分片玩的一個小把戲。
一顆CPU核心同一時刻只能執行一個線程,然后操作系統切換上下文,核心開始執行另一個線程的代碼,以此類推。
給定一顆CPU核心,其順序執行A和B永遠比通過時間分片“同時”執行A和B要快,這是一條計算機科學的基本法則。一旦線程的數量超過了CPU核心的數量,再增加線程數系統就只會更慢,而不是更快。

2,在這一時間段(即"I/O等待")內,線程是在“阻塞”着等待磁盤,此時操作系統可以將那個空閑的CPU核心用於服務其他線程。
所以,由於線程總是在I/O上阻塞,我們可以讓線程/連接數比CPU核心多一些,這樣能夠在同樣的時間內完成更多的工作。
那么應該多多少呢?這要取決於磁盤。
較新型的SSD不需要尋址,也沒有旋轉的碟片。
可別想當然地認為“SSD速度更快,所以我們應該增加線程數”,恰恰相反,無需尋址和沒有旋回耗時意味着更少的阻塞,所以更少的線程[更接近於CPU核心數]會發揮出更高的性能。只有當阻塞創造了更多的執行機會時,更多的線程數才能發揮出更好的性能。

最大連接數計算公式
下面的公式是由PostgreSQL提供的,不過我們認為可以廣泛地應用於大多數數據庫產品。你應該模擬預期的訪問量,並從這一公式開始測試你的應用,尋找最合適的連接數值。
連接數 = ((核心數 * 2) + 有效磁盤數)

公理:你需要一個小連接池,和一個充滿了等待連接的線程的隊列
如果你有10000個並發用戶,設置一個10000的連接池基本等於失了智。1000仍然很恐怖。即是100也太多了。你需要一個10來個連接的小連接池,然后讓剩下的業務線程都在隊列里等待。連接池中的連接數量應該等於你的數據庫能夠有效同時進行的查詢任務數(通常不會高於2*CPU核心數)
這段話反復琢磨了很多次
對於非CPU密集型計算來說,單純滴增加線程數可能會降低整體運算效果,然后想到了一個問題:為什么單線程的Redis單實例可以有10+QPS的超級響應速度,這里強調的單線程,除了哪些面試套路性的答案之外,其實很多問題上面也能夠說明其原理。
1,redis基於內存的操作,IO的阻塞延遲相比SSD和HDD,分別低了3/6個數量級,可以認為基於內存的IO延遲是極底的
2,基於1,在沒有IO阻塞或者IO延遲極地的情況下,相比多線程,單線程其實要比多線程更快。

20201209補充:數據庫的 max_connections 真的越大越好嗎?這里有一個實測連接數的數據

2,如何處理對數據庫的並發請求數大於數據庫的最大連接數:
如果連接數是一個合理的值,不是100或者200,真正的並發數超出最大連接數,此時必須使用連接池,否則會報超出最大連接數的錯誤,連接池不僅可以提高連接效率,在超出最大連接后等待機制,也可以起到了限流的作用。
而如果真的不去限制最大連接數,隨着並發的增加去機械地增加連接數,只會沖垮數據庫,即便沖不跨數據庫,其結果就是所有的請求,在數據庫里相互牽制相互拖累。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM