亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

我如何將軟件系統的性能提高35,000%

> An on-call developer's worst nightmare (red indicates errors)

 

深入探討如何通過緩存,作業化,隊列分離等解決平臺的擴展性,穩定性和性能問題。

一天處理超過$ 20,000,000

先前的公司建立了用于大規模捐贈日的支付系統和捐贈日軟件,在該捐贈日中,我們為一次競選活動就收到數萬筆捐款。

我在那家公司的職責之一是擴展系統并確保其不會傾覆。 在最壞的情況下,每秒僅3–5個請求就會崩潰。

由于低效的體系結構,可疑的技術選擇以及急速的開發,它具有許多限制,而且是創可貼和巨大的性能差距的拼湊而成。 魔咒和咒語的結合將使服務器全天運行。

在使用該平臺時,它有潛力每秒處理數千個請求并同時運行數千個廣告系列,而所有這些操作的成本大致相同。

怎么樣? 我會告訴你!

分析使用模式

在深入研究如何優化該系統之前,我們必須了解其使用模式以及要在其下進行優化的特定環境和約束,否則將是在黑暗中進行拍攝。

給一天定義了開始和結束

我如何將軟件系統的性能提高35,000%

> RPS: Giving days started and ended suddenly.

 

提前幾天安排好大規模的計劃活動。 它們在非常特定的日期和時間開始和停止。 有時這些日期是可移動的。 有時不是。

強調分享

在競選期間,大力宣傳捐贈的努力可能很大。

我們的系統可能在一天的開始就發送數十萬封電子郵件,在整個活動期間定期跟蹤電子郵件,以鼓勵人們參觀,參與,共享和捐贈。

社交媒體鏈接被發布在現有的每個網絡平臺上的任何地方,其中一些我從未聽說過。

整個校園甚至還有實物海報,展位和傳單。 有些客戶甚至在整個24-48期間都進行了電視特輯。

活動既尖刻又恒定

鑒于以上所述,我們的資源使用情況可以最好地描述為尖峰和不變。

我如何將軟件系統的性能提高35,000%

> CPU: mostly constant resource usage with occasional spikes in activity.

 

在奉獻日的某些部分,例如一天的開始和社交媒體的協調推送,我們可以看到活動大量增加。 對于單個廣告系列,我們可以在不到一秒鐘的時間內從每秒0個請求增加到每秒150個請求。 這種缺乏加速的行為特征有時可能與DDoS難以區分。

在這些事件之外,資源使用情況是恒定的。 當用戶與網站互動時,我們將看到捐贈和活動。

最終,當一天結束并且活動全部結束時,活動一開始就突然下降。

有遠見的優勢

由于開始/結束日期是已知的,并且我們與客戶緊密合作以找出他們當天的游戲計劃,因此它可以為我們的服務器活動提供很多可預測性。 這種可預測性允許計劃負載。

如果我們知道客戶在日?;顒又幸_到的目標是什么,我們可以通過性能優化和調整服務器設置以最好地管理他們的預期負載來為此做準備。 可以通過一些基本計算來相對精確地估計其中的大部分。

我們正在嘗試優化什么?

現在我們知道要處理的使用方式,讓我們簡單地回顧一下我們已有的一些指標。 請記住,在優化之前,我們應該基準測試并衡量我們能做到的一切。

我們應該忘記效率低下的問題,大約有97%的時間是這樣的:過早的優化是萬惡之源。 然而,我們不應該放棄我們那關鍵的3%的機會。"

-唐納德·努斯

正如他們所說:"測量兩次,切割一次。"

對于我們的系統,我們可以將指標分為兩類:

· 衡量活動的指標

· 衡量績效的指標

測量活動

測量活動很重要。 這是服務器性能的輸入。

每秒的請求很簡單。 問一個問題:我們的服務器每秒處理多少個請求? 更多意味著更多的活動。

CPU使用率是我們密切關注的另一項指標,用于檢測系統不可用性。 密集的計算會導致系統備份,并且系統不應該首先對Web請求進行密集的計算。

內存使用情況是成敗指標。 我們的服務器上只有這么多容量。 一些低效的代碼是內存消耗,將成千上萬個對象實例化到內存中。 這些內存泄漏被發現并被壓縮。

由于我們使用的云服務提供商對連接數量有限制,因此連接計數值得關注。

衡量績效

性能的最大衡量標準是響應時間。 降低它意味著我們表現良好,提高它意味著我們表現不好。 諸如DataDog或NewRelic之類的APM工具可以向我們展示層級的響應時間,我們可以用來確定瓶頸。

從技術上講,Heroku上的整體請求響應時間限制為30秒超時,實際上,我們希望大多數面向客戶頁面的請求能在3秒內完成。 我個人認為超過8秒的任何時間都被視為中斷。

第50個百分位數通常在100毫秒以下,因為許多請求都是快速完成的API端點。

第99個百分位數可能會超過20秒而沒有問題,因為某些管理頁面僅花了一段時間才能完成。

我真正關心的是第95個百分點-我們希望95%的請求在3秒內完成。 這95%代表了大部分客戶請求和參與,并代表了捐助者將經歷的事情。

低掛優化

讓我們看一下低掛的優化成果是什么:

· 垂直和水平縮放

· N + 1個查詢

· 低效的代碼

· 背景

· 資產最小化

· 內存泄漏

· 共置

垂直和水平縮放

垂直縮放

我要做的第一件事就是增加每個服務器的功能-通過垂直擴展實現性能。 我為每個服務器提供了更多的內存和處理資源,以幫助更快地服務和滿足請求。

我如何將軟件系統的性能提高35,000%

> Here, New Relic is showing a large spike in request queue time. In this case, it was time spent wa

 

但是,垂直縮放具有一些缺點。 其中之一是您可以垂直擴展單個實例的數量有實際限制。

第二個缺點是垂直擴展會變得非常昂貴。 當您沒有無限的資源時,成本將成為主要考慮因素,也是決定權衡因素的一個因素。

水平縮放

如果一臺服務器每秒可滿足10個用戶請求,則粗略估算表明10臺服務器每秒可滿足100個請求。 實際上,它并不能完全線性地擴展,但是對于一個假設來說是很好的。 這稱為水平縮放。

我們將服務器配置為根據各種指標自動擴展。 隨著服務器啟動以處理任何增加的活動,我們發現等待延遲/排隊時間通常會出現一個小的峰值。 一旦額外的服務器完全啟動,隨著系統適應增加的負載,流量請求隊列時間就會減少。

我如何將軟件系統的性能提高35,000%

> As activity increased, we automatically spun up more servers, which allowed us to handle the incre

 

幾個挑戰

水平縮放并非一帆風順。

在代碼庫中有很多不是線程安全的實踐。 例如,在代碼庫中使用類實例變量作為共享狀態非常流行,這導致線程彼此覆蓋。我不得不花費大量時間來遍歷它,并修改算法和代碼來以某種方式管理數據 這對于多線程環境是安全的。

我還必須實現更好的連接池和管理技術-我們經常會耗盡與各個商店的連接,因為許多存儲都是硬編碼的,并在實例化時建立了直接連接,這意味著如果存在,應用程序實例將無法處理任何事務。 沒有可用的連接。

在Heroku上縮放

雖然您可以并且應該在其他平臺上設置縮放比例,但是我們使用的是Heroku,而Heroku使縮放變得容易。

您擁有可控制的測功機數量,并且具有增加每個測功機功率的能力。 如果您需要更細粒度的控件,那么像HireFire這樣易于集成的供應商將提供擴展配置選項,這些功能可為您提供強大的功能和靈活性。

您還可以設置與網絡服務器并發性相關的內容。 我們正在使用Puma,它不僅可以通過WEB_CONCURRENCY標志來更改工作程序的數量,還可以選擇更改每個進程的線程數。

結果

可自定義的垂直和水平縮放比例相結合,使我們在為各種性能特征準備場地時具有極大的靈活性。

這是一項長期的工作。 在確定將成本,性能和資源使用量平衡到可接受水平之前,我不得不在擴展閾值方面做很多工作。 由于可接受的級別在公司及其環境中會有所不同,因此我建議將其作為一種實踐,以不斷地適當地測試擴展配置。

N + 1個查詢

N + 1查詢是需要其他查詢才能完整了解數據的查詢。 它們通常是由于數據檢索注意事項或體系結構問題而引起的。

例如,假設您有一個需要返回捐贈的端點和捐贈的捐贈者。 N + 1查詢可能隱藏在其中-首先必須進行查詢以檢索所有捐贈,然后對于每次捐贈,還必須獲取捐贈者記錄。

通常,附加查詢會隱藏在檢索后的序列化器中,尤其是在Ruby on Rails中:

class DonationsController
  def index donations = Donation.all 
  end
end

class DonationSerializer
  belongs_to :donor 
  # This will result in a N+1 query (see above) 
  # because the query it is being used on doesn't load donors.
end

N + 1查詢的解決方案通常包括急于加載相關記錄并確保在初始查詢中將其提?。?/p>

Donation.all.includes(:donor)

我如何將軟件系統的性能提高35,000%

> Finding the hidden N+1 queries reduced our response times, sometimes drastically.

 

低效的代碼

在代碼中有很多實例,它們在不需要時執行資源密集型的工作。

轉向更快的庫

一些可用的庫非常慢。

對于序列化,在序列化較大的集合時,使用更快的庫(例如oj)可以大大提高性能。

流媒體

我們處理了很多Excel電子表格以及其他批量數據報告和上傳。 最初編寫了大量代碼,首先將整個電子表格加載到內存中,然后對其進行操作,這可能會占用大量時間,CPU和內存。

許多先前的現有代碼試圖在不真正了解手頭問題的情況下變得聰明和優化。 這些解決方案通常可以通過將整個工作表加載到內存中并將其推入內存高速緩存中來工作,這會導致重大問題,因為工作表仍在內存中。 它解決了使問題惡化的癥狀,而不是原因。

我不得不重寫大量代碼及其算法來支持流傳輸,以最大程度地減少內存和CPU占用空間。 這樣一來,算法和代碼就不必加載整個電子表格,這對加快處理速度具有重要作用。

將集合遍歷移動到數據庫

當數據庫可以輕松地處理它時,有很多代碼可以在應用程序中執行操作。 示例包括遍歷數千條記錄以添加一些內容,而不是計算數據庫中的總和,或者急于加載整個文檔以訪問單個字段。

我進行的一個特定代碼優化涉及用一個總數據庫查詢替換耗時數秒并運行多個查詢的長時間運行的計算。

有問題的查詢是拉出每一個捐贈的用戶,遍歷每條記錄,從該用戶那里拉相關的標簽(例如,"學生","校友"等),將它們全部合并,然后減少結果 放入一組不同的標簽中

它看起來像下面的樣子:

def get_unique_tags 
  all_tags = [] 
  @cause.donations.each{ 
    |donation| donation.cause.account.tags.each{ 
      |cause_tag| all_tags << tag if donation.tags.include?(tag.value) 
    } 
  } 
  unique_tags = [] 
  all_tags.each{ 
    |tag| unique_tags << tag unless unique_tags.include?(tag) 
  }
end

隱藏在廣告系列頁面呈現生命周期最深處的此代碼在每次單個請求時都被調用。

我如何將軟件系統的性能提高35,000%

> Much of the page time spent loading the campaign page was spent in the database (brown).

 

對于只有幾個標簽的較小捐贈天數,這不是問題,也絕不是問題。 但是,那一年的新情況是,我們的一些大客戶在捐贈當天上傳了成千上萬個不同的標簽。

我將該邏輯移到單個聚合查詢中,如下所示,結果是瞬時的:

我如何將軟件系統的性能提高35,000%

> A code optimization I did reduced the load time of most campaign pages to 447ms, down from 2500ms.

 

背景

有些事情不需要立即在網絡請求中發生-諸如發送電子郵件之類的事情可能會延遲幾秒鐘,或者完全由系統的其他部分處理。

這被稱為"背景",它會移動本應逐步執行的操作并使它們平行。

如果您可以使請求周期的一部分異步進行,則意味著響應將更快地返回給用戶,從而減少了使用的資源。

我提供了對核心生命周期無關緊要的所有內容的背景信息:電子郵件發送,上傳,報告生成等。

資產最小化

事實證明,我們的許多前端資產并未經過壓縮或優化。 這是一個相當容易的更改,將這些資產的加載時間縮短了多達70%。

我們有一個部署腳本,可以將前端資產推送到AWS S3。 我要做的就是生成并上傳壓縮的壓縮版本,同時告訴S3通過設置內容編碼和內容類型來提供gzip。

如下所示的Webpack配置將執行此操作:

plugins.push(new CompressionPlugin({ 
  test: /.(js|css)$/,}));
  let s3Plugin = new S3Plugin({ 
    s3Options: { 
      accessKeyId: <ACCESS_KEY_ID>, 
      secretAccessKey: <SECRET_ACCESS_KEY>, 
      region: <REGION> 
    }, 
    s3UploadOptions: { 
      Bucket: <BUCKET>, 
      asset: '[path][query]', 
      ContentEncoding(fileName) { 
    		if (/.gz/.test(fileName)) { return 'gzip' } 
			}, ContentType(fileName) { 
        if (/.css/.test(fileName)) { return 'text/css' } 
        if (/.js/.test(fileName)) { return 'text/JAVAscript' } 
      } 
	},
});
plugins.push(s3Plugin);

內存泄漏

我花了大量時間來尋找內存泄漏,當我們開始使用交換內存時,內存泄漏極大地降低了性能(詛咒您,R14錯誤)。

在尋找導致泄漏的實際原因時,我們做了傳統的"以特定頻率重啟服務器"創可貼。 我積極地調整了設置:我們更改了垃圾收集時間,交換了序列化程序庫,甚至將ruby垃圾收集器更改為jemalloc

內存泄漏的主題完全是一篇文章,但是這里有兩個非常有用的鏈接可以節省您的時間和精力:

· 我如何花兩周的時間來尋找Ruby中的內存泄漏

· 使用jemalloc改善ruby應用程序的內存使用率和性能

代管

我們使用的某些服務所關注的區域與服務器所在的區域不同。

我們的服務器位于弗吉尼亞北部(us-east-2),但某些服務(例如S3)位于俄勒岡州(us-west-2)。 當執行許多操作的工作流必須與該服務進行通信時,所產生的延遲會迅速加起來。

這里的幾個MS和幾個MS可以快速累加起來。 通過確保我們的服務位于同一區域,我們消除了不必要的延遲,從而極大地加快了查詢和操作的速度。

帕累托再次罷工

上面的部分說明了我為提高性能而使用的各種性能杠桿。 但是,我很快發現,它們是低落的果實。

調整和拉動杠桿可以顯著提高性能和穩定性,但很快就可以看出,系統的單個部分負責絕大部分的性能,穩定性和擴展性問題。 這完全是80/20規則。

這是瓶頸。 這是我的白鯨。

停機時間剖析

我加入公司后不久,就在一天結束的那一天,我們突然收到了來自客戶成功團隊的大量錯誤警報和瘋狂消息。

SOS很清楚:該站點已關閉且無法使用。

我如何將軟件系統的性能提高35,000%

> The pale green section is request queuing time.

 

上圖說明了發生的情況-負載顯著增加,導致該站點長時間無法使用。

隨著數據庫使用率的增加(黃色區域),每個請求處理的時間也增加了,導致其他請求開始備份和排隊(淺綠色區域)。

令人印象深刻的是停機時間的速度。 事情非常非常迅速地備份。 白天所有信號都很好,然后服務器突然不堪重負。

過時的事件響應手冊

當時我們執行了她的標準操作程序,這是啟動更多服務器。

不幸的是,它的影響為零,因為大量的計算都延遲了所有Web請求,因此增加應用服務器的數量并不能解決問題。

與直覺相反,這實際上使問題變得更糟-向服務器提供更多請求使數據庫承受更大壓力。

是什么原因造成的?

發生了什么? 我們有一個緩存系統,從所有方面來看,它都運行良好。

深入研究,我發現如何實現緩存存在多個明顯的問題。 大量的漏洞使緩存系統成為整個平臺的單點故障。

緩存為王

讓我們深入研究我們的緩存系統如何工作。

class Campaign 
cache_fields :
first_name, 
:total_raiseddef total_raised #
 ...complex calculation here endend

cache_fields將調用一個混合函數,該函數將對屬性的訪問包裝在一個函數中,該函數將在嘗試訪問屬性(或函數結果)之前先查看緩存。

但是,如果由于某種原因或其他原因在redis緩存中不存在值,會發生什么情況?

處理緩存未命中

像所有高速緩存未命中一樣,它將嘗試實時重新計算該值并提供它,將新計算的值保存到高速緩存中。

但是,這有一些問題。 如果存在緩存丟失,請求將在高負載時間內強制執行資源密集型計算。

很明顯,以前的開發人員曾考慮過這一點-代碼已經嘗試過一種解決方案:計劃緩存。

按計劃緩存

每5分鐘將運行CacheUpdateJob,它將更新所有設置為要緩存的字段。

該緩存系統在理論上運行良好-通過定期緩存,該系統可以將內容保留在緩存中。

但是,它在實踐中存在很多問題,我們在幾天的奉獻中發現了這些問題。

緩存更新

問題的主要原因是緩存的填充和更新時間。

CacheUpdateJob將每5分鐘運行一次,以盡責的方式計算值,并自計算之時起設置5分鐘的到期時間。

這是一個隱藏的問題。 從本質上講,它保證了CacheUpdateJob始終僅在值從高速緩存中丟失后才進行更新。

緩存未命中

當用戶嘗試在某個值從緩存中調出之后但在CacheUpdateJob可以緩存新值之前嘗試訪問該值時,將導致緩存未命中,從而導致實時計算該值。

對于少量的人來說,這是可以接受的,但是在主要的捐贈日,它將為每個請求執行重新計算。

我如何將軟件系統的性能提高35,000%

> Cache failures led to increased 500 Internal Server Error responses — a result of timeouts.

 

發生高速緩存未命中之后,直到任何一個請求成功完成并成功將值插入高速緩存為止,所有訪問該數據的請求都將執行資源密集型查詢,從而大大提高了使用率,尤其是在數據庫CPU上 。

對于需要大量計算的值,這意味著它可以快速阻塞數據庫的資源:

我如何將軟件系統的性能提高35,000%

> When multiple cache misses occurred, the database could get overwhelmed quickly.

 

然后,用戶的行為使問題更加復雜,并使整個問題變得更加糟糕。 當用戶遇到延遲時,他們將刷新頁面并重試,從而導致更多的額外負載:

我如何將軟件系統的性能提高35,000%

> Long-running database queries retried repeatedly caused us to lose our ability to read from the da

 

解決方案的前三分之一-垂直縮放

我實施的首批解決方案之一是垂直擴展—改進了數據庫的資源配置。

擴展數據庫只是解決該問題的一個臨時工具。 在負載增加的某個時刻,我們將再次遇到此問題。

這也是一個昂貴的解決方案-花數千美元垂直擴展數據庫集群并不是一個合理的支出。

解決方案的第二個三分之一-水平縮放

我們有一個數據庫集群,其中沒有以任何方式使用只讀副本。 我們可以轉換長期運行的報表和其他對時間敏感的查詢,以便在只讀副本而不是主副本上運行,從而將負載分布在整個集群上,而不是只分布在整個集群上。

解決方案的最后三分之一-防止比賽條件

我們需要一種方法,通過防止系統一次又一次地重新計算相同的精確數據來防止系統過載。

我解決了這一問題,方法是添加了在多個請求同時請求重新生成緩存時返回陳舊數據的功能。

只有一個請求會導致重新計算,其余請求將處理過時的數據,直到完成該計算,而不是一遍又一遍地觸發相同的計算。

Rails通過race_condition_ttl和expires_in參數的組合來支持這一點:

Rails.cache.fetch(cache_key, race_condition_ttl: 30.seconds, expires_in: 15.minutes)

火車不準時

隨著我們成功的成長,我們進行的競選活動也增加了。 反過來,這使CacheUpdateJob花費的時間越來越長,才能遍歷數千個廣告系列。

有一天,我收到了團隊遇到的潛在錯誤的通知。 他們已經在幾個小時前將電子郵件排隊,但沒有人收到。 我檢查并意識到,傳統上只有幾個作業的隊列中有成千上萬的作業-所有CacheUpdateJob。

調查進一步表明發生了什么事。 CacheUpdateJob達到了這樣的程度,即作業的運行時間要比其運行的時間長。

這意味著,盡管CacheUpdateJob每5分鐘運行一次,但要花費10多分鐘才能完成。 在此期間,紙從緩存中丟失,并且作業在隊列中堆積。 這也意味著CacheUpdateJob一直在運行,并收取相當可觀的使用費。

這阻止了所有其他工作的進行。

分成多個隊列

這里的解決方案是將我們擁有的各種作業分成多個隊列,我們可以獨立擴展。

郵件程序和其他用戶觸發的批量作業被放在一個隊列中。 事務性工作被放置在另一個中。 昂貴的報告作業被放置在第三個隊列中。 使系統保持運行狀態的作業(例如CacheUpdateJob)被放置在資源豐富的隊列中。

這有助于確保任何一個隊列中的備份不會對系統的其余部分造成很大的影響,并且使我們能夠在緊急情況下關閉系統中不需要的部分。

將觸發器與執行分開

我們進行的其他更改之一是確保CacheUpdateJob本身不會完成工作,并將此職責轉移給它排隊的其他作業。 這也使我們能夠在排隊之前檢查重復作業的存在。 如果我們已經為某個廣告系列排隊等待緩存更新,則沒有必要在隊列中添加第二個作業以緩存同一廣告系列。

這確保了我們可以與觸發緩存更新的事物并行化并獨立擴展緩存更新的處理,并以最佳方式進行。

在需要的地方分批

我意識到,拆分成單個工作的開銷抵消了最初將它們拆分出來的一些好處。

我們實施了批處理,以便CacheUpdateJob不會為每條記錄創建一個新作業,而是將記錄分為約100個左右的可自定義組。 這確保了批次較小且可以快速完成,同時仍為我們提供了所需的分離功能。

僅緩存所需的內容

我們還查看了CacheUpdateJob,發現它正在不加區別地更新緩存-甚至緩存了幾年前運行的活動。

我創建了一個設置機制,使我們可以確定每個廣告系列緩存內容的頻率。

對于不經常訪問的舊版廣告系列,我們無需費心更新這些值。 對于那些每天運行活躍的日子,我們更新的頻率更高,并且它們具有更高的緩存優先級。

內存不足

當我們付出很多天時,我們開始看到越來越多的企業成功。 業務量的增加意味著以前可以接受的內存分配突然達到了極限。

這意味著在某個時候,我們會突然開始發現我們無法將項目添加到緩存中而導致整個卡片卡癱瘓的能力出現了故障。

主要搬遷

我們確定了原因之一-我們的緩存服務器配置不正確。

我們的主要逐出過程設置為永不撤離,并且在達到內存時拋出錯誤。 這就是導致我們在負載增加的情況下達到內存限制的原因。

解決方案看起來很簡單-將Redis緩存服務器上的密鑰逐出設置為volatile-lru。 從理論上講,這將確保只有帶有TTL的鍵才會引起問題。

如果真那么容易就好了

這帶來了系統從未設計過的其他挑戰。 我們有很多值依賴于其他值進行重新計算,這些值又被用于計算其他值。

因為緩存是臨時構建的,而且是偶然的,所以這些項目中的一些預計會被緩存,而其他則不會,并且它們都有不同的TTL。

收回一段時間未使用的密鑰的行為可能會觸發一系列的再生故障,從而使系統癱瘓。

我們有一個難題:

· 我們需要逐出密鑰,以確保不會耗盡內存

· 如果我們收回任意密鑰,將導致值再生失敗

· 從架構上講,我們無法過渡到這些查詢

· 我們受到運營成本的限制,因此我們無法擴展$

這個看似棘手的問題雖然簡單易懂,卻有一個簡單的解決方案。

后備緩存

我在數據庫層實現了后備緩存。

對于我們通過cache_fields緩存的每個字段,我們還添加了隨附的時間戳和緩存值:

cache_fields :total_raised

每當更新緩存的字段時,cache_fields函數將創建并更新兩個額外的屬性:

· cached_total_raised

· cached_timestamp_total_raised

每當在Redis緩存中找不到該值時,它將使用存儲在數據庫中的值,該值永遠不會過期。 所得的提取速度比從Redis提取的速度慢,但比重新計算的速度快得多。

如果數據庫中沒有緩存的值,它將重新計算該值。

這確保了幾乎在每種情況下,緩存值都以一種或另一種形式存在,從而阻止了計算的運行,除非該值由CacheUpdateJob強制更新或由客戶成功團隊要求手動更新。

陳舊的緩存

所有這些緩存都導致了一個問題-我們經常會遇到陳舊且不再準確的舊數據。 我們通常不知道其緩存在什么級別。

一個小例子

我們遇到的情況將向您顯示一些后果。

Account.find('12345a').campaigns.limit(10)

Account.find('12345a').campaigns.limit(20)

由于我只能將其描述為過于激進的查詢緩存或ORM中的錯誤,因此如果連續運行,上述命令將返回相同的結果。

如果您之后立即執行以下操作,您將獲得更多有趣的結果:

Account.find('12345a').campaigns.limit(20).count

Account.find('12345a').campaigns.limit(20).to_a.length

奇怪的是,#count將返回20,但是#to_a將返回10。

它帶來了可怕的用戶體驗

從用戶體驗的角度來看,這是不可接受的。 人們進行捐贈時,他們希望能夠立即在總金額中看到新的捐贈。 他們不認為"哦,這個系統一定已經緩存了以前的值。"

同樣,緩存必須足夠頻繁地更新以跟蹤籌款活動的進度。 客戶成功管理團隊每天與客戶保持密切聯系,并且必須提供進度報告。 如果報告已過時,他們將無法做到這一點。

它造成了一些非常嚴重的潛在錯誤

想象一下,如果要對集合進行范圍界定以進行批量刪除。 您以為您要刪除20條記錄,但實際上是在刪除類似查詢返回的先前的記錄集。

這就是噩夢,我希望您擁有良好的備份和審核表。

解決方案—緩存清除工具

我構建了多個工具,客戶成功可使用這些工具來強制在特定隊列上進行緩存刷新。 這樣可以確保每當需要最新數據時,他們就可以擁有它們。

通過將緩存的屬性訪問器更改為接受并使用一組可選參數,我現在可以在需要的任何時候強制刷新緩存:

@campaign.total_raised(force_refresh: true)

在對新鮮度敏感的操作中,這將確保每次都處理正確類型的數據。

我還確保關鍵報告之類的功能使用了較薄的緩存層,并盡可能地利用了最新數據。

最終結果

我如何將軟件系統的性能提高35,000%

 

在所有優化的最后,我們有了一個系統,可以處理我們預期的下一個數量級的負載-每秒2000個以上的請求,數千個并發活動。 大多數面向捐助者的端點的加載時間均少于50ms,而面向客戶頁面的加載時間則在300ms之內。

這是一段漫長的旅程,進行了許多高壓部署,但最終結果不言而喻。 最終,我們有了一個在贈予日中可以忽略的系統-大部分情況下。

(本文翻譯自Joseph Gefroh的文章《How I Scaled a Software System's Performance By 35,000%》,參考:https://medium.com/swlh/how-i-scaled-a-software-systems-performance-by-35-000-6dacd63732df)

分享到:
標簽:軟件系統
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定