Twitter重構了廣告平臺

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

這就是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只有兩種廣告產品——推廣推文和推廣賬戶時,這個單體平臺運行得很好。然而,當我們擴大業務時,單體模式帶來的挑戰便多於解決方案了。

新增一個廣告產品

在舊的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,每個產品一個,或者一組類似的產品一個。

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

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

經驗總結

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

我們構建了橫向平臺組件

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

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

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

好處

便於添加新產品

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

隨着廣告業務的持續增長,添加新產品將會很容易,只要在需要的時候引入這些橫向平臺服務就可以了。

提升速度

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

風險與利弊

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

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

我們評估了這些風險,並且確定,新架構的好處大於這些風險造成的影響。整體開發速度的提高和更可預測的特性改進交付,對於我們爲自己設定的雄心勃勃的業務目標至關重要。新架構提供了一個模塊化系統,讓我們可以更快的試驗,並降低了耦合度。

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

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

遷移

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

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

爲了確保新舊服務在功能上的一致性,我們開發了一個自定義的正確性評估框架。它分別針對舊AdServer和新AdServer重放了請求,以便在可接受的閾值內比較兩個系統的指標。我們在離線測試中使用了這種方法,藉此我們可以瞭解新系統的性能。它幫助我們及早發現問題,防止錯誤進入生產環境。

在將代碼發送到生產環境後,我們使用了一個試驗框架,讓我們可以洞察生產環境中的總體收益指標。許多預測和競價相關的度量標準需要一個更長的反饋循環來消除噪音和評估變更的真實影響。因此,對於遷移的真正的端到端驗證,我們依賴這個框架來保證收入指標的正常。

總結

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

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

查看英文原文: Building Twitter’s ad platform architecture for the future

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章