<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Vertical-Scaling on Tarragon</title><link>https://tarrragon.github.io/blog/tags/vertical-scaling/</link><description>Recent content in Vertical-Scaling on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/vertical-scaling/index.xml" rel="self" type="application/rss+xml"/><item><title>垂直與水平擴展的判斷</title><link>https://tarrragon.github.io/blog/devops/02-horizontal-scaling/vertical-vs-horizontal/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/02-horizontal-scaling/vertical-vs-horizontal/</guid><description>&lt;p>垂直擴展跟水平擴展的判斷樞紐是一個問題：這個元件能不能做成無狀態。能做成無狀態的，加實例（水平）幾乎可以無腦複製；做不成、或改造成本太高的，只能換更大的機器（垂直）先撐。這個模組前面幾章一直在講怎麼做到無狀態，正是因為無狀態與否是這個決策的關鍵——一個服務落在哪一邊，決定了它該往哪個方向擴。&lt;/p>
&lt;p>兩種擴展的機制、各自的物理與成本天花板，在 &lt;a href="https://tarrragon.github.io/blog/devops/05-capacity-planning/scaling-inflection-point/" data-link-title="規模拐點判斷" data-link-desc="判斷什麼訊號代表該擴容、什麼代表可縮容、以及該往垂直還水平擴時，用飽和曲線的三段區間、膝點的早期訊號與 ramp-up 方法回來讀">規模拐點判斷&lt;/a> 的垂直對水平段展開過（垂直有兩道牆——換更大的機器會撞到物理規格上限、以及成本在高階機型非線性飆升；水平則有協調與連線放大成本）。這一章不重述那些機制，聚焦在「怎麼用無狀態與否做這個判斷」。&lt;/p>
&lt;h2 id="先問能不能無狀態">先問能不能無狀態&lt;/h2>
&lt;p>判斷從一個問題開始：這個要擴的元件是無狀態的、還是有狀態的。無狀態的部分（API server、worker）水平擴展幾乎無腦——每個實例對等、加一台就多一台的容量，這個模組前面幾章的設計就是為了讓服務落在這一邊。有狀態的部分（資料庫、快取、session store）不能靠複製水平擴展，因為狀態不對等——不能開兩台資料庫主庫同時寫、期待它們自己一致。&lt;/p>
&lt;p>把這個前提判反，是水平擴展最常見的撞牆方式：用水平擴展的策略去動一個有狀態的服務，加了實例卻發現狀態沒有跟著分攤，QPS 沒漲、甚至因為協調成本更慢。判斷的第一步永遠是先分清楚：這個元件是無狀態的（水平的候選）、還是有狀態的（要另一套策略）。&lt;/p>
&lt;h2 id="有狀態的節點垂直撐讀路徑用副本擴">有狀態的節點：垂直撐，讀路徑用副本擴&lt;/h2>
&lt;p>有狀態的節點（典型是資料庫主庫）擴展走另一條路。寫入的部分靠垂直撐——換更大的機器,不改程式、立即見效，適合這種不能水平複製的關鍵節點。但垂直有天花板，且有狀態的關鍵節點常常比機器規格更早撞牆（交易型資料庫主庫的真實上限往往卡在架構因素、不是規格不夠），這條在拐點判斷那章講過。&lt;/p>
&lt;p>垂直撐主庫的同時，讀路徑可以水平擴——加唯讀副本分攤讀流量，這是 &lt;a href="https://tarrragon.github.io/blog/devops/02-horizontal-scaling/shared-storage-selection/" data-link-title="Shared storage 選型" data-link-desc="把外置的狀態放進共享儲存時，按存取型態與狀態性質選 DB、KV、物件儲存，並處理多實例共享一個 DB 帶來的讀路徑與連線瓶頸">Shared storage 選型&lt;/a> 的讀寫分離。所以一個有狀態的資料庫，實際的擴展是混合的：寫路徑垂直撐、讀路徑用副本水平擴。垂直撐到頂、讀副本也不夠時，最後一步是分片（sharding）——把資料按某個鍵拆到多台，每台只管一部分，這才是有狀態服務真正的水平擴展，但它要重新設計資料的切分方式，成本最高、留到前面手段都用盡才動。&lt;/p>
&lt;h2 id="混合是常態不是二選一">混合是常態，不是二選一&lt;/h2>
&lt;p>真實系統極少是純垂直或純水平，而是按層混合：無狀態的應用層水平擴（加實例）、有狀態的資料層垂直撐加讀副本（撐不住再分片）。這對應一個擴展框架的三個方向——複製（把無狀態的元件多開幾份）、功能拆分（把不同功能拆成獨立服務、各自按自己的需求擴）、資料分片（把有狀態的資料按鍵拆開），常常同時在動、不是選一個。這個框架（AKF Scale Cube）的完整拆解在 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">backend 擴展軸&lt;/a>。&lt;/p>
&lt;p>判斷的收斂是這樣：先問這一層能不能無狀態。能，就水平複製，這是最便宜的擴展。不能，先看改造成無狀態的成本值不值得；不值得或改不動，就垂直撐這個節點、讀路徑用副本水平擴、撐到極限再分片。每一步都是在「這個元件的狀態能不能被分攤」這條線上做選擇——這也是為什麼水平擴展這個模組，從頭到尾都在講無狀態。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>垂直的兩道牆、水平的隱性成本、擴展框架的完整機制 → &lt;a href="https://tarrragon.github.io/blog/devops/05-capacity-planning/scaling-inflection-point/" data-link-title="規模拐點判斷" data-link-desc="判斷什麼訊號代表該擴容、什麼代表可縮容、以及該往垂直還水平擴時，用飽和曲線的三段區間、膝點的早期訊號與 ramp-up 方法回來讀">規模拐點判斷&lt;/a>&lt;/li>
&lt;li>怎麼把一個服務改造成無狀態 → &lt;a href="https://tarrragon.github.io/blog/devops/02-horizontal-scaling/stateless-design/" data-link-title="Stateless 設計原則" data-link-desc="要把服務改成能水平擴展的無狀態設計時，釐清什麼算「本機狀態」、哪些常見寫法會破壞無狀態、隱式狀態怎麼抓、以及定時 job 這種無狀態例外怎麼處理">Stateless 設計原則&lt;/a>&lt;/li>
&lt;li>讀寫分離、讀副本、分片的儲存側設計 → &lt;a href="https://tarrragon.github.io/blog/devops/02-horizontal-scaling/shared-storage-selection/" data-link-title="Shared storage 選型" data-link-desc="把外置的狀態放進共享儲存時，按存取型態與狀態性質選 DB、KV、物件儲存，並處理多實例共享一個 DB 帶來的讀路徑與連線瓶頸">Shared storage 選型&lt;/a>&lt;/li>
&lt;li>擴與縮的觸發訊號、縮回怎麼做 → &lt;a href="https://tarrragon.github.io/blog/devops/02-horizontal-scaling/scaling-triggers/" data-link-title="擴展的觸發與縮回" data-link-desc="設計自動擴縮時，釐清擴展前要先窮盡哪些低成本手段、觸發訊號怎麼分層、以及縮回為什麼比擴容難、要先 drain 才能縮">擴展的觸發與縮回&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>垂直擴展跟水平擴展的判斷樞紐是一個問題：這個元件能不能做成無狀態。能做成無狀態的，加實例（水平）幾乎可以無腦複製；做不成、或改造成本太高的，只能換更大的機器（垂直）先撐。這個模組前面幾章一直在講怎麼做到無狀態，正是因為無狀態與否是這個決策的關鍵——一個服務落在哪一邊，決定了它該往哪個方向擴。</p>
<p>兩種擴展的機制、各自的物理與成本天花板，在 <a href="/blog/devops/05-capacity-planning/scaling-inflection-point/" data-link-title="規模拐點判斷" data-link-desc="判斷什麼訊號代表該擴容、什麼代表可縮容、以及該往垂直還水平擴時，用飽和曲線的三段區間、膝點的早期訊號與 ramp-up 方法回來讀">規模拐點判斷</a> 的垂直對水平段展開過（垂直有兩道牆——換更大的機器會撞到物理規格上限、以及成本在高階機型非線性飆升；水平則有協調與連線放大成本）。這一章不重述那些機制，聚焦在「怎麼用無狀態與否做這個判斷」。</p>
<h2 id="先問能不能無狀態">先問能不能無狀態</h2>
<p>判斷從一個問題開始：這個要擴的元件是無狀態的、還是有狀態的。無狀態的部分（API server、worker）水平擴展幾乎無腦——每個實例對等、加一台就多一台的容量，這個模組前面幾章的設計就是為了讓服務落在這一邊。有狀態的部分（資料庫、快取、session store）不能靠複製水平擴展，因為狀態不對等——不能開兩台資料庫主庫同時寫、期待它們自己一致。</p>
<p>把這個前提判反，是水平擴展最常見的撞牆方式：用水平擴展的策略去動一個有狀態的服務，加了實例卻發現狀態沒有跟著分攤，QPS 沒漲、甚至因為協調成本更慢。判斷的第一步永遠是先分清楚：這個元件是無狀態的（水平的候選）、還是有狀態的（要另一套策略）。</p>
<h2 id="有狀態的節點垂直撐讀路徑用副本擴">有狀態的節點：垂直撐，讀路徑用副本擴</h2>
<p>有狀態的節點（典型是資料庫主庫）擴展走另一條路。寫入的部分靠垂直撐——換更大的機器,不改程式、立即見效，適合這種不能水平複製的關鍵節點。但垂直有天花板，且有狀態的關鍵節點常常比機器規格更早撞牆（交易型資料庫主庫的真實上限往往卡在架構因素、不是規格不夠），這條在拐點判斷那章講過。</p>
<p>垂直撐主庫的同時，讀路徑可以水平擴——加唯讀副本分攤讀流量，這是 <a href="/blog/devops/02-horizontal-scaling/shared-storage-selection/" data-link-title="Shared storage 選型" data-link-desc="把外置的狀態放進共享儲存時，按存取型態與狀態性質選 DB、KV、物件儲存，並處理多實例共享一個 DB 帶來的讀路徑與連線瓶頸">Shared storage 選型</a> 的讀寫分離。所以一個有狀態的資料庫，實際的擴展是混合的：寫路徑垂直撐、讀路徑用副本水平擴。垂直撐到頂、讀副本也不夠時，最後一步是分片（sharding）——把資料按某個鍵拆到多台，每台只管一部分，這才是有狀態服務真正的水平擴展，但它要重新設計資料的切分方式，成本最高、留到前面手段都用盡才動。</p>
<h2 id="混合是常態不是二選一">混合是常態，不是二選一</h2>
<p>真實系統極少是純垂直或純水平，而是按層混合：無狀態的應用層水平擴（加實例）、有狀態的資料層垂直撐加讀副本（撐不住再分片）。這對應一個擴展框架的三個方向——複製（把無狀態的元件多開幾份）、功能拆分（把不同功能拆成獨立服務、各自按自己的需求擴）、資料分片（把有狀態的資料按鍵拆開），常常同時在動、不是選一個。這個框架（AKF Scale Cube）的完整拆解在 <a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">backend 擴展軸</a>。</p>
<p>判斷的收斂是這樣：先問這一層能不能無狀態。能，就水平複製，這是最便宜的擴展。不能，先看改造成無狀態的成本值不值得；不值得或改不動，就垂直撐這個節點、讀路徑用副本水平擴、撐到極限再分片。每一步都是在「這個元件的狀態能不能被分攤」這條線上做選擇——這也是為什麼水平擴展這個模組，從頭到尾都在講無狀態。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>垂直的兩道牆、水平的隱性成本、擴展框架的完整機制 → <a href="/blog/devops/05-capacity-planning/scaling-inflection-point/" data-link-title="規模拐點判斷" data-link-desc="判斷什麼訊號代表該擴容、什麼代表可縮容、以及該往垂直還水平擴時，用飽和曲線的三段區間、膝點的早期訊號與 ramp-up 方法回來讀">規模拐點判斷</a></li>
<li>怎麼把一個服務改造成無狀態 → <a href="/blog/devops/02-horizontal-scaling/stateless-design/" data-link-title="Stateless 設計原則" data-link-desc="要把服務改成能水平擴展的無狀態設計時，釐清什麼算「本機狀態」、哪些常見寫法會破壞無狀態、隱式狀態怎麼抓、以及定時 job 這種無狀態例外怎麼處理">Stateless 設計原則</a></li>
<li>讀寫分離、讀副本、分片的儲存側設計 → <a href="/blog/devops/02-horizontal-scaling/shared-storage-selection/" data-link-title="Shared storage 選型" data-link-desc="把外置的狀態放進共享儲存時，按存取型態與狀態性質選 DB、KV、物件儲存，並處理多實例共享一個 DB 帶來的讀路徑與連線瓶頸">Shared storage 選型</a></li>
<li>擴與縮的觸發訊號、縮回怎麼做 → <a href="/blog/devops/02-horizontal-scaling/scaling-triggers/" data-link-title="擴展的觸發與縮回" data-link-desc="設計自動擴縮時，釐清擴展前要先窮盡哪些低成本手段、觸發訊號怎麼分層、以及縮回為什麼比擴容難、要先 drain 才能縮">擴展的觸發與縮回</a></li>
</ul>
]]></content:encoded></item></channel></rss>