Saving another 100TB of RAM
https://blog.cloudflare.com/saving-100-tb-of-ram-with-math/📌 Cloudflare靠一點機率數學,省下100TB記憶體
TL;DR:Cloudflare發現負載平衡服務的consistent hashing用太多記憶體,用機率統計重新檢視了雜湊點數量的設計。
當你的服務跑在數千臺伺服器、動用數PB記憶體與數百萬CPU核心時,1%的節省聽起來微不足道,但實際換算下來可能是上百TB的等級。Cloudflare最近的一篇工程部落格,就記錄了他們如何靠重新審視一個經典演算法,省下超過100TB的全球記憶體。
🤔 一張工單,牽出的記憶體超額使用問題
故事起點是Cloudflare的Performance團隊成員Ivan提交的一張工單:內部負載平衡服務Pingora Backend Router(簡稱PBR)中,與pingora-ketama(Cloudflare開源的consistent hashing函式庫)相關的結構,記憶體用量遠超預期。PBR的作用是把可快取的請求依URL路由到伺服器,確保每個檔案在每個資料中心只需保存一份副本,並提供穩定的定位方式。
🧩 Consistent Hashing是什麼,為什麼會吃記憶體
Consistent hashing是一種常見的任務分派方法:把伺服器與任務(例如以IP位址、cache key)都透過雜湊函式映射到同一個數字線(或想像成一個環)上,每個任務會被指派給數字線上離它最近的伺服器。這個設計的好處是伺服器增減時,只需重新調整少量任務歸屬,不需要大搬風。
但問題在於:雜湊值本質上接近隨機數,所以每臺伺服器在數字線上分到的區間大小並不平均,有的伺服器可能分到特別長的一段區間,處理的請求量也會遠高於其他伺服器。
📊 用統計量化「不公平」有多嚴重
文章用期望值(Expected value)與標準差(Standard deviation)來描述這個問題。對於N臺伺服器中的其中一臺,其負責區間佔全體的比例,統計上可以算出:
- 期望值 Exp = 1/N
- 標準差 SD = (1/N) × sqrt((N-1)/(N+1))
以100臺伺服器為例,Exp = 1%,SD約等於0.99%。乍看還算合理,但如果換算成「相對於目標值的誤差比例」(也就是變異係數 CV = SD/Exp = sqrt((N-1)/(N+1))),在N=100時CV約達99%,意味著某些伺服器實際負擔可能比預期多出將近一倍,處理的請求量是「應得」份量的兩倍,而另一些伺服器則幾乎閒置。
💡 解法只能是:加更多雜湊點
Consistent hashing的簡單性是把雙面刃:因為所有東西都被壓縮成數字線上的一個點,任何改善方案也只能透過「加更多雜湊點」來實現——為每臺伺服器產生多個雜湊值,而不是只有一個。文章用大數法則的直覺解釋:單一區間的標準差雖大,但把許多小區間加總起來,忽長忽短的區間彼此抵消,整體就會趨於平均。NGINX把每臺伺服器預設雜湊點數硬編碼為160個,Pingora沿用了相同的預設值。以100臺伺服器的例子計算,把雜湊點從1個增加到160個,變異係數會從約99%大幅降至約8%。
⚠️ 素材未完整揭露的部分
原文在探討「如果再加更多雜湊點會怎樣」時被截斷,Cloudflare團隊最終如何權衡雜湊點數量與記憶體用量、具體採用了什麼調整方案來達成100TB的節省,素材並未提供細節,有興趣的讀者建議查閱原文全文。
🎯 實務啟示
如果你的系統也用了consistent hashing做負載平衡或cache路由,這篇文章提醒了一件常被忽略的事:像「每臺伺服器160個雜湊點」這種預設值,往往是套用主流函式庫(如NGINX)的慣例,而非針對自身規模重新算過的結果。在大規模部署下,雜湊點數量與記憶體使用量之間的權衡值得用機率統計重新檢視,而不是照抄預設值。
🔗 來源
- 標題:Saving another 100TB of RAM
- 作者/機構:Cloudflare(經 Hacker News 用戶 f311a 分享)
- 連結:https://blog.cloudflare.com/saving-100-tb-of-ram-with-math/
#Cloudflare #ConsistentHashing #Rust #SystemsEngineering #Pingora #LoadBalancing #PerformanceEngineering #DistributedSystems #MemoryOptimization #BackendEngineering
原始資料 Hacker News · 收集於 2026-09-19
摘要原文
Cloudflare operates at a scale so big that even after working here for years, it doesn’t seem real. We have thousands of servers all over the world with petabytes of RAM and millions of CPU cores, and all of it is pushed to the max. As vast as those resources feel, they are still finite, and when you need every service to run on every node, it doesn’t leave room for wasted space. At this scale, small improvements are greatly magnified, so even 1%-at-a-time improvements are worth celebrating. And some tweaks add up to a lot more: in this post, we’ll look at how small changes to a single algorithm reduced the memory footprint of one of our Pingora-based services significantly. That allowed us to reclaim more than 100TB of RAM globally, on top of the 100TB of memory the DNS team was able to shed last month . Waste not Maintaining equitable resource sharing between teams is not easy, especially in large organizations. One of the ways Cloudflare ensures the balance is kept is through the tireless efforts of the wonderful Performance team. This story starts with a ticket filed by Ivan who found: Excessive memory usage from pingora-ketama in Pingora Backend Router . The finding was that our internal load-balancing service, Pingora Backend Router (yes, PBR), was using significantly more memory than expected — specifically in structures associated with pingora-ketama, which is our open-source library for handling consistent hashing. In order to talk about how we addressed this seeming overuse of memory, we need to talk about what consistent hashing even is, why we are using it in PBR, and how it became so memory hungry. Along the way, we’ll learn some Rust and even a little math. Consistent hashing Consistent hashing is a widely used method for distributing tasks across multiple servers in a way that does not require large changes when servers are added or removed. Internally we use it to route cacheable requests to servers by URL. This allows us to keep only one copy of a file stored per data center and gives a stable way to find the location of each file. We have mentioned this system before , but let’s take the time to walk through how and why this algorithm is used and how it works. The key concept of consistent hashing is that while hash functions can accept any kind of input, their output is limited to a single unsigned integer (32, 64, or 128-bit integers depending on which hash function). This allows us to relate tasks and servers to each other in a consistent way. Most discussions of consistent hashing have you think of that output space as a continuous, circular ring that wraps around from its max value to zero. This depiction makes for some nice visualizations, but it can also make the simple concept of integer ranges seem more complicated than it needs to be. For our discussion, we’ll represent the 32-bit output of our hash function as a number line. Now, let’s say we have a set of servers, A, B, & C, and a set of tasks t-z. We can map each onto the number line based on the hash of their representative values, so something like IP addresses for servers and cache keys for tasks. Assigning tasks to servers is now just a matter of finding the first server to the left of each task. We can represent this visually by coloring in the region of hashes that will be associated with each server. Notice that the range covered by server C wraps around to the beginning, hence the idea that hashes exist in a ring. And that’s it. At a base level, consistent hashing is this simple — but it doesn’t take long to see that there is room for improvement. Notice that the range covered by server A in our example is significantly larger than that of either B or C. This is a problem because the fraction of the requests a server handles is going to be proportional to the size of its range on the number line. Ideally we would like to guarantee each server will have an equal size, but because hashes are essentially random numbers, we have to talk about the size of the regions in terms of statistics . 😨 Math and consequences First: don’t panic. I promise I'm not about to lie to you and that we will stay safely within the bounds of a day-one probability lesson. When we talk about statistical distributions, there are two big factors that help us quantify uncertainty in helpful ways: expected value and standard deviation . In (over-)simplified terms, expected value gives us a point where measurements based on a distribution will be centered, and standard deviation tells how close to that central point most measurements are likely to be. For consistent hashing, we can calculate these factors for the fractional size of the range associated with one of N servers. (Details on where this formula comes from later). $$m \begin{align*} \text{Exp} &= \frac{1}{N} \\ \text{SD} &= \frac{1}{N}\sqrt{\frac{N-1}{N+1}} \end{align*} m$$ In terms of concrete numbers, let’s say we have 100 servers. The formulas above give: $$m \text{Exp}=1/100 = 1\% \\ \text{SD}= \frac{1}{100}\sqrt{\frac{100-1}{100+1}} \approx 0.99\% m$$ That tells us that we can expect that the range each server handles will be centered around 0.99% of the total and most of the lengths to fall within 1% of what's expected. This sounds good until we realize that that’s 0.99% of the total length . We need to scale the standard deviation by the expected value to see how big the error is as a fraction of the target size. This value is called the coefficient of variation . $$m \text{CV} = \frac{\text{SD}}{\text{Exp}} = \sqrt{\frac{N-1}{N+1}} m$$ At $m N=100, \text{CV} \approx 99\% m$ — meaning some servers will likely be working 99% harder than they should be (handling twice as many requests) while others could be doing practically nothing! Now that we have a way to predict how evenly loaded servers will be using consistent hashing, we can start working on improvements. What if we add hashes? The simplicity of consistent hashing is a double-edged sword. It’s easy to understand and implement because everything is turned into easily-relatable hashes on the same numberline, but any improvements to the system will also need to be relatable to that numberline. That means the solution to any consistent hashing problem can only be more hashes . It’s less like a golden hammer (a tool with which all problems look like nails) and more like a golden nail in that it turns all tools into hammers. To solve the problem of imbalanced workloads, we can add multiple hashes to represent each server instead of just one. We’ll get to the math behind this momentarily, but it should make some intuitive sense that while each individual range has a large standard deviation, adding a bunch together should make their total size even out. If we take our three-server example from the above diagrams and add two more hashes at random for each server, we see that it helps even out each server’s workload. This is an admittedly contrived example. The random nature of the system means there’s no guarantee how much improvement you will get from adding 2 additional hashes per server, but it should make some intuitive sense that combining more of these hash segments together produces a more even distribution. Each segment in the sum has a chance of balancing another. Maybe one is too short; maybe one is too long. This is essentially what the law of large numbers tells us should happen… The obvious problem is it only works for large numbers. In NGINX, the baseline number of hashes per server is hardcoded to 160 , and Pingora uses the same value as the default . I’ll spare you the math for now, but if we go back to our 100-server example, if we use 160 points per server instead of just one, the coefficient of variation (which we can think of like an error margin) drops from about 99% to about 8%, a significant improvement. What if we add more hashes? (438 points, 97 comments on Hacker News)
由 claude-code/sonnet 自動生成