Linux中國

擴展一個 GraphQL 網站

我通常會抽象地總結我為他人所做的工作(出於顯而易見的原因),但是我被允許公開談論一個網站:Vocal 。我去年為它做了一些 SRE 工作。實際上早在 2 月份,我就在 GraphQL 悉尼會議上做過一次演講,不過這篇博客推遲了一點才發表。

Vocal 是一個基於 GraphQL 的網站,它獲得了人們的關注,然後就遇到了可擴展性問題,而我是來解決這個問題的。這篇文章會講述我的工作。顯然,如果你正在擴展一個 GraphQL 站點,你會發現這篇文章很有用,但其中大部分內容講的都是當一個站點獲得了足夠的流量而出現的必須解決的技術問題。如果你對站點可擴展性有興趣,你可能想先閱讀 最近我發表的一系列關於可擴展性的文章

Vocal

Vocal 是一個博客平台,內容包括日記、電影評論、文章評論、食譜、專業或業餘攝影、美容和生活小貼士以及詩歌,當然,還有可愛的貓貓和狗狗照片。

Vocal 的不同之處在於,它允許人們製作觀眾感興趣的作品而獲得報酬。作者的頁面每次被瀏覽都可以獲得一小筆錢,還能獲得其他用戶的捐贈。有很多專業人士在這個平台上展示他們的工作,但對於大多數普通用戶來說,他們只是把 Vocal 當作一個興趣愛好,碰巧還能賺些零花錢作為獎勵。

Vocal 是新澤西初創公司 Jerrick Media 的產品,更新:Jerrick Media 已經更名為 Creatd,在納斯達克上市。2015 年,他們與 Thinkmill 合作一起開發,Thinkmill 是一家悉尼中型軟體開發諮詢公司,擅長 JavaScript、React 和 GraphQL 開發。

劇透

不幸的是,有人告訴我,由於法律原因,我不能提供具體的流量數字,但公開的信息可以說明。Alexa 對所有網站按照流量進行排名。這是我演講中展示的 Alexa 排名圖,從 2019 年 11 月到今年 2 月,Vocal 流量增長到全球排名第 5567 位。

去年 11 月到今年 2 月 Vocal 的全球排名從 9574 名增長到 5567 名

曲線增長變慢是正常的,因為它需要越來越多的流量來贏得每個位置。Vocal 現在排名 4900 名左右,顯然還有很長的路要走,但對於一家初創公司來說,這一點也不寒酸。大多數初創公司都很樂意與 Vocal 互換排名。

在網站升級後不久,Creatd 開展了一項營銷活動,使流量翻了一番。在技術方面,我們要做的就是觀察儀錶盤上的上升的數字。自發布以來的 9 個月里,只有兩個平台問題需要員工干預:3 月份每五年一次的 AWS RDS 證書輪換,以及一款應用推出時遇到的 Terraform 錯誤。作為一名 SRE,我很高興看到 Vocal 不需要太多的平台工作來保持運行。更新:該系統也抗過了 2020 年的美國大選,沒有任何意外。

以下是本文技術內容的概述:

  • 技術和歷史背景
  • 從 MongoDB 遷移到 Postgres
  • 部署基礎設施的改造
  • 使應用程序兼容擴展措施
  • 讓 HTTP 緩存發揮作用
  • 其他一些性能調整

一些背景信息

Thinkmill 使用 Next.js(一個基於 React 的 Web 框架)構建了一個網站,和 Keystone 在 MongoDB 前面提供的 GraphQL API 進行交互。Keystone 是一個基於 GraphQL 的無頭 CMS 庫:在 JavaScripy 中定義一個模式,將它與一些數據存儲掛鉤,並獲得一個自動生成的 GraphQL API 用於數據訪問。這是一個自由開源軟體項目,由 Thinkmill 提供商業支持。

Vocal V2

Vocal 的第一版就受到了關注,它找到了一個喜歡它的用戶群,並不斷壯大,最終 Creatd 請求 Thinkmill 幫助開發 V2,並於去年 9 月成功推出。Creatd 員工避免了 第二個系統效應,他們一般都是根據用戶的反饋進行改變,所以他們 主要是 UI 和功能更改,我就不贅述了。相反,我將討論下我的工作內容:使新站點更加健壯和可擴展。

聲明:我很感謝能與 Creatd 以及 Thinkmill 在 Vocal 上的合作,並且他們允許我發表這個故事,但 我仍然是一名獨立顧問,我寫這篇文章沒有報酬,甚至沒有被要求寫它,這仍然是我自己的個人博客。

遷移資料庫

Thinkmill 在使用 MongoDB 時遇到了幾個可擴展性問題,因此決定升級到 Keystone 5 以利用其新的 Postgres 支持。

如果你從事技術工作的時間足夠長,那你可能還記得 00 年代末的 「NOSQL」 營銷,這可能聽起來很有趣。NoSQL 的一個重要特點是,像 Postgres 這樣的關係資料庫(SQL)不像 MongoDB 這樣「網站級規模」的 NoSQL 資料庫那樣具有可擴展性。從技術上將,這種說法是正確的,但 NoSQL 資料庫的可擴展性來自它可以有效處理各種查詢的折衷。簡單的非關係資料庫(如文檔資料庫和鍵值資料庫)有其一席之地,但當它們用作應用的通用後端時,應用程序通常會在超出關係資料庫的理論擴展限制之前,就超出了資料庫的查詢限制。Vocal 的原本的大多數資料庫查詢在 MongoDB 上運行良好,但隨著時間推移,越來越多的查詢需要特殊技巧才能工作。

在技術要求方面,Vocal 與維基百科非常相似。維基百科是世界上最大的網站之一,它運行在 MySQL(或者說它的分支 MariaDB)上。當然,這需要一些重要的工程來實現,但在可預見的未來,我認為關係資料庫不會對 Vocal 的擴展構成嚴重威脅。

我做過一個比較,託管的 AWS RDS Postgres 實例的成本不到舊 MongoDB 實例的五分之一,但 Postgres 實例的 CPU 使用率仍然低於 10%,儘管它提供的流量比舊站點多。這主要是因為一些重要的查詢在文檔資料庫架構下一直效率很低。

遷移的過程可以新寫一篇博客文章來講述,但基本上是 Thinkmill 開發人員構建了一個 ETL 管道,使用 MoSQL 來完成這項繁重的工作。由於 Keystone 對於 Postgres 支持仍然比較基礎,但它是一個 FOSS 項目,所以我能夠解決在 SQL 生成性能方面遇到的問題。對於這類事情,我總是推薦 Markys Winand 的 SQL 博文:使用 Luke 索引現代 SQL。他的文章很友好,甚至對那些暫時不太關注 SQL 人來說也是容易理解的,但他擁有你大多數需要的理論知識。如果你仍然有問題,一本好的、專註於即可性能的書可以幫助你。

平台

架構

V1 是幾個 Node.js 應用,運行在 Cloudflare(作為 CDN)背後的單個虛擬專用伺服器(VPS)上。我喜歡把避免過度工程化作為一個高度優先事項,所以我對這一架構豎起了大拇指。然而,在 V2 開始開發時,很明顯,Vocal 已經超越了這個簡單的架構。在處理巨大峰值流量時,它沒有給 Thinkmill 開發人員提供很多選擇,而且它很難在不停機情況下安全部署更新。

這是 V2 的新架構:

Vocal V2 的技術架構,請求從 CDN 進入,然後經過 AWS 的負載均衡。負載均衡將流量分配到兩個應用程序 「Platform」 和 「Website」。「Platform」 是一款 Keystone 應用程序,將數據存儲在 Redis 和 Postgres 中。

基本上就是兩個 Node.js 應用程序複製放在負載均衡器後面,非常簡單。有些人認為可擴展架構要比這複雜得多,但是我曾經在一些比 Vocal 規模大幾個數量級的網站工作過,它們仍然只是在負載均衡器後面複製服務,帶有 DB 後端。你仔細想想,如果平台架構需要隨著站點的增長而變得越來越複雜,那麼它就不是真正「可擴展的」。網站可擴展性主要是解決那些破壞可擴展的實現細節。

如果流量增長得足夠多,Vocal 的架構可能需要一些補充,但它變得更加複雜的主要原因是新功能。例如,如果(出於某種原因)Vocal 將來需要處理實時地理空間數據,那將是一個與博客文章截然不同的技術,所以我預期它會進行架構上的更改。大型網站架構的複雜性主要來自於複雜的功能。

不知道未來的架構應該是什麼樣子很正常,所以我總是建議你儘可能從簡單開始。修復一個簡單架構要比複雜架構更容易,成本也更低。此外,不必要的複雜架構更有可能出現錯誤,而這些錯誤將更難調試。

順便說一下,Vocal 分成了兩個應用程序,但這並不重要。一個常見的擴展錯誤是,以可擴展的名義過早地將應用分割成更小的服務,但將應用分割在錯誤的位置,從而導致更多的可擴展性問題。作為一個單體應用,Vocal 可以擴展的很好,但它的分割做的也很好。

基礎設施

Thinkmill 有一些人有使用 AWS 經驗,但它主要是一個開發車間,需要一些比之前的 Vocal 部署更容易上手的東西。我最終在 AWS Fargate 上部署了新的 Vocal,這是彈性容器服務(ECS)的一個相對較新的後端。在過去,許多人希望 ECS 作為一個「託管服務運行 Docker 容器」的簡單產品,但人們仍然必須構建和管理自己的伺服器集群,這讓人感到失望。使用 ECS Fargate,AWS 就可以管理集群了。它支持運行帶有複製、健康檢查、滾動更新、自動縮放和簡單警報等基本功能的 Docker 容器。

一個好的替代方案是平台即服務(PaaS),比如 App Engine 或 Heroku。Thinkmill 已經在簡單的項目中使用它們,但通常在其他項目中需要更大的靈活性。在 PaaS 上運行的網站規模要大得多,但 Vocal 的規模決定了使用自定義雲部署是有經濟意義的。

另一個明顯的替代方案是 Kubernetes。Kubernetes 比 ECS Fargate 擁有更多的功能,但它的成本要高得多 —— 無論是資源開銷還是維護(例如定期節點升級)所需的人員。一般來說,我不向任何沒有專門 DevOps 員工的地方推薦 Kubernetes。Fargate 具有 Vocal 需要的功能,使得 Thinkmill 和 Creatd 能專心於網站改進,而不是忙碌於搭建基礎設施。

另一種選擇是「無伺服器」功能產品,例如 AWS Lambda 或 Google 雲。它們非常適合處理流量很低或很不規則的服務,但是 ECS Fargate 的自動伸縮功能足以滿足 Vocal 的後端。這些產品的另一個優點是,它們允許開發人員在雲環境中部署東西,但無需了解很多關於雲環境的知識。權衡的結果是,無伺服器產品與開發過程、測試以及調試過程緊密耦合。Thinkmill 內部已經有足夠的 AWS 專業知識來管理 Fargate 的部署,任何知道如何開發 Node.js 簡單的 Hello World 應用程序的開發人員都可以在 Vocal 上工作,而無需了解無伺服器功能或 Fargate 的知識。

ECS Fargate 的一個明顯缺點是供應商鎖定。但是,避免供應商鎖定是一種權衡,就像避免停機一樣。如果你擔心遷移,那麼在平台獨立性花費比遷移上更多的錢是沒有意義的。在 Vocal 中,依賴於 Fargate 的代碼總量小於 500 行 Terraform。最重要的是 Vocal 應用程序代碼本身與平台無關,它可以在普通開發人員的機器上運行,然後打包成一個 Docker 容器,幾乎可以運行在任何可以運行 Docker 容器的地方,包括 ECS Fargate。

Fargate 的另一個缺點是設置複雜。與 AWS 中的大多數東西一樣,它處於一個 VPC、子網、IAM 策略世界中。幸運的是,這類東西是相對靜態的(不像伺服器集群一樣需要維護)。

製作一個可擴展的應用程序

如果你想輕鬆地運行一個大規模的應用程序,需要做一大堆正確的事情。如果你遵循 應用程序設計的十二個守則 the Twelve-Factor App design ,事情會變得容易,所以我不會在這裡贅述。

如果員工無法規模化操作,那麼構建一個「可擴展」系統就毫無意義 —— 就像在獨輪車上安裝噴氣式發動機一樣。使 Vocal 可擴展的一個重要部分是建立 CI/CD 和 基礎設施即代碼 之類的東西。同樣,有些部署的思路也不值得考慮,因為它們使生產與開發環境相差太大(參閱 應用程序設計守則第十守則)。生產和開發之間的任何差異都會降低應用程序的開發速度,並且(在實踐中)最終可能會導致錯誤。

緩存

緩存是一個很大的話題 —— 我曾經做過 一個關於 HTTP 緩存的演講,相對比較基礎。我將在這裡談論緩存對於 GraphQL 的重要性。

首先,一個重要的警告:每當遇到性能問題時,你可能會想:「我可以將這個值放入緩存中嗎,以便再次使用時速度更快?」微基準測試 總是 告訴你:是的。 然而,由於緩存一致性等問題,隨處設置緩存往往會使整個系統 變慢。以下是我對於緩存的檢查表:

  1. 是否需要通過緩存解決性能問題
  2. 再仔細想想(非緩存的性能往往更加健壯)
  3. 是否可以通過改進現有的緩存來解決問題
  4. 如果所有都失敗了,那麼可以考慮添加新的緩存

在 Web 系統中,你經常使用的一個緩存是 HTTP 緩存系統,因此,在添加額外緩存之前,試著使用 HTTP 緩存是一個好主意。我將在這篇文章中重點討論這一點。

另一個常見的陷阱是使用哈希映射或應用程序內部其他東西進行緩存。它在本地開發中效果很好,但在擴展時表現糟糕。最好的辦法是使用支持顯式緩存庫,支持 Redis 或 Memcached 這樣的可插拔後端。

基礎知識

HTTP 規範中有兩種類型緩存:私有和公共。私有緩存不會和多個用戶共享數據 —— 實際上就是用戶的瀏覽器緩存。其餘的就是公共緩存。它們包括受你控制的(例如 CDN、Varnish 或 Nginx 等伺服器)和不受你控制的(代理)。代理緩存在當今的 HTTPS 世界中很少見,但一些公司網路會有。

緩存查找鍵通常基於 URL,因此如果你遵循「相同的內容,相同的 URL;不同的內容,不同的 URL」 規則,即,給每個頁面一個規範的 URL,避免從同一個 URL 返回不同的內容這樣「聰明」的技巧,緩存就會強壯一點。顯然,這對 GraphQL API 端點有影響(我將在後面討論)。

你的伺服器可以採用自定義配置,但配置 HTTP 緩存的主要方法是在 Web 響應上設置 HTTP 頭。最重要的標頭是 cache-control。下面這一行說明所有緩存都可以緩存頁面長達 3600 秒(一小時):

cache-control: max-age=3600, public

對於針對用戶的頁面(例如用戶設置頁面),重要的是使用 private 而不是 public 來告訴公共緩存不要存儲響應,防止其提供給其他用戶。

另一個常見的標頭是 vary,它告訴緩存,響應是基於 URL 之外的一些內容而變化。(實際上,它將 HTTP 頭和 URL 一起添加到緩存鍵中。)這是一個非常生硬的工具,這就是為什麼儘可能使用良好 URL 結構的原因,但一個重要的用例是告訴瀏覽器響應取決於登錄的 cookie,以便在登錄或註銷時更新頁面。

vary: cookie

如果頁面根據登錄狀態而變化,你需要 cache-control:private(和 vary:cookie),即使是在公開的、已登出的頁面版本上,以確保響應不會混淆。

其他有用的標頭包括 etaglast-modified,但我不會在這裡介紹它們。你可能仍然會看到一些諸如 expirespragma:cache 這種老舊的 HTTP 標頭。它們早在 1997 年就被 HTTP/1.1 淘汰了,所以我只在我想禁用緩存或者我感到偏執時才使用它們。

客戶端標頭

鮮為人知的是,HTTP 規範允許在客戶端請求中使用 cache-control 標頭以減少緩存時間並獲得最新響應。不幸的是,似乎大多數瀏覽器都不支持大於 0 的 max-age ,但如果你有時在更新後需要一個最新響應,no-cache 會很有用。

HTTP 緩存和 GraphQL

如上,正常的緩存鍵是 URL。但是 GraphQL API 通常只使用一個端點(比如說 /api/)。如果你希望 GraphQL 查詢可以緩存,你需要查詢參數出現在 URL 路徑中,例如 /api/?query={user{id}}&variables={"x":99}(忽略了 URL 轉義)。訣竅是將 GraphQL 客戶端配置為使用 HTTP GET 請求進行查詢(例如,apollo-link-http 設置為 useGETForQueries )。

Mutation 不能緩存,所以它們仍然需要使用 HTTP POST 請求。對於 POST 請求,緩存只會看到 /api/ 作為 URL 路徑,但緩存會直接拒絕緩存 POST 請求。請記住,GET 用於非 Mutation 查詢(即冪等),POST 用於 Mutation(即非冪等)。在一種情況下,你可能希望避免使用 GET 查詢:如果查詢變數包含敏感信息。URL 經常出現在日誌文件、瀏覽器歷史記錄和聊天中,因此 URL 中包含敏感信息通常是一個壞主意。無論如何,像身份驗證這種事情應該作為不可緩存的 Mutation 來完成,這是一個特殊的情況,值得記住。

不幸的是,有一個問題:GraphQL 查詢往往比 REST API URL 大得多。如果你簡單地切換到基於 GET 的查詢,你會得到一些非常長的 URL,很容易超過 2000 位元組的限制,目前一些流行的瀏覽器和伺服器還不會接受它們。一種解決方案是發送某種查詢 ID,而不是發送整個查詢,即類似於 /api/?queryId=42&variables={"x":99}。Apollo GraphQL 伺服器對此支持兩種方式:

一種方法是 從代碼中提取所有 GraphQL 查詢,並構建一個伺服器端和客戶端共享的查詢表。缺點之一是這會使構建過程更加複雜,另一個缺點是它將客戶端項目與伺服器項目耦合,這與 GraphQL 的一個主要賣點背道而馳。還有一個缺點是 X 版本和 Y 版本的代碼可能識別一組不同的查詢,這會成為一個問題,因為 1:複製的應用程序將在更新推出或回滾期間提供多個版本,2:客戶端可能會使用緩存的 JavaScript,即使你升級或降級伺服器。

另一種方式是 Apollo GraphQL 所宣稱的 自動持久查詢(APQ)。對於 APQ 而言,查詢 ID 是查詢的哈希值。客戶端向伺服器發出請求,通過哈希查詢。如果伺服器無法識別該查詢,則客戶端會在 POST 請求中發送完整的查詢,伺服器會保存此次查詢的散列值,以便下次識別。

HTTP 緩存和 Keystone 5

如上所述,Vocal 使用 Keystone 5 生成 GraphQL API,而 Keystone 5 和 Apollo GraphQL 伺服器配合一起工作。那麼我們是如何設置緩存標頭的呢?

Apollo 支持 GraphQL 模式的 緩存提示 cache hint 。巧妙地是,Apollo 會收集查詢涉及的所有內容的所有緩存提示,然後它會自動計算適當的緩存標頭值。例如,以這個查詢為例:

query userAvatarUrl {
    authenticatedUser {
        name
        avatar_url
    }
}

如果 name 的最長期限為 1 天,而 avatar_url 的最長期限為 1 小時,則整體緩存最長期限將是最小值,即 1 小時。authenticatedUser 取決於登錄 cookie,因此它需要一個 private 提示,它會覆蓋其他欄位的 public,因此生成的 HTTP 頭將是 cache-control:max-age=3600,private

對 Keystone 列表和欄位添加了緩存提示支持。以下是一個簡單例子,在文檔的待辦列表演示中給一個欄位添加緩存提示:

const keystone = new Keystone({
  name: 'Keystone To-Do List',
  adapter: new MongooseAdapter(),
});

keystone.createList('Todo', {
  schemaDoc: 'A list of things which need to be done',
  fields: {
    name: {
      type: Text,
      schemaDoc: 'This is the thing you need to do',
      isRequired: true,
      cacheHint: {
        scope: 'PUBLIC',
        maxAge: 3600,
      },
    },
  },
});

另一個問題:CORS

令人沮喪的是, 跨域資源共享 Cross-Origin Resource Sharing (CORS)規則會與基於 API 網站中的緩存產生衝突。

在深入問題細節之前,讓我們跳到最簡單的解決方案:將主站點和 API 放在一個域名上。如果你的站點和 API 位於同一個域名上,就不必擔心 CORS 規則(但你可能需要考慮 限制 cookie)。如果你的 API 專門用於網站,這是最簡單的解決方案,你可以愉快地跳過這一節。

在 Vocal V1 中,網站(Next.js)和平台(Keystone GraphQL)應用程序處於不同域(vocal.mediaapi.vocal.media)。為了保護用戶免受惡意網站的侵害,現代瀏覽器不會隨便讓一個網站與另一個網站進行交互。因此,在允許 vocal.mediaapi.vocal.media 發出請求之前,瀏覽器會對 api.vocal.media 進行「預檢」。這是一個使用 OPTIONS 方法的 HTTP 請求,主要是詢問跨域資源共享是否允許。預檢通過後,瀏覽器會發出最初的正常請求。

令人沮喪的是,預檢是針對每個 URL 的。瀏覽器為每個 URL 發出一個新的 OPTIONS 請求,伺服器每次都會響應。伺服器沒法說 vocal.media 是所有 api.vocal.media 請求的可信來源 。當所有內容都是對一個 API 端點的 POST 請求時,這個問題並不嚴重,但是在為每個查詢提供 GET 式 URL 後,每個查詢都因預檢而延遲。更令人沮喪的是,HTTP 規範說 OPTIONS 請求不能被緩存,所以你會發現你所有的 GraphQL 數據都被完美地緩存在用戶身旁的 CDN 中,但瀏覽器仍然必須向源伺服器發出所有的預檢請求。

如果你不能只使用一個共享的域,有幾種解決方案。

如果你的 API 足夠簡單,你或許可以利用 CORS 規則的例外

某些緩存伺服器可以配置為忽略 HTTP 規範,任何情況都會緩存 OPTIONS 請求。例如,基於 Varnish 的緩存和 AWS CloudFrone。這不如完全避免預檢那麼有效,但比默認的要好。

另一個很魔改的選項是 JSONP。當心:如果做錯了,那麼可能會創建安全漏洞。

讓 Vocal 更好地利用緩存

讓 HTTP 緩存在底層工作之後,我需要讓應用程序更好地利用它。

HTTP 緩存的一個限制是它在響應級別上要麼是全有要麼是全無的。大多數響應都可以緩存,但如果一個位元組不能緩存,那整個頁面就無法緩存。作為一個博客平台,大多數 Vocal 數據都是可緩存的,但在舊網站上,由於右上角的菜單欄,幾乎沒有頁面可以緩存。對於匿名用戶,菜單欄將顯示邀請用戶登錄或創建賬號的鏈接。對於已登錄用戶,它會變成用戶頭像和用戶個人資料菜單,因為頁面會根據用戶登錄狀態而變化,所以無法在 CDN 中緩存任何頁面。

Vocal 的一個典型頁面。該頁面的大部分內容都是高度可緩存的內容,但在舊網站中,由於右上角有一個小菜單,實際上沒有一個內容是可緩存的。

這些頁面是由 React 組件的伺服器端渲染(SSR)的。解決方法是將所有依賴於登錄 cookie 的 React 組件,強制它們 只在客戶端進行延遲呈現。現在,伺服器會返回完全通用的頁面,其中包含用於個性化組件(如登錄菜單欄)的佔位符。當頁面在瀏覽器中載入時,這些佔位符將通過調用 GraphQL API 在客戶端填充。通用頁面可以安全地緩存到 CDN 中。

這一技巧不僅提高了緩存命中率,還幫助改善了人們感知的頁面載入時間。空白屏幕和旋轉動畫讓我們不耐煩,但一旦第一個內容出現,它會分散我們幾百毫秒的注意力。如果人們在社交媒體上點擊一個 Vocal 帖子的鏈接,主要內容就會立即從 CDN 中出現,很少有人會注意到,有些組件直到幾百毫秒後才會完全出現。

順便說一下,另一個讓用戶更快地看到第一個內容的技巧是 流式渲染,而不是等待整個頁面渲染完成後再發送。不幸的是,Node.js 還不支持這個功能

拆分響應來提高可緩存性也適用於 GraphQL。通過一個請求查詢多個數據片段的能力通常是 GraphQL 的一個優勢,但如果響應的不同部分具有差別很大的緩存,那麼最好將它們分開。舉個簡單的例子,Vocal 的分頁組件需要知道當前頁面的頁數和內容。最初,組件在一個查詢中同時獲取兩個頁面,但由於頁面的總數是所有頁面的一個常量,所有我將其設置為一個單獨的查詢,以便緩存它。

緩存的好處

緩存的明顯好處是它減輕了 Vocal 後端伺服器的負載。這很好。但是依賴緩存來獲得容量是危險的,你仍然需要一個備份計劃,以便當有一天你不可避免地放棄緩存。

提高頁面響應速度是使用緩存是一個更好的理由。

其他一些好處可能不那麼明顯。峰值流量往往是高度本地化的。如果一個有很多社交媒體粉絲的人分享了一個頁面的鏈接,那麼 Vocal 的流量就會大幅上升,但主要是指向分享的那個頁面及其元素。這就是為什麼緩存擅長吸收最糟糕的流量峰值,它使後端流量模式相對更平滑,更容易被自動伸縮處理。

另一個好處是 優雅的退化 graceful degradation 。即使後端由於某些原因出現了嚴重的問題,站點最受歡迎的部分仍然可以通過 CDN 緩存來提供服務。

其他的性能調整

正如我常說的,可擴展的秘訣不是讓事情變得更複雜。它只是讓事情變得不比需要的更複雜,然後徹底解決所有阻礙擴展的東西。擴展 Vocal 的規模涉及到許多小事,在這篇文章中無法一一說明。

一個經驗:對於分散式系統中難以調試的問題,最困難的部分通常是獲取正確的數據,從而了解發生的原因。我能想到很多時候,我被困住了,只能靠猜測來「即興發揮」,而不是想辦法找到正確的數據。有時這行得通,但對複雜的問題卻不行。

一個相關技巧是,你可以通過獲取系統中每個組件的實時數據(甚至只是 tail -F 的日誌),在不同的窗口中顯示,然後在另一個窗口中單擊網站來了解很多信息。比如:「為什麼切換這個複選框會在後台產生這麼多的 DB 查詢?」

這裡有個修復的例子。有些頁面需要幾秒鐘以上的時間來呈現,但這個情況只會在部署環境中使用 SSR 時會出現。監控儀錶盤沒有顯示任何 CPU 使用量峰值,應用程序也沒有使用磁碟,所以這表明應用程序可能正在等待網路請求,可能是對後端的請求。在開發環境中,我可以使用 sysstat 工具來記錄 CPU、RAM、磁碟使用情況,以及 Postgres 語句日誌和正常的應用日誌來觀察應用程序是如何工作的。Node.js 支持探測跟蹤 HTTP 請求,比如使用 bpftrace,但處於某些無聊的原因,它們不能在開發環境中工作,所以我在源代碼中找到了探針,並創建了一個帶有請求日誌的 Node.js 版本。我使用 tcpdump 記錄網路數據,這讓我找到了問題所在:對於網站發出的每一個 API 請求,都要創建一個新的網路連接到 「Platform」。(如果這都不起作用,我想我會在應用程序中添加請求跟蹤功能。)

網路連接在本地機器上速度很快,但在現實網路上卻不可忽略。建立加密連接(比在生產環境中)需要更長時間。如果你向一個伺服器(比如一個 API)發出大量請求,保持連接打開並重用它是很重要的。瀏覽器會自動這麼做,但 Node.js 默認不會,因為它不知道你是否發出了很多請求,所以這個問題只出現在 SSR 上。與許多漫長的調試過程一樣,修復卻非常簡單:只需將 SSR 配置為 保持連接存活,這樣會使頁面的呈現時間大幅下降。

如果你想了解更多這方面的知識,我強烈建議你閱讀《高性能瀏覽器網路》這本書(可免費在線閱讀),並跟進 Brendan Gregg 發表的指南

你的站點呢?

實際上,我們還可以做很多事情來提升 Vocal 的速度,但我們沒有全做。這是因為在初創公司和在大公司身為一個固定員工做 SRE 工作還是有很大區別的。我們的目標、預算和發布日期都很緊張,但最終我們的網站得到了很大改善,給了用戶他們想要的東西。

同樣的,你的站點有它自己的目標,並且可能與 Vocal 有很大的不同。然而,我希望這篇文章和它的鏈接至少能給你一些有用的思路,為用戶創造更好的東西。

via: https://theartofmachinery.com/2020/06/29/scaling_a_graphql_site.html

作者:Simon Arneaud 選題:lujun9972 譯者:MjSeven 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國