Jordan Sitkin 和 Luke Demi 討論了 Coinbase 應對 2017 年加密貨幣的激增的方式,以及工程師們是如何利用從這些經歷中獲得的經驗教訓來創建用
Jordan Sitkin 和 Luke Demi 討論了 Coinbase 應對 2017 年加密貨幣的激增的方式,以及工程師們是如何利用從這些經歷中獲得的經驗教訓來創建用于容量規劃的新工具和技術、為加密貨幣熱潮的未來浪潮做準備的。Luke Demi 是 Coinbase 可靠性團隊(Reliability Team)的軟件工程師。Jordan Sitkin 是 Coinbase 的軟件工程師。本文整理自他們在 QCon 的演講《Capacity Planning for Crypto Mania》。
正文
Demi:我就開門見山地說了。如果我們試圖在 2017 年 5 月訪問 Coinbase,那么,可能看到這么一個頁面。我們也許在嘗試檢查自己的余額、或在購買新的加密貨幣,或者是想從 Coinbase 的網站提取資金,但是我們卻發現加密貨幣的價格在飛漲。人們突然對購買加密貨幣真正感興趣了,所以加密貨幣的交易系統崩潰了。有點像 QCon 的網站,對吧?只是開個玩笑,不過我們迫不及待地想從負責此事的人那里看到關于這一“貨幣戰爭”的故事。
但不管怎么說,冷靜。沒錯,這個現象很糟糕,原因有很多。但主要是因為,如果當時你是 Coinbase 的客戶,那么你一定會產生擔心的情緒——你會擔心自己的錢不見了。Coinbase 剛剛被黑了,發生了什么事?我們開始看到一些可怕的說法,比如這種:“Coinbase 隨著比特幣和以太網而沒落了。”
好吧,這么說很對。但是,很傷人。這些年我們一直低調行事,嘗試建立這個生態系統,并試圖領導這場變革。這些 Reddit 上的評論,有點幽默,但抓住了那天的情緒。我再讀一條評論:“有趣的是,Coinbase 應該使用了 AWS,因此容量不該是個問題。除非他們不使用自動擴展或者太便宜而無法購買更多的 AWS 資源,或者只是懶。”
這是某天我們在一個房間里的寫照。那天非常有趣,因為我們一整天都在發愁。大概有 8 小時,我們在一個有沙發的房間里就這么坐著,想找出方法來讓網站回復原狀。那天的《紐約時報》刊登了一篇文章,稱 Coinbase 宕機了,無法處理網絡負載。對我們公司來說,這是一個重要的轉折點。我們意識到,如果我們不能團結一致,那么,Coinbase 就無法生存。
Siktin:我們把這個訪談叫做加密狂熱的容量規劃。該訪談的內容是我們如何找回最佳狀態。也就是說,我們如何從 Luke 剛介紹的、去年那個黑暗的日子開始把這個故事反轉。我們準備介紹我們在那段時間學到的東西。在那段短短幾個月的時間里,Coinbase 的流量增加了 20 倍。
我們犯了一些錯,得到了一些教訓,而且我們有幸在工作的同時進行了一些有趣的挑戰。當然,在此期間,我們的工具和系統不得不快速成熟。我們將介紹 Coinbase 從那天開始的故事,并會隨時提出一些見解和經驗。然后,我們計劃講講我們現在在做的事,為未來做更好的準備。
先簡單做個介紹。我是 Jordan,這位是 Luke。我們是 Coinbase 可靠性團隊成員。需要說明的是,在去年那場瘋狂的熱潮中,我們只是涉及其中的兩位。我們也是現在和未來負責維護系統可靠性的人。
買賣數字貨幣
正如你們中的很多人所指出的,大家已經很熟悉 Coinbase 了。對于 Coinbase,大多數人都知道是個應用程序。我們希望它成為最簡單、最值得信任的數字貨幣交易及管理場所。但是現在,Coinbase 實際上遠不止是個應用程序。我們是圍繞著 Coinbase 這一名稱的一個其他品牌和服務的小集合。為了更全面地概述我們的技術棧,今天我們在這里主要討論的是一個獨立 Rails 應用程序。它由 MongoDB 數據庫支持,其基礎設施在 AWS 上部署和管理。
在今天的演講中,我們希望告訴大家兩件重要的事情。首先,今天演講的題目是跟容量規劃有關的,但是,我們實際上會用大多數時間來談負載測試。原因是,我們覺得負載測試(或者叫容量測試、壓力測試、批量測試)是我們在容量規劃工作中最重要的工具。我的意思是,通過容量測試(或同樣術語的不同其他叫法),實際上能夠模擬和研究生產中可能發生的真實故障。這樣說應該比較容易理解吧。
其次,我們希望告訴大家,在負載測試環境中,與生產完全對等不是從負載測試系統中獲得良好結果的必須要求。我們準備解決它的方法之一是,引入我們稱之為容量周期的概念。這將是我們在整個演講中都要回歸的主題,因此,我希望現在就把它介紹給大家。我們準備詳細介紹它的含義,但我們也打算通過這種方式分享我們的一些經驗。
后端 RPM
Demi:好,我們稍微回頭看看,再講一點關于這個故事的背景。其實只是展示一下我們的流量模式在事情變糟前的樣子。我要說的是,它們很隨意。這是我們到 Rails 服務的后端流量。我們可以看到,流量一會兒增加一會兒下降,但其波動處于一個相當狹窄的范圍內。這里畫了一條紅線,沒其他特別的原因,只是表示上面的情況很糟糕,但我們離那里還很遠。
在這個時間點,紅線就意味著人們真正開始對加密貨幣、盈利能力這類東西感興趣了,它們不是我們擔心的事情類型。我們不認為我們會碰到這條紅線。然而,這條紅線最終成為一場災難。這是我們的流量圖。稍微提一下,該圖截止到 2017 年 7 月前后。這張圖展示了我們的流量變化,以及我們穿過這條紅線的方式。很顯然,這背后是以太坊和比特幣的熱潮。
我們在這條紅線上方待了幾天。有幾天我們從太平洋時間凌晨 3 點開始一直到半夜,每分鐘都有 10 萬多個請求,因為人們只是不斷嘗試登錄以購買產品。我們的系統崩了,但是沒關系,人們只是想繼續回來。對這一現象最恰當的描述就是“爆炸“,人們到處飛。這里再展示一下《紐約時報》的那篇文章。大家可以看出那段時間事情進展得有多不順利。
網絡服務時間崩潰
事情進展不順利的原因是,雖然事情進展的方式正常,但進展速度太快了。這是 New Relic,非常簡單,它顯示了我們的的 Ruby 時間和 MongoDB 時間。我們可以看到,Ruby 時間顯然占了我們應用程序內部所做的一大塊,MongoDB 時間只有一點點。
這是我們訪問網站時,通常看到的樣子。80% 的時間是 Ruby 的,一點點是 MongoDB 的,還有其他一些服務我們沒有算在這里。但是,當我們經歷這些問題時,也就是當網站宕機時,它看起來是這樣的。出于一大堆不同的原因,這讓人感到困惑。
首先,Ruby 和 MongoDB 緊密相連。我們可以看到,由于某些原因,它們是同步的。很顯然,它們上升了很多。我的意思是,大家看一下左側的圖表,這是 4 秒的響應時間,只是為了獲取 Ruby 和 Monogo。我們可以想像在那個時間點,沒人能通過。
這是個讓人非常困惑的圖表。但是,更讓人困惑的是,在那個時間點,這是我們擁有的唯一圖表。我們當時盯著這圖表看,坐在那里討論說:“好吧,顯然 Mongo 很慢,但是 Ruby 也很慢。”“還會有其他情況嗎?”“伙計,圖表就是這樣顯示的”。我們沒有使用工具深入研究正在發生的事情。
實際上,這張圖表是錯誤的。但是,它讓我們在這段時間陷入了很難搞的事情。我們花了好多天做荒謬的事。我們當時最靠譜的做法應該是:“如果我們調整內核參數會怎樣?”那在當時是有道理的。是的,也許內核就是問題所在。難搞的事情有一大堆。
驚醒夢中人
為了解釋 Jordan 提出的周期,我來描述一下當時發生的事情。我們會在每天早上,或用一整天的時間,進行一次細致的一致負載測試。當我們進而分析它時,我們完全搞錯了方向。對于工具和周期的改進部分,我們應該做的是添加工具以幫助我們理解在負載測試過程中發生的事。在那個時間點,我們沒有那么做。我們只是再次盯著同樣的工具,并思考古怪的想法。
為了解決這一問題及其中一些擴展問題,我們做了每個人都最愛做的事——把責任推到數據庫和升級版本上。我們升級了 MongoDB 的每一個版本,以讓自己保持最新的版本。如果你熟悉 MongoDB 就會知道,我們一開始使用的版本非常糟糕,因此升級版本會有一些相當大的改進。我們還做了另外一些事,比如把我們的集群分開。
舉個例子說,我們有一個用戶集和一個事務集被托管在同一個主機上,我們就把它們分開。這樣它們各自就會有更多的增長空間,這讓我們擺脫了當時的困境。然而,我們意識到,如果我們希望能夠承受更多的負載,還需要更好的方法。因為在這個時間點上,我還是感覺流量一直在增加,也就是事情仍然在變壞。情況確實如此。
然而,我們在這里得到了一個重大的教訓。這比較明顯,但是把它說清楚真的非常重要。好的工具會讓問題顯現出來。但是,糟糕的工具會讓問題變得模糊,讓人感到困惑,總有一天會讓我們看上去像個白癡。
Sitkin:回到我們的時間線,假設我們現在處于 2017 年中期。回到這張我們一直在看的流量圖表。我們可以看到,在這次突破之后,我們被迫經歷了一些慌亂、快速改進了系統,存活下來了。流量增加而又相對穩定了。我們的流量到達了新基線水平。我們基本上在這個時間點處理這個問題。我們可以看到,雖然有些波動,但我們基本上處于一種新的流量平衡中。
此時,那些來自用戶的負載測試再也沒有使我們的站點以相同的方式宕機了。我們沒有跳進這個周期,這個周期是我們后來才明白的。結果,我們對我們的工具做了很大變換,優化了我們已有的工具。但是,我們沒有足夠的創造力來強迫我們的系統創建下一個故障模式。因此,我們仍有點不安,擔心未來。為了從周期的角度來說明這一點,最終我們將其親切地稱為 YOLO 負載測試階段,可以在這里看到我們的負載測試步驟的一周版本。
我們意識到,我們需要人為地返回這個負載測試,以便回到快速改進周期。于是,我們只做了我們可以想到的最簡單的事。我們基本上只是采用了一些現成的工具,運行了一些合成負載測試。我們再次根據開發環境進行了測試。我們不是很確定從哪里開始最好,于是,我們就這么開始工作了。
實際負載測試的支柱
在繼續詳細介紹我們所做的工作之前,現在是介紹我們稱之為實際負載測試支柱概念的好時機。這是一種在三個類別中分解負載測試策略的方法,是評估實際可行性的一種更好的方法。其中一個重點是,這三個類別中的每一個都可以單獨解決,同時提高了我們負載測試的真實性。我們不一定需要把它們一起提高,或者它們彼此之間甚至不必同樣重要。
第一個我們稱為數據;現在展示的是我們數據庫中數據的形狀。類似于:在我們的測試環境中,每種類型有幾行?跟在生產中所有的相比,這些記錄之間的關系是否真實?接下來的是流量。這是從負載測試系統中出來的流量形狀。
我們可以問問自己,出現了多少請求?速率是多少?這些請求到達哪些端點?這些請求的分布是否匹配我們在生產環境中實際看到的流量類型?第三類是三者中最簡單的,那就是構成我們負載測試環境的物理系統是否匹配我們在生產中實際運行的東西?我們在負載測試環境中運行的這類服務器有多少?它們之間是如何關聯的?網絡層看起來都一樣嗎?
在這種情況下,回到我們在這個時間點真正在做的。我們從一個名為 Locust 的現成開源工具開始。我們覺得,對我們來說,這是開始實施分布式負載測試最容易的方法。我們知道,我們可以從中得到相當大的吞吐量。大家如果不熟悉 Locust,我就簡單介紹一下。
這是一個工具,允許我們用 Python 編寫模擬的用戶流,然后在多個節點上回放它們,以實施分布式負載測試。這其中,我們擁有的一個主控件是調試我們的模擬用戶池中的用戶數量,然后添加到這個池中的速度快慢。
再多講一些細節。這是 Locust 的基本架構圖。我們有一個主節點,我們與它交互來控制測試,然后,它控制一群從屬節點。這些從屬節點中的每一個負責維護一個小用戶池,這些用戶正運行在我們用 Python 編寫的模擬用戶流中。
在這個時間點上,這是我們的第一次嘗試。最終,這個測試對我們來說就是個玩具。它沒有真正提供我們所需的結果類型。但是,我們沒有真正理解它沒有提供的原因,也沒有真正相信它提供給我們的結果。但是,盡管存在這些問題,它確實有助于我們更好地理解一個良好的負載測試系統應有的樣子。
從我們剛剛介紹的這些支柱的角度來看,我們知道,數據和系統與生產不匹配。這是一種設計。我們知道,我們剛剛的測試只是用一種幼稚的先通過方式來實驗。但真正的問題是,我們不了解系統輸出流量的方式在哪些方面是不現實的。我們還沒有建立一個提高現實性的流程,甚至沒有創建這個初始用戶流。我們并沒有根據基本原理來做這件事。因此,該系統永遠不會成為我們工具箱中的重要工具,因為我們不信任它。巧合的是,這也很快變得無關緊要。
圣杯(the holy grail)
Demi:因為在 2017 年稍晚的時候,也就是 2017 年 10 月,加密貨幣真正成為主流。現在,我們把我們工作的公司稱為“媽媽都知道“的公司了。我們的應用程序在應用程序商店下載排行榜上排名第一,新聞報道開始的風格變成這樣了:“比特幣狂熱”,“加密貨幣狂熱”,“我們應該買點加密貨幣嗎?”,“我們是否應該把錢都投資到加密貨幣上?”
我們的流量現在呈現這種趨勢。這條紅線是我為了擴展而隨意畫的。并且對更大規模,流量會有更大的躍升,這也就是我們之前宕機的原因。而在這段時間里,情況沒有那么糟糕。我們肯定會在某些地方出故障,這取決于我們跟得有多近,信不信由你們。但是,我們能夠生存下來的原因是,我們這里有個東西,我們把它稱為“容量周期的圣杯“。
為了解決這個問題,也就是我們認識到的基本問題,我們進入了這個周期的節奏。比如,我們會在凌晨 4:30 醒來,這時候東海岸的人們會在想:“我應該買比特幣嗎?”他們就開始對我們的網站進行狂轟濫炸。我們花了一個早上只是為了保持網站在線。午餐時,我們會分析結果:今天宕機的原因是什么?明天我們又會因為什么宕機?
例如,假設我們在 Mongo 上的用戶集合現在已經被分解成一個新的集群,我們沒有更多空間進行垂直擴展。那么我們能做些什么?于是,我們花上一個晚上試圖改進。我們添加檢測以增加清晰度,也就是可以通過添加新功能來改進。然后,第 2 天,我們再次測試負載。這個周期會繼續下去。每天我們醒來,喝杯咖啡,“好,現在來個負載測試,”然后,下午來分析發生的事,并改進工具。
這是在這段時間,我們為該用戶集群所做的其中一些重要事情。在一個周末,我們利用了我們稱之為 Memcached 的標識緩存添加了一個完整的緩存層。這使用戶集群的負載下降了 90%。這給我們帶來了足夠的余量,至少夠用幾周。
它允許我們堅持到第二天并解決下一個問題。這個流程讓我們解決了問題。它也告訴了我們一個重要的道理:當我們在解決問題時,反饋越快,進展也越快。每次我們經歷這個周期,就是一個改進的機會。我們經歷的次數越多,能夠做出的改進次數也越多。
Sitkin:讓我們回到我們的時間線。現在是 2018 年,我們剛剛經歷過這個讓人痛苦、非常累人的周期。在這個周期中,我們得到了快速反饋周期和快速改進,并我們的系統上取得很多進展。我們進入了一個平穩期,不是每天都能創下歷史新高。我們開始不那么頻繁地創歷史新高了。你可能會認為,我們會因為體驗到這一點而感到寬慰。是的,相信我,在這幾天能多睡點真是太好了。
但是,每天早上我們都會有一種潛在的焦慮感,因為我們不再進行這個奇妙的負載測試了。在增加我們的容量方面,我們沒有這種真正的催化想法,即我們要做的最重要的事情是什么。就周期而言,基本上,這是當我們的負載測試逐步脫離我們控制的時候。有一點點害怕,因為我們知道仍然存在一些揮之不去的性能問題。很顯然,仍然要添加新功能,因為新情況還在繼續出現。
事實上,現在我們非常關注推出新貨幣。每次我們進行這項工作時,都可以看到略微不同的有趣的流量峰值,我們希望為這些情況做好準備的不同流量模式。因此,我們在這個時間點覺得有必要回到現實,恢復這個負載測試步驟。我們需要能夠在測試環境中看到實際負載。
生產流量的捕獲及回放
基于我們在 YOLO 合成負載測試方法上的經驗,我們對來自合成系統的流量并不十分自信。在我們處理這個問題的過程中,這個揮之不去的問題出現了好幾次,那就是,我們為什么不能在負載測試中使用實際的生產數據。這看起來是個很自然的問題,非常有趣。這樣,在我們的負載測試中創建宕機的方式就會自然地與我們在生產中看到的宕機類型相匹配。其原因就是,我們實際上在利用實際的用戶行為。
此外,我們非常確定 MongoDB 是目前堆棧中最敏感的部分。它是共享最多的資源,這也就是之前我們宕機的原因。但是,一般來說,單獨測試某些東西不是好主意,我希望在這里使用另一個概念,以證明我們為什么覺得在像數據庫之類的東西中進行單獨測試是有用的。
Neil Gunther 有一個通用可擴展性定律(Universal Scalability Law)的概念。這是眾多描述系統拓撲擴展的不同方法的其中之一。在這里的這張簡單圖表上,我們已經有了兩條線;虛線代表沒有共享資源的系統可以如何擴展。隨著吞吐量的增長,負載和它有完美的線性關系,因此它可以進行完美的線性擴展。這兩者之間的關系沒有理由隨著負載的增加而變化,在該系統中沒有共享資源。
但是,與此相反,我們有這條紅線,它代表有共享資源的系統可以如何擴展,因為要爭奪共享資源(如數據庫)。隨著負載的增加,吞吐量根據圍繞著共享資源的爭奪情況會下降。這個曲線的形狀會根據系統的實際設計變化。但是,這里的關鍵是,隨著系統的增長,我們在這些資源上的收益遞減。
然后,隨著負載更多的增長,我們甚至開始后退了一點點。這是一種說法,在數據庫上的負載幾乎總是讓我們失敗。正是這個原因,它才是共享資源。大多數時候,我們對像應用程序節點的東西有個相當清晰的擴展描述。這些節點是無狀態的,本身并不是共享資源,但它們通常會加載到有問題的數據庫上。
mongoreplay 加農炮(mongoreplay cannon)
因此,我們設計了圍繞負載測試數據庫的捕獲回放。最終,我們構建了被稱為 Mongoreplay 加農炮(Mongoreplay cannon)的東西。它由兩個主要部分組成;我們已經有了 Mongoreplay 捕獲流程,和我們的應用程序節點及后端工作人員有機地結合在一起。
它在數據庫驅動上創建了一個額外的套接字,用于監聽 Mongo 的有線流量并存儲它。然后,我們有另一個流程:稍后當我們準備實施負載測試時,能夠交互并處理這些捕獲文件,把它們合成一個文件,然后以實際卷的倍數回放到測試環境。這些文件通常是我們生產數據庫的克隆。
這是一個巨大的勝利。當我們在討論單獨測試數據庫時,這對我們來說非常好。我們使用它可以很好地完成一些事情,如充滿信心地正確調整我們的集群規模。因為我們知道我們可以根據數據庫的更改參數調整不同的可調參數,并確切知道它在現實世界中的響應方式。
又因為,我們在應用程序實際生成的數據庫上回放相同的負載。我們能夠在我們的測試環境中創建這些非常現實的故障。當然,接下來我們要考慮的是,如果這招真的好用,那么我們可以單獨測試數據庫。但是,用同樣的策略測試我們系統的其余部分會怎樣呢?
Demi:正如我們能看到的,很顯然,我們能夠單獨測試 MongoDB。因此,從字面上看,我們能夠在 Rails 和 Mongo 之間捕獲流量并回放之。這是我們能夠獲得的最接近的結果。然而,我們意識到,在我們的系統中有很多邊界,我們不得不想出一種方法來測試,該方法能夠與我們的 Mongoreplay 故事的成功相匹配。
這一切努力是為了確保我們不會在適當的時機宕機。如果我們不測試負載測試中的關系和邊界,那么,它可以告訴我們很多關于單獨系統的事情。但是,如果在我們的環境中稍微不那么突出的系統或其他共享資源之間發生新的回歸,那怎么辦?因此,我們知道我們需要嘗試找到不同的方法。
流量、數據及系統
讓我們從流量開始,回顧現實主義的三層或三個支柱。這些是我們以有效性為標準對它們進行的排序。例如,最底層的、最不切實際或基本的測試方法是使用這種簡單的單用戶流。因此,這意味著只測試當用戶點擊三或四個頁面時所發生的事,就像我們在運行 A/B 測試或類似的東西一樣。
第二種,也許是更現實的方法,可以根據真實的用戶流做些像合成流量生成這類的事情。比如,我們可能會有個工具可以綜合地生成流量,但卻需要查看我們的日志數據,以找出如何在新環境中進行回放。最后,Holy Grail 是這種捕獲回放的想法。我們用的是完全真實的生產流量,我們只是把它指向其他內容,并看看它的響應情況。
順便提一下,捕獲回放聽起來不錯。我們碰到的主要問題是,特別對流量來說,記錄 post body 是極其困難的。我們在 post body 中存儲了很多敏感信息,針對非生產環境或者在生產環境中重放這些信息時,會存在很多問題。此外,很多用于后續請求的 ID 并沒有被確定地生成。因此,當我們嘗試重放流量時,很難匹配那些請求。結果,我們需要做的是以某種方式重寫它們,使其工作,這有損于現實性。
然后,回過來談談數據。最簡單的方法就是,用 siege 測試開發環境的方法來測試數據庫,以便創建它們。只需要創建一些基本用戶,把它們插入負載測試框架中就可以了。下一個最佳選擇是,綜合生成數據,但用更實際的方法來實現。實際查看用戶的布局、擁有很多賬號的用戶是否有很多以及這些用戶之間的關系,然后在我們的環境中綜合地創建這些東西。
最后,這里有排在前兩位的清除數據的方法。因此,我們可以使用生產數據庫,把它放到較少的生產環境中或另一個生產環境中。但是,重要的是,要明確地刪除那些我們要保護的重要客戶信息。很顯然,獲取一個真實負載測試的最佳可能方法是在生產環境中對數據進行實際的測試。
最后,來講講系統,這是有道理的。但是,如果我們在一個簡單的開發環境中進行測試,那得不到實際的結果。如果我們的集群大小有問題,那么就不會產生實際的結果。然后,是創建生產平價環境。也許我們想做一個編譯框架,該框架可以創建我們的所有生產資源,只要把它指向另一個 AWS 賬號并運行就可以。這很現實。但是,很顯然,在生產中測試是最終的策略。我們得到了確切的生產環境,它具有所有的問題和節點等等東西。
然而,我們開始思考這個問題時,意識到捕獲回放方法很難,合成方法也很難。但是,我們是區塊鏈公司。我們為什么不能用區塊鏈解決方案來解決這個問題?于是,我們決定采用大膽的新方法。因此,今天,我們很高興地宣布,在 Coinbase 發布 Load Testing Coin。
Load Testing Coin 是兼容 ERC20 的 coin,允許你告訴你的用戶來加載測試你的負載。我在開玩笑。我們不希望把大家搞得太興奮。我們不得不這樣做,沒有隨便搞個 Load Testing Coin,那是假的。但是,事實上,那會很酷,對嗎?如果你只需要告訴用戶來,… ,不是 coin,這個 coin 是愚蠢的。說真的,你會買它嗎?
Sitkin:是的,我也許會考慮買。
Demi:我們會加油的。抱歉,這個講話是要轉錄的,對嗎?可以掐掉這段嗎?所以這里的關鍵在于,理想的解決方案是告訴實際用戶到網站來做真實的事情。就像發生在我們身上的一樣,我們希望根據需要創建密碼狂熱,但是,如果我們能激勵人們加入,那就好了。
不幸的是,這個想法被否決了。但是,事實是,我們決定要做的是,回到我們跟 Locust 的最初策略上。我們決定看看,根據這些層,我們可以對這個策略做多少改進?我們如何沿著這些層往上走,并把這個策略改進為我們日常工作中可以用的實際東西?
實踐中的容量周期
Sitkin:因此,正如 Luke 所說的,我們回到我們之前被稱為 YOLO 的測試策略。但是,我們從這個新起點開始,更好地了解我們需要負載測試系統做什么。因此,在這一節里,我將逐步介紹在我們的實際日常工作中應用這個容量周期可能的感覺。就采取負載系統而言,負載系統處于一個非常基本的狀態,然后,使用我們的周期來增加其真實性。最終,找到我們系統的一些有趣的擴展問題。
這個示例的設置在這里,我們打算盡快開始。我們將其范圍縮小到剛夠用規范工廠創建種子數據。我們可以通過數據獲得現實基線水平。然后,對于流量,我們剛打算重新創建腳本,只是一個單用戶流的單個簡化版本,只是那些最基本的可能工作。然后,系統變成基于我們已經在運行的開發環境,即位于負載平衡器背后的少數節點,沒有后臺工作人員。然后,我們的三個主要后臺數據庫每個只有一個實例。因此很直接,很簡單,很精簡。
我們開始吧。我們進行第一個負載測試,每秒最多 400 個請求。首先,Redis 上的 CPU 得到了充分利用。我們到達了上限。因此,很自然地,我們會深入查看系統是如何布局的。很明顯,這里的問題是,我們通過運行幾個不同的 Redis 集群來隔離生產中的工作負載,然后在我們的測試環境中,我們只運行這個服務一切的集群。
因此,這是一個快速的循環周期。很明顯,我們在這里能做的最簡單的事情是,讓我們的系統更加真實。于是,我們打算通過打破 Redis 集群和測試環境來改進層,以匹配我們在生產環境中所運行的東西。
好了,那么,讓我們再次經歷這個周期。結果,這一次,我們在同一個時間點宕機了。看看我們 Redis 集群 CPU 的統計數據,我們又一次看到其中之一滿負荷運作。事實證明,我們實際上只是忘了禁用一個跟蹤工具,該工具消耗了大量計算資源,而在生產中我們不使用它。因此,我們又有了一個好機會來提高我們的負載測試環境中的系統類別的真實性,即只需要禁用該工具。因此,這是又一個快速反饋周期。
好,我們再來測試一下。這次,我們走得更遠一些,每秒有 850 個請求。再我們的系統中查看這些指標,這次發生了不同的事情。我們查看 Redis 狀態,看上去都沒問題。我們查看 Mongo 統計數據,看上去也沒問題。所以我們縮小了查看范圍,我們只查看了在這個端點上我們實際看到的生產中的請求,還有我們在負載測試系統中看到的請求的端到端跟蹤,并且所有內容看起來幾乎相同。 但顯然,出現問題比我們預期的時間要早。 但事實證明,我們注意到我們的應用程序節點上的 CPU 滿負荷了。 基本上,Ruby 時間是問題的根源。
我們在這里有另一個機會來提高系統中的真實性,可以通過匹配應用程序節點和我們在生產中實際運行的支持服務之間的比例來實現。最簡單的方法是增加在測試環境中運行的應用程序節點數量,因為這讓我們與生產系統更為接近。因此,我們可以很快地做出改變,并再次完成該周期。
第四次的時候,我們走得更遠,每秒有 1500 個請求。這一次,出現的第一件事是,我們 MongoDB 集群上的 CPU 負載太高了。如果你們經常使用 MongoDB,應該知道 mloginfo 這個出色的工具。我們使用了這個工具。這確實是個非常好的工具,可以用于解析 MongoDB 服務日志,并提供特定排序細分,特別是那些查詢形狀在系統中是又昂貴又慢的。
并且,它給我們指出,有一個單獨的、從未出現過的昂貴的查詢在這里排名很高。這種情況向我們指出一個事實,即在負載測試系統和生產中的數據形狀之間可能有重大的差異。因此,這里我們意識到,在每次負載測試運行之間,我們沒有正確地重置我們的數據庫狀態。這給我們指出了一個事實,我們需要增加數據類別的真實性,例如修復此重置腳本。
現在,我們使用改進的負載測試環境進行另一次測試運行。我們在每秒 1400 次請求時出錯,非常類似的范圍。但是這一次,在 MongoDB 上的 CPU 統計數據又一次沒問題。但是,通過查看跟蹤,我們看到,事實上來自 Memcached 的響應很慢。那絕對是我們不習慣看到的。因此,我們對這個問題進行了一點研究,查看硬件級的統計數據。第一件事情是,我們嘗試針對單個 T2 micro 運行生產級的負載。T2 micro 是一個運行 Memcached 的很小實例。因此,很顯然,很快就崩潰了。
同樣,這里有個簡單的增加現實性的方法,就是提高 MongoDB 節點上實例的大小。這給我們另一個機會來完成一個周期。這一次,我們事實上做得相當好;在這個用戶流上每秒有 1700 個請求。從資源的角度看,這實際上是特別昂貴的流。通常,我們在生產中不經常碰到這種負載。在測試環境中達到每秒 1750 個請求是非常令人鼓舞的;這是個好結果。
Mongo 上的 CPU 統計數據看起來相當不錯,就像我們的跨堆棧硬件統計數據一樣,看起來相當健康。這讓我們稍作停頓,因為,假如我們這里的用戶流事實上以非常逼真的方法來運行的話,那么,我們自然不會期望能夠提供這類負載。
因此,這讓我們意識到,我們在生產環境中所看到的和我們在負載測試環境中所看到的之間,可能在流量類別上有差異。再深入一點,我們注意到,這絕對是事實。為了在我們的應用程序中重現這個用戶行為,我們只是運行了一小部分真正需要點擊的端點。通過更好地調查和編寫更好的腳本,以更好地重建用戶在實際執行該流程時在日志中所看到的內容,那么,我們能夠提高流量類別的真實性。
最后一次運行這個周期,這次,我們每秒最多得到 700 個請求。根據我們可能期望看到的情況,這可能更為現實。分析表明,我們大部分堆棧的硬件統計數據,特別是 Mongo 沒有問題。我們習慣于看到 Mongo 中的故障,因此,這常常是我們在數據庫層首先查看硬件統計數據的地方。
然而,我們 web 節點上的 CPU 也滿負荷。上次我們遇到這個問題時,我們覺得測試環境中的應用程序節點增加了一倍,因為,這讓我們到了更接近生產環境的地方。但是,這次我們不打算這么做,因為在負載測試中運行的節點數量與在生產環境中的節點數量的比例是準確的。因此,這么做就不對,會因為添加更多節點而破壞我們的真實性。
但是,當我們真正挖掘一些分析以查看我們的應用程序在 CPU 方面做了什么事、實際上占用了什么的時候,我們發現了一些有趣的事情。這是一個我們已經意識到的問題。但是,我們注意到,這個特定的性能問題是造成我們這次測試失敗的瓶頸。我們有一些事件跟蹤的處理十分低效。
因此,這里,這是我們希望得到的結果。我們實際上發現了一個令人信服的現實瓶頸。當我們達到這個負載水平時,這可能會讓我們在生產中宕機。實際上,這里要做的正確的事情是應用修復。因此,我們改進了系統。現在我們可以跳回到循環周期中,并獲得更多的見解。
我把這個例子留在這里。但是,我希望,通過講解這個真實世界的例子,我已經讓你知道,這個快速反饋周期有多么的強大。它可以在測試下改進系統,也可以改進測試系統本身。循環周期的每次迭代都是一個改進這些東西其中之一的機會。不久之后你就會發現,事實上最重要的問題在哪里。
關于這些問題,還要注意一件重要的事情,即我們也許意識到我們的系統有很多潛在的性能問題。但是,真正的價值在于,它迫使這些問題中的其中一個變為最突出的、最可能讓我們失敗的問題。于是,這讓我們的工作計劃變得非常容易。這是最重要的事,它實際會把能力提升到一個新的水平。
現在,我們在這里,對未來有一些更大的計劃。但是,我希望在本節留下一個經驗,即我們都在負載測試環境中追求真實性。但是,關鍵在于,隨著我們每一步都在增加真實性,這個過程就和最終目標一樣重要,因為我們一路上要學很多東西。
未來
Demi:我們今天就是為此事而來的。我們現在在用的這個負載是為新貨幣的發行做準備的。現在,這更像是 Luke 和 Jordan 的工具。我們能夠用它來找出和確認每次我們發行新貨幣時會引發流量激增的任何事情。我們可以測試那些已知的在這些過程中將會變得突出的流,并確保沒有在它們發生前就存在,而我們卻還沒有發現的瓶頸。
我們目前正在增加這些測試的真實性。構建自動化方法來了解最新的流量模式,并把它們應用到我們實際在運行的測試方法中,這是我們目前的首要任務。同時,我們要繼續改進我們使用數據的方法。我們希望很快得到的結論是,我們的流量基于完全真實的流量,并且,我們的數據基于真實的清洗用戶數據。理想情況下,我們的系統也是一樣的。
這個周期將繼續指導我們。我們對這個容量周期感到非常興奮。我們在內部對每個人宣傳此周期的有效性;有些人相信也有些人不信。但是,我們內部真正要做的就是堅持這個想法。我們不希望這只是個 Luke 和 Jordan 的工具。因為 Luke 和 Jordan 的工具無法長久存活。當我們在做其他事情時分心了,怎么辦?如果這個工具開始分離崩析,怎么辦?它會變得毫無用途。
我們正在嘗試做的是,創建一個可重復的持續元素以實現把我們的代碼交付到生產中。我們希望為大家創建這個本質上是快速反饋的循環周期。因此,當有人推動新的生產變革時,他們應該知道它會不會影響生產工作中的績效。因此,我們處理這個問題的一些方法可能成為壓縮測試環境中構建過程的一部分,比如一個非常一致的環境。我們能夠處理多少請求來掌控最重要的流?這些是我們現在要關注的事情。
教訓
但是,在我們離開之前,我想再快速回顧一下我們的教訓。首先,良好的工具會讓問題浮現出來,而糟糕的工具會使問題變得模糊不清,讓我們感到困惑。我認為,這適用于幾乎任何事情,特別是,如果我們是那些經常在生產中挖掘問題的人。如果我們沒有合適的工具,那么就添加合適的工具。如果我們不了解我們的工具,那么要保持好奇,去研究它。不要以為我們看到的就是實際存在的。
更快的反饋意味著更快的進展。這適用于一切,但是,特別適用于我們的生存能力,以及在這些瘋狂流量增長時期有所發展。這只是因為,我們能夠使用這個快速反饋來指導我們采取的行動。最終,我們使用簡化的負載測試環境獲得出色的負載測試結果,并增加了真實性。
因此,當我們進行負載測試時,過程和目標一樣有價值。不要害怕深入,立即開始以取得成果。我們過去曾經遇到問題,我們已經經歷了無數次宕機。我們一開始就想到真實性,并試圖讓一切都和生產環境中的一樣,但是,在這個過程中,我們失去了一路上所學到的東西。
演講到此結束。最終,我們學到了關于容量規劃的很多真正有價值的經驗教訓。我們當然希望這個演講能幫助你避免犯我們已經犯過的錯。這是我們的推特號。但是,我們真的很想知道你們是如何在現場進行負載測試的。希望這能激發一些有趣的對話。當然,也可以在會議的其他時間通過推特聯系我們。
問與答
參與者 1:謝謝你們的演講。很有見解,真實地反映了我們團隊現在正在進行的一些活動。我很想從你們的經驗中學到東西,并把它應用到我的團隊中。我的一個問題是,你們是從哪里觸發這些任務的?如果負載測試從生產負載中減去 5 倍、10 倍、50 倍,那么,你們會延伸到什么程度?你們是否會滿足于“好吧,目前的設計就這樣了,它能夠支持什么”?
Sitkin:Locust 提供了一個 web UI,讓我們真正開始測試。因此,這是使用這個開源工具的好處之一。為了回答你關于我們如何決定將測試提升到什么程度的問題,到目前為止,我們基本上把它設置在一個荒謬的程度上,我們在尋找性能開始下降、服務崩潰的時間點。
因此,在這個時間點上,我們真正在做壓力測試。我們在尋找系統停止工作的時間,然后只是測量這個程度是什么,不斷地將它增加。我們對想要達到的目標有個粗略的想法,這是上次在解決中讓我們宕機原因的一個粗略倍數。但是,其中很大一部分只是意識到特定用戶流從哪里開始消失。
參與者 1:還有一個問題。由于依賴服務不受你們控制,你們是如何使用依賴服務的?如果這些是瓶頸,你們有什么建議來解決它們,甚至和這些團隊合作來解決問題?
Sitkin:是的,這就是我說的負載測試中有點藝術性的一部分。我們在開始時非常簡單的一個好處是,如果很難開始,那么就放一邊。然后,當你對一小部分流的真實性有信心時,就可以開始逐個把這些依賴項添加進去。它們中的每一個都會有自己的特性,關于刪除或模擬的最好方法。
例如,作為加密貨幣公司,我們最終觸及的很多事情都是區塊鏈。當然,我們必須創造性地剔除這些東西。我們用這樣的東西只能實現一定程度的真實性。因此,我發現很難對這類問題給出一個普遍的答案。但是,我認為,回到流程,一切都是關于逐一解決它們的問題。獲得快速反饋周期,并一次只添加一個。不是一下子就追求完全的真實性。因為通過添加一個更改獲得的觀察結果可能非常有用。
參與者 2:謝謝你的精彩演講。我的問題是,作為一家快速發展的公司,當你們飛快地發布新產品和新功能時,你們是否在生產中看到,數據的形狀和請求的模式經常變化?如果是這樣,你們如何讓你們的負載測試環境跟得上這些改變?
Demi:是的,特別是在流量方面,這是使用捕獲回放的主要好處之一。你可以保證捕獲并再次運行,你可以保證流量是最新的。但是。我們現在的處理方法是。通過一個腳本運行于數據的各個部分,然后我們提取其中存在的流。因此,存在一定的有狀態流。因此,你不能只是按順序回放請求,需要把它們拉出來。
我們能夠做的是,生成數據形狀,然后把它們應用到 Locust 上。這個部分事實上更容易。數據的形狀實際上極其困難。這是我們完全沒解決的問題。我們知道數據的形狀是什么樣的,我們實際上能夠使用我們數據團隊在用的分析工具。
它們能夠在我們擁有的最大用戶上指導我們,比方說,10000 個不同的交易,對嗎?因此,我們可以在那里構建一個分布,但是要保持它是最新的,我們根本沒解決這個問題。這很難。但是,理想情況下,我們可以做的是,與這些工具進行交互,并在我們清除和更新測試時進行調整。
參與者 3:很棒的演講。我想知道,因為生成負載測試是相當昂貴的,基本上像攻擊,因為我們試圖讓網站宕機。我想知道,你是否找到一個方法可以計算實際情況下的理論極限值?是否可以根據一些例子,據此推斷并找到下一個瓶頸在哪里?
Sitkin:我接觸過一點通用擴展理論的概念。當我們實施這個有點巧妙的方法進行負載測試時,這些是我們要注意的事情。這也有助于我們形成關于什么可能出錯的假設。但是,我認為,我們發現進入循環周期的好事是,我們很快就能測試這些假設。這也有點讓我們的性能理論方法變得更好,看到這些東西與現實世界的真實關系。但是,為了更直接地回答你的問題,我們還沒有做類似學術研究的方法。我們一直在實踐中關注那些我們系統中產生的啟發。你有什么不同意見嗎?
Demi:代價不菲。現在我們要做的是,構建一些真正復雜的技術,以讓我們不因為財務問題而失敗。這就涉及到如何進行擴展。幸運的是,AWS 使這個變得非常簡單。數據庫擴展的方式,應用程序服務擴展的方式,我們有一個腳本來啟動和關閉。這是有幫助的。
但是,我們喜歡做的是,當我們接受這種擠壓測試的想法時,每一個進入我們主要項目的拉取請求或提交,這就是我們打算做的事。我認為,我們還沒有真正討論作為一個開始,我們可以做什么,但是,使用我們在擠壓測試環境中目前所有的相同數據,并在完整的生產環境中也這么做,來看看它們能處理多少。然后,我們可以進行推斷,那將是個相當不錯的處理方法。你對這個想法有什么看法?
Sitkin:好主意。Luke。
Demi:一起做個演講不容易。我們必須對每個想法進行交流。我很高興你喜歡這樣。