收到主機商來信「你的網站透過 admin-ajax.php 大量吃 CPU」時,多數人第一個被點名的嫌疑犯就是 WordPress 的心跳 API(Heartbeat)。它確實會持續送出請求、確實會吃資源,但在很多情況下真正的元凶並不是它。WordPress heartbeat 優化的第一步,不是急著貼一段停用程式碼,而是先搞懂這顆「心跳」在背後做了什麼、什麼時候才該動它,以及動錯了會犧牲哪些你正在依賴的功能。
這篇會把整條脈絡講清楚:Heartbeat 的運作原理、它怎麼變成 admin-ajax.php 的負載、怎麼跟最常被搞混的 WooCommerce 購物車片段分辨開來,以及如何用 heartbeat_settings 過濾器安全地降低後台負擔,而不會把編輯器的自動儲存弄壞。
WordPress Heartbeat API 是什麼,為什麼會拖慢後台
Heartbeat API 是 WordPress 內建的一套「瀏覽器對伺服器」輪詢機制,從 WordPress 3.6 開始導入。只要你登入後台、開著某個分頁,瀏覽器就會每隔幾秒對伺服器送出一次 AJAX 請求,像心跳一樣固定跳動,這也是它名字的由來。
它支撐的是四項你每天都在用、卻很少察覺的功能。第一是自動儲存,區塊編輯器會定期把目前草稿回傳伺服器,瀏覽器當掉或網路斷線時不會整篇丟失。第二是文章鎖定,當你打開一篇文章編輯,心跳會持續告訴伺服器「這篇我還在改」,第二位編輯打開同一篇時會看到「使用者 X 正在編輯」的提示,而不是默默覆蓋掉前一個人的修改。第三是後台同步,像活動小工具、工具列上的外掛更新數字、「概況」面板,都是靠心跳定時刷新,不必整頁重整。第四是外掛通知,外掛可以掛進 heartbeat_received 這個 PHP 過濾器,把伺服器端的訊息推回後台,例如「剛進來一張新訂單」或「資安掃描完成了」。
問題出在頻率與成本。在文章編輯器畫面,心跳預設每 15 秒跳一次;在儀表板與其他後台畫面,預設每 60 秒一次。單一編輯者開一個分頁時,這點負載微不足道;但只要同時有多位編輯各自開著分頁,或有人習慣把後台分頁掛一整天,這些請求就會累積成可觀的伺服器壓力,尤其在資源有限的共享主機上,可能直接表現成 CPU 飆高或後台變慢。
一次心跳請求在伺服器端做了哪些事
每一次心跳,都是一個完整的 WordPress 請求,這是它成本的根源。把單一次「脈衝」的旅程拆開來看會更清楚。
瀏覽器先送出一個帶有 action=heartbeat 的 POST 請求到 /wp-admin/admin-ajax.php。Web 伺服器(nginx 或 Apache)把請求交給 PHP-FPM,一個 PHP worker 隨即啟動,完整載入 WordPress 核心、所有啟用中的外掛與佈景主題。WordPress 接著驗證 nonce、觸發 heartbeat_received 過濾器、收集外掛想推回的資料,最後以 JSON 回傳。回應送出後,這個 worker 才被釋放,瀏覽器排定下一次心跳。
關鍵在於這類請求無法被快取。因為它們是送往後台的 POST 請求,所有主流頁面快取(WP Rocket、LiteSpeed Cache、W3 Total Cache、Varnish、nginx 的 fastcgi_cache)預設都會排除它,而且每次回傳的都是因人而異的 JSON,本來也沒有可重複利用的快取版本。換句話說,正確的解法永遠是「讓它少跳幾次」,而不是「想辦法快取它」。
PHP worker 是真正的瓶頸所在。worker 是處理 PHP 程式碼的引擎,數量有限;每次心跳都會佔住一個 worker 直到回應完成,背景任務跳得越密集,能拿來服務真正前台訪客的 worker 就越少。一個人開一個編輯分頁,大約是每分鐘 4 次請求,CPU 成本可以忽略;但五位編輯同時在寫稿、各開一個分頁,一小時下來光是編輯器就可能累積上千次心跳,再加上儀表板分頁的背景請求,worker 池就開始吃緊。
admin-ajax.php 變慢不一定是 Heartbeat,先分辨 WooCommerce 購物車片段
這是整個優化過程裡最該先做、也最常被略過的判斷:admin-ajax.php 負載高,元凶不一定是心跳。在一個有大量 admin-ajax.php 請求的 WooCommerce 商店上,真正的來源往往根本不是 Heartbeat,而是 WooCommerce 的購物車片段(cart fragments),兩者是完全獨立的機制。
兩者的差別很明確。Heartbeat 只會從「已登入的後台分頁」發出,例如文章編輯器、儀表板、外掛頁,請求內容帶著 action=heartbeat。購物車片段則相反,它從「前台」發出,任何帶有 session cookie 的訪客每次載入頁面都會觸發,包括購物車空空、根本還沒登入的逛街訪客。它的請求網址帶著 wc-ajax=get_refreshed_fragments,存在的目的是在頁面命中快取之後,重新整理頁首那個迷你購物車小工具,因為快取下來的 HTML 並不知道這位訪客的購物車狀態。在一個有前台快取、流量不低的商店,購物車片段一小時輕鬆就能產生上萬次請求,而這當中沒有一個是心跳。
分辨方法就在伺服器存取記錄檔。打開記錄、找出對 admin-ajax.php 的請求行,看網址查詢字串或 POST 內容:帶 action=heartbeat 的就是心跳,調整 heartbeat_settings 才會有效果;網址帶 wc-ajax=get_refreshed_fragments 的是購物車片段,這時候再怎麼調心跳都不會動,要改用 WooCommerce 的過濾器停用購物車片段或設快取繞過規則;至於帶其他 action= 值的,則是別的外掛(站內即時搜尋、商品篩選、彈窗、分析追蹤),各有各的診斷方式。
如果你在一個真正問題是購物車片段的網站上拚命調心跳,CPU 曲線不會有任何改變。先分辨清楚,是避免白費一個下午的最快方法。
動手調整前,先用記錄檔與 Query Monitor 量出 Heartbeat 佔比
不要盲目調心跳,先量。記錄檔或效能分析工具會告訴你,admin-ajax.php 流量裡到底有多少比例真的來自 Heartbeat,以及這個量是否值得處理。
最直接的是伺服器存取記錄檔。每一筆請求都會寫一行,把含有 admin-ajax.php 的行篩出來,再數其中 action=heartbeat 的比例。如果心跳只佔總 admin-ajax.php 流量的個位數百分比,那它就不是你的問題;如果佔到六成以上,調整之後就會看到明顯差異。
另一個利器是免費的 Query Monitor 外掛。它會在後台工具列加一個面板,列出當前請求觸發的每一個 AJAX 動作、跑過的每一個掛鉤、以及每段查詢花了多久。在會觸發心跳的頁面上,面板會明確顯示 heartbeat_received 這條路徑,這是最快看出「哪些外掛掛了心跳處理常式、每次心跳各自付出多少成本」的方式。有些外掛在 heartbeat_received 裡塞了昂貴的查詢,光調間隔還不夠,得回頭看那個外掛本身。
判斷邏輯很單純:心跳佔比低就不要動它,把力氣花在真正的負載源;佔比高才往下走,套用後面的調整方法。量完一項、改一項、再量一次,是唯一能可靠把曲線往對的方向推的順序。
用 heartbeat_settings 過濾器把心跳間隔調到 60 秒
控制 Heartbeat 的主要管道是 heartbeat_settings 這個過濾器。它屬於 WordPress 核心、不需要任何外掛,也因此能扛過外掛停更與 WordPress 版本升級,是最值得優先採用的做法。
把下面的程式碼放進站台專屬外掛,或子佈景主題的 functions.php,不要去改父佈景主題或別人寫的外掛。最常用的一招是把間隔統一拉到 60 秒,這會保留自動儲存與文章鎖定,只是節奏放慢,多數網站套用後使用者不會有感。
// 將 Heartbeat 間隔設為 60 秒,套用到所有後台情境。
add_filter( 'heartbeat_settings', function( $settings ) {
$settings['interval'] = 60;
return $settings;
} );
這裡有個必須記住的限制:interval 參數在目前的 WordPress 版本會被限制在 15 到 120 秒之間,設定範圍外的值會被默默忽略。換句話說,60 秒是編輯器能調到的較慢端,而 120 秒是整個機制允許的上限。
如果你想保留編輯器的反應速度,只放慢其他後台畫面,可以在過濾器裡用 get_current_screen() 做情境判斷。編輯器維持預設的 15 秒讓自動儲存夠即時,儀表板那種閒置畫面則拉到 120 秒。
// 編輯器維持 15 秒以確保自動儲存即時,其他後台畫面放慢到 120 秒。
add_filter( 'heartbeat_settings', function( $settings ) {
if ( function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
if ( $screen && in_array( $screen->base, array( 'post', 'post-new' ), true ) ) {
return $settings; // 編輯器保留預設的 15 秒。
}
}
$settings['interval'] = 120;
return $settings;
} );
還有一個常被忽略的細節能順手省下流量:當瀏覽器分頁失去焦點時,心跳會自動進入「慢速模式」,把間隔拉長到大約兩分鐘。也就是說,編輯者掛在背景視窗的分頁,本來就只產生約一半的流量,這在規劃要不要進一步調慢編輯器時值得納入考量。
在前台與特定頁面停用 Heartbeat 的程式碼寫法
在前台停用心跳是安全的,因為 WordPress 核心本來就不會為「未登入訪客」在前台載入 heartbeat 腳本。網路上常見的「心跳在前台每 120 秒跳一次」其實是誤讀,120 秒是允許間隔的上限,不是前台預設值。前台會載入心跳,通常是某些外掛或佈景主題針對「登入後同時在前台逛站」的使用者主動掛載,停用它只會影響到這部分。
// 僅在前台頁面載入時取消註冊 heartbeat 腳本。
add_action( 'init', function() {
if ( ! is_admin() ) {
wp_deregister_script( 'heartbeat' );
}
} );
這裡掛在 init 而不是 wp_enqueue_scripts,是為了在外掛把腳本排進佇列之前就先取消註冊。若要更保險地搶在所有外掛之前執行,可以把 add_action 的優先序設為 1,讓取消註冊發生在最早的時機。
至於「整站完全停用」屬於最後手段,後果在下一節說明。只有在你已經確認心跳是主要負載源、而且編輯團隊能接受失去自動儲存的前提下才用它。
// 全域取消註冊 heartbeat 腳本,會讓自動儲存與文章鎖定失效。
add_action( 'init', function() {
wp_deregister_script( 'heartbeat' );
}, 1 );
選擇權衡很簡單:能用過濾器調慢就不要直接停用,能局部停用就不要全域停用。涉及線上收款流程的網站要特別謹慎,部分金流外掛會透過心跳推回交易或訂單狀態通知,全域停用前最好先在測試環境確認後台通知還正常。任何改動都先在測試環境驗過再上線。
完全停用 Heartbeat 會犧牲哪些功能
幾乎每一篇喊著「停用心跳來省資源」的文章,都跳過了這一段,但它恰恰最關鍵,因為設錯會悄悄弄壞編輯團隊每天依賴的功能。
自動儲存會停止。瀏覽器當機、網路斷線,或編輯途中不小心關掉分頁,從上次手動儲存之後打的字會全部消失,一篇長文可能就是半小時的心血付諸流水。文章鎖定也會停止,兩位編輯能同時打開同一篇、各自存檔覆蓋對方,最後存的人贏,先存的人改動被默默蓋掉,而且因為鎖定機制沒了,連警告對話框都不會出現。
外掛通知同樣失效。WooCommerce 的「新訂單」即時提示、資安掃描完成訊息、即時備份狀態這類靠心跳把更新推回後台的功能,都會無聲地失靈。視覺化頁面建構器也可能出狀況,Elementor、Divi 等建構器會用心跳維持編輯器的即時狀態(修訂歷史、協作者指示、建構器內的自動儲存),全域停用心跳有時會讓建構器跳出看起來像它自己壞掉的錯誤。區塊編輯器(Gutenberg)的文章鎖定指示也跟古典編輯器一樣依賴心跳,停用後兩邊都會失去這個提示。
比較安全的預設做法是:把編輯器調到 60 秒、儀表板維持預設,這樣能砍掉約四分之三的編輯器流量,同時保住自動儲存與文章鎖定。只有在量測確認心跳確實主導了 admin-ajax.php 流量、團隊也同意接受失去自動儲存時,才升級到完全停用。
針對不同情境也有不同對策。如果問題出在編輯者開著卻沒在看的儀表板分頁,正確做法是把儀表板進一步調慢(例如 120 秒)、別動編輯器,不要為了背景流量犧牲編輯器的自動儲存,因為編輯器才是丟失心血最痛的地方。如果問題是少數重度使用者整天掛著編輯器分頁,則把編輯器調到 30 或 60 秒即可,反正背景分頁的慢速模式已經幫忙減半。如果問題是多位編輯同時都在密集寫稿,那你撞到的是 PHP worker 池的上限,不是心跳的上限,調心跳只有邊際效果,增加 worker 數量或升級主機方案才是主力。
用 Heartbeat Control 外掛取代手動程式碼的時機
如果不想碰程式碼,有一個專門的外掛可選:由 WP Media(WP Rocket 的開發團隊)出的 Heartbeat Control。它提供圖形介面,針對儀表板、前台、文章編輯器三個情境各給一組滑桿,可以分別啟用、停用或調慢,目前有超過八萬個網站在用。
但它有一個必須知道的隱憂。這個外掛最後一次釋出是 2.0.1 版、日期落在 2023 年,僅測試到 WordPress 6.3,在較新的 WordPress 版本上,外掛目錄會顯示「未在最近三個主要版本測試過」的警示。它對多數網站仍然可用,因為它包裝的 heartbeat_settings 過濾器本身沒變,但你應該把它當成「靠維生系統撐著」的相依套件,而不是還在積極維護的工具。
選擇邏輯很清楚:當你需要的是那個圖形介面時才用外掛,例如不想碰程式碼、想自己調設定的後台管理者,或要把同一套設定快速套到許多客戶網站的代營運團隊。其餘情況都建議用前面的程式碼片段,一個你完全掌控的檔案,沒有外掛更新風險,也沒有額外的後台介面要管。
先量再調,把伺服器資源留給真正的訪客
WordPress heartbeat 優化的重點,從頭到尾都是「先量再調、一次只動一處」。Heartbeat 並不是壞掉的設計,對絕大多數網站來說,它只佔伺服器零點幾個百分比的容量,換來的自動儲存、文章鎖定與即時通知遠比這點成本值得。真正常見的浪費,是在問題根本不在心跳的網站上拚命調心跳。
實際動手時,先打開存取記錄檔分辨負載到底來自心跳、購物車片段還是其他外掛;確認心跳佔比夠高,再用 heartbeat_settings 把間隔調到 60 秒,需要時用 get_current_screen() 區分編輯器與儀表板;前台可以安全停用,整站完全停用則留到最後、並接受失去自動儲存的代價。每改一項就回頭看一次曲線,把有限的 PHP worker 留給真正在看你內容的訪客,這才是降低後台 admin-ajax 負載最踏實的路徑。