天天看點

Twitter 重構了廣告平台

作者 | TwitterEng‎

譯者 | 平川

策劃 | Tina

軟體系統的一大優點是它們具有極強的适應性。然而,在複雜軟體系統的演進過程中,這種可塑性會阻礙而不是促進其發展。在某種程度上,軟體将進入一個不再服務于其目的——為人們提供幫助——的階段。

這就是 2019 年初 Twitter AdServer 的情況。經過 10 年的疊代開發之後,系統的效率已經太低,無法與組織的發展保持同步。剛開始的時候,我們是一個非常小的工程師團隊,隻提供單一類型的廣告格式( 推廣推文),創造了大約 2800 萬美元的收入。如今,Twitter 的收入組織包括 10 倍以上的工程師和約 30 億美元的收入,支援多種廣告格式——品牌、視訊、卡片。

新産品釋出慢,團隊之間緊密依賴,管理成本很高,這些都增加了組織的複雜性。為了進一步擴大規模,我們就得進行根本性的改革。

我們是如何投放廣告的?

AdServer 漏鬥(funnel)主要由 Admixer 和 Adshard 組成。Admixer 是上遊用戶端和 AdServer 管道之間的接口。當 Admixer 收到一個廣告請求時,在将請求分發給 Adshard 之前,它會将額外的使用者資訊補充到請求中。Adshard 在分片架構下運作,每個 Adshard 負責一個廣告子集,通過 AdServer 漏鬥的 3 個主要階段運作請求:

  • 候選項選擇 :為使用者選擇一組有效的活動子項(即一組具有相同目标市場選擇标準的廣告;有關詳細資訊,請參考活動子項定義)。在這一階段,(1)我們應用了所有标準的活動目标選擇條件,最終為使用者提供符合條件的廣告(例如地理位置、年齡、興趣等);(2)剔除使用者可能不喜歡的不相關的廣告;(3)確定我們隻提供有效的廣告(例如,隻提供沒有結束的活動)。
  • 産品邏輯 :此階段根據一些業務規則将來自候選項選擇階段的每個活動子項擴充為一組創意,并添加額外的産品特性豐富這些創意。(創意是實際展示給使用者的廣告,例如一條推廣推文——請參考創意定義了解更多細節)。
  • 候選項排名 :完成上述階段後,對廣告進行排名。每個廣告都會得到一個分數(表示使用者浏覽該廣告的可能性),并根據這個分數對廣告進行排名。我們使用一些實時訓練的機器學習模型和廣告客戶資料來計算我們在競價管道中使用的分數。

在這個漏鬥中,不同的元件還附加了與廣告請求和廣告候選相關聯的中繼資料,并會将這些中繼資料寫入 AdMixer 中我們的底層鍵值存儲中。稍後,在回報循環中,分析管道将使用這些資料,用于賬單、欺詐檢測和其他分析。

過去,我們是通過盡可能減少網絡跳數來優化系統,以最小化延遲和操作開銷。這導緻單個服務(即 Adshard)完成了大部分繁重的工作,進而形成了一個單體模型。

熵 增

當 Twitter 隻有兩種廣告産品——推廣推文和推廣賬戶時,這個單體平台運作得很好。然而,當我們擴大業務時,單體模式帶來的挑戰便多于解決方案了。

新增一個廣告産品

Twitter 重構了廣告平台

在舊的 Adserver 中,由于遺留代碼的挑戰和複雜性,重用現有模式和實踐就成了常态。上圖是一個在舊的 AdServer 上新增一個廣告産品(如推廣趨勢)的例子。該廣告産品具有以下特點:

  1. 應該總是根據條件 Geo == Country 選取;
  2. 應該不需要競價,進而可以跳過排名階段。

通常,新增一個廣告産品需要做一些零零碎碎的工作。考慮到現有架構的性質和其他遺留代碼的限制,跳過排名階段不是可行的選項,于是我們采用了一種不合正常的變通方法,在排名管道裡向代碼中添加基于産品的條件邏輯 if ( product_type == ‘PROMOTED_TREND’ ) {…} else {…}這種基于産品的邏輯也存在于選擇管道中,導緻了這些階段緊密耦合,增加了日益增多的意大利面式代碼的複雜性。

開發速度

下面是所有基于大量的遺留代碼進行開發的團隊都面臨的一些挑戰。

  • 過度膨脹的資料結構 :請求和響應對象的大小随着業務邏輯的增加而快速增長。由于請求 / 響應對象在這 3 個階段中共享,是以不變性保證是一項挑戰。在候選排名階段添加一個新特性,需要了解該特性所需的字段在上遊(選擇和創意階段)和下遊(Admixer)是在何處如何設定的。要想修改的話,就幾乎需要了解整個服務管道。這是一個令人畏縮的過程,尤其是對新工程師來說。
  • 資料通路挑戰 :從曆史上看,Admixer 一直是負責擷取使用者相關資料的服務,這主要是為了延遲和資源優化。(由于采用分片架構,在 Adshard 中擷取相同的使用者資料需要 25x RPC)。是以,要在 Adshard 中使用一個新屬性,我們需要在 Admixer 中添加相應的使用者資料擷取器,并将其發送給 Adshard。這個過程非常耗時,并且,取決于使用者屬性的類型,可能會對 AdServer 的性能産生影響。這個過程也使得解耦平台與産品變得非常具有挑戰性。
  • 技術債務 :複雜的遺留代碼增加了技術債務。棄用舊字段以及清理未使用代碼的風險越來越大。這通常會導緻功能的意外更改,引入 bug 并拉低整體生産力。

解決方案:我們如何設計這些服務

這些長期存在的工程問題以及開發人員的生産力損失,使得我們需要改變系統設計的範式。我們在架構中缺乏明确的關注點分離,并且不同的産品領域之間高度耦合。

在軟體行業中,這些問題相當常見,而将單體分解成微服務是解決這些問題的流行方法。然而,它本身也是有利有弊,如果倉促設計,反而會導緻生産率降低。讓我們通過一個例子看下分解服務時可能采用的一種方法。

服務分解思考練習:每個産品一個 AdServer

由于單體 AdServer 對每個産品團隊而言都是一個瓶頸,而不同的産品可能有不同的架構需求,是以我們可以選擇将單個 AdServer 分解為 N 個不同的 AdServer,每個産品一個,或者一組類似的産品一個。

Twitter 重構了廣告平台

在上面的架構中,我們有三個不同的 AdServer,分别用于 Video Ad Product、Takeover Ad Product 和 Performance Ad Product。它們由各自的産品工程團隊負責,每個團隊都有自己的代碼庫和部署管道。這似乎提供了自主性,并有助于分離關注點,解耦不同的産品領域,然而,實際上,這樣的分離可能會使事情變得更糟。

現在,每個産品工程團隊都必須增加人手來維護整個 AdServer。每個團隊都必須維護和運作自己的候選生成和候選排名管道,即使他們很少修改它們(這些通常是由機器學習領域專家負責修改)。對于這些領域,情況變得更糟。現在,要釋出一個用于廣告預測的新特性,我們需要修改三個不同服務的代碼,而不是一個!最後,很難確定來自所有 AdServer 的分析資料和日志能夠融合到一起,以確定下遊系統的正常運作(分析是跨産品的橫切關注點)。

經驗總結

我們認識到,僅僅分解是不夠的。我們在上面為每個産品建構的 AdServer 架構既缺少内聚性)(每個 AdServer 仍然做了太多的事情),也缺少可重用性(例如,在所有三個服務中都運作着的廣告候選排名)。我們突然認識到,如果我們要為産品工程團隊提供自主性,就必須用可以跨産品重用的橫向平台元件來為他們提供支援!為橫切關注點提供即插即用的服務可以為工程團隊創造乘數效應。

我們建構了橫向平台元件

是以,我們确定了可以被大多數廣告産品直接使用的“通用廣告技術功能”,包括:

  • 候選項選擇 :給定使用者屬性,确定可以針對使用者需求展開競逐的廣告候選項。
  • 候選項排名 :給定使用者屬性和廣告候選項,根據與使用者的相關性給廣告候選項打分。
  • 回調和分析 :定義契約,标準化所有提供廣告服務的服務的分析資料集。

我們圍繞這些功能建構服務,并将自己重組為平台團隊,每個團隊擁有其中一個功能。以前架構中的産品 AdServer 現在變成了更精簡的元件,它們依賴于橫向平台元件,并在其上建構特定于産品的邏輯。

Twitter 重構了廣告平台

好 處

Twitter 重構了廣告平台

便于添加新産品

讓我們重新審視上面提到的與聚光燈廣告有關的問題,以及新架構如何處理這個問題。通過建構不同的廣告候選項選擇服務和廣告候選項排名服務,我們可以更好地将關注點分離開來。它打破了廣告産品必須采用 AdServer 管道的 3 階段範式這一模式。現在,聚光燈廣告有了靈活性,可以隻與選擇服務內建,使得這些廣告可以跳過排名階段。這讓我們擺脫了為繞過推廣趨勢廣告排名而采用的笨拙方法,實作了一個更幹淨、更健壯的代碼庫。

随着廣告業務的持續增長,添加新産品将會很容易,隻要在需要的時候引入這些橫向平台服務就可以了。

提升速度

通過定義良好的 API,我們可以在團隊之間實作職責分離。修改候選項排名管道不需要了解選擇或創意階段。這是一種雙赢的局面,每個團隊隻需要了解和維護他們自己的代碼,這讓他們可以更快地采取行動。這也使得故障更加容易診斷,因為我們可以隔離服務中的問題并獨立地測試它們。

風險與利弊

在 Twitter,這種廣告模式的轉變必然會伴随着風險和權衡。我們想列出其中一些,以提醒讀者,在決定對現有系統進行大規模重構之前,必須識别和承認存在的弊端。

  • 增加硬體成本 :從一個服務建立許多不同的服務無疑意味着增加運作這些系統的計算成本。為了確定增長在可接受的範圍内,我們為自己設定了一個具體目标,将廣告服務系統的營運成本控制在收入的 5% 以内。這有助于我們在需要的時候優先考慮效率,讓我們更容易做出設計決策。就計算資源而言,新架構的開銷大約是前一個架構的兩倍,但這在我們可以接受的限度之内。
  • 增加了産品開發團隊的營運成本 :擁有多個新服務意味着維護和營運這些服務的工程成本,其中一些新增的負擔落在了産品開發團隊身上(而不是像之前那樣更多地落在平台團隊身上)。這意味着除了要加速開發新特性外,産品開發團隊還需要适當地成長以支援他們擁有的新系統。
  • 新特性開發的暫時放緩 :這項工作需要花費超過 40 名工程師以及工程和産品經理 1.5 年的時間。我們估計,在此期間,新特性的開發速度會降低大約 15%(主要是在廣告服務方面)。為了支援這個項目,組織負責人會願意做出這樣的權衡。
  • 競價排名的複雜性增加 :這是對新架構的技術考量——由于每個産品負責人都服務于自己的請求,我們部分失去了在更低粒度上對廣告排名和競價做出全局最優決策的能力。通過将這種邏輯轉移到更高粒度的集中式平台服務上,可以在某種程度上彌補這一點。

我們評估了這些風險,并且确定,新架構的好處大于這些風險造成的影響。整體開發速度的提高和更可預測的特性改進傳遞,對于我們為自己設定的雄心勃勃的業務目标至關重要。新架構提供了一個子產品化系統,讓我們可以更快的試驗,并降低了耦合度。

我們已經開始看到這種決策的好處了:

  • 對于大多數規劃好的項目,沒有一個團隊會成為瓶頸,而在規劃好的廣告服務項目中,90% 以上的都可以執行。
  • 試驗速度快了許多——在廣告服務空間進行的線上排名試驗現在快了 50%。

遷 移

多個團隊每天都推送新代碼這樣一個部署節奏,再加上數十萬 QPS 的龐大規模,使得 AdServer 的分解非常具有挑戰性。

在開始遷移時,我們采用了記憶體内 API 優先的方法,對代碼進行邏輯分離。另外,這還使我們能夠運作一些初始的系統性能分析,保證與舊系統相比,CPU 和記憶體占用的增量是可接受的。這奠定了橫向平台服務的基礎,這些基本服務源自重構代碼并重新安排記憶體版本的打包結構。

為了確定新舊服務在功能上的一緻性,我們開發了一個自定義的正确性評估架構。它分别針對舊 AdServer 和新 AdServer 重放了請求,以便在可接受的門檻值内比較兩個系統的名額。我們在離線測試中使用了這種方法,借此我們可以了解新系統的性能。它幫助我們及早發現問題,防止錯誤進入生産環境。

在将代碼發送到生産環境後,我們使用了一個試驗架構,讓我們可以洞察生産環境中的總體收益名額。許多預測和競價相關的度量标準需要一個更長的回報循環來消除噪音和評估變更的真實影響。是以,對于遷移的真正的端到端驗證,我們依賴這個架構來保證收入名額的正常。

總 結

分解 AdServer 改善了我們系統的狀态,強化了 Twitter 廣告業務的基礎,讓我們可以把時間和資源集中在解決真正的工程問題上,而不是與遺留基礎設施的問題作鬥争。随着廣告業務和技術的發展,更多的挑戰将會到來,但我們很高興能夠建立可以提高系統效率的解決方案。

如果你對解決這些挑戰感興趣,可以考慮加入這個團隊。

參考閱讀:

https://blog.twitter.com/engineering/en_us/topics/infrastructure/2020/building-twitters-ad-platform-architecture-for-the-future.html