Categories
程式開發

Slack技術演進模式:在正確的時間採用革命性的技術


科技界的大多數新生事物都只是一波又一波的潮流:說話和做事的模式來了又走,沒有留下永恆的印記。微內核,IA-64 架構,對象請求代理,20 世紀 90 年代的神經網絡,這些東西都已經不復存在,也不會再回來了。時間已經證明了哪些東西是曇花一現,為了說明問題,我們必須追溯到很久以前。

今天的我們很難想像,在鼎盛時期,那些技術有多麼的受歡迎。它們有很多極具魅力、真誠且聰明的倡導者,倡導者們得到了看似合理的基本原理論證的支持,這些論證證明了他們選擇的技術必然會取得勝利。這些潮流催生了運動、宣言、會議和公司。但請不要把這些潮流與蓄意欺詐混為一談,後者要少見得多。推動這些技術潮流的動機是發自內心的真誠,不管它們以何種方式出場,二者產生的結果是完全不同的。

另一方面,一些重要的新技術是革命性的:它們強大而持久的變化為技術採用者帶來了長期的優勢。面向對象編程、硬件虛擬化、萬維網、公有云、CI/CD 和20 世紀90 年代的神經網絡(深度學習的重生)現在成了計算機世界永久的組成部分,而它們曾經“混跡”在潮流之中,難以區分。我們被技術浪潮所包圍,在它們展露頭角之前,我們並不知道如何用它們來獲得成功。

與其他科技公司一樣,Slack 希望在正確的時間採用革命性的技術,避免把過多的精力浪費在追逐潮流上。 Slack 採取了什麼樣的戰略來確保做到這一點?這篇文章介紹了 Slack 用來解決這個問題的方法。

區分潮流和革命

我們不能依靠個別領導者的直覺來區分哪個才是贏家。相反,我們要積極地探索新事物,雖然我們也知道這些嘗試大部分都不會有任何回報。但為了讓我們的投入偏向於有用的新事物,避免踩太多潮流的坑,我們應該在早期就要無情地扼殺那些沒有價值的實驗。我們希望每件事都去嘗試一下,這意味著我們仍然要與潮流為伍。我們希望最終能夠乘著潮流到達彼岸,我們與潮流打交道的經驗不斷為我們提供積極的回報。

技術採用曲線

我們創建了一個描述性的新技術採用曲線模型。

Slack技術演進模式:在正確的時間採用革命性的技術 1

這是一條典型的 S 曲線,描述了技術的採用隨時間變化的情況。 S 形反映了技術採用的變化。一開始,當只有幾個實驗者在做實驗時,我們別無選擇,只能慢慢地接受。稍後,當情況變得清晰的時候,就會有更多的人參與進來,我們會在中間陡峭向上傾斜的部分快速地將新技術應用到生產中。當大多數有成果的試驗圓滿完成,就會剩下寥寥無幾難啃的“硬骨頭”。於是,在周期接近結束時,採用速度又慢了下來。

我們把這三個階段畫出來:

Slack技術演進模式:在正確的時間採用革命性的技術 2

我們並不是第一波發現技術採用遵循這種 S 型曲線的人。 Everett Rogers 在 1962 年的《創新擴散理論》(diffusion of innovation)一書中就已提出了這一模型。不過,Rogers 並沒有提到 Erlang 或者 MongoDB,因為他是一位農村社會學家,他觀察的是農業技術採用的模式。但事實證明,計算機領域與人類活動的其他領域並沒有太大的不同。

為了讓這個抽象的概念更具像一些,我們將例舉一些已經在 Slack 經歷了探索、擴張和遷移階段的技術。

React

從 2015 年發布第一個穩定版本以來,React 席捲了前端開發領域。虛擬 DOM 和單向數據綁定讓它成為開發 Slack 桌面用戶界面的一項引人注目的技術。

  • 2016 年,對於 Slack 的前端開發來說,React 還只是一項陌生的技術。在第一階段,對 React 感興趣的工程師開始嘗試使用它,將它用在試驗項目中。當他們確信 React 可以為 Slack 帶來重要的價值時,他們開發了一個有說服力的演示項目,使用 React 重建了 Slack 的表情選擇器。這個使用 React 開發的 Slack UI(之前有延遲感)比之前表現得更好。原型比理論驗證或白板草圖更容易讓開發者接受。
  • 當我們的團隊意識到 React 將會對我們的代碼庫產生重大影響時,只是做一些零碎的小手術是無法讓 React 的性能優勢得以體現的。但進行大規模的重寫也是不可能的,因為風險與回報比不允許我們這麼做。當 React 進入第二階段,一個真正的遷移項目開始啟動了。項目制定了一個計劃,並配備了大量的開發人員。隨著項目的進行,有很多團隊也選擇了新的視圖樣式。但在這個中間階段,很多舊的視圖仍然存在,並需要維護。
  • 在第三階段,我們將客戶端代碼庫中的遺留視圖清理乾淨。我們終於在 2019 年 7 月發布了一個只使用 React 的桌面版 Slack。

Hack

在服務器端,我們從 2016 年開始從 PHP 遷移到 Hack。這次遷移的一個關鍵部分是在我們的 PHP 代碼中逐步引入類型:

  • 在 2017 年,我們進入了第一階段,一些類型愛好者開始在代碼庫中使用簡單的類型。
  • 有些人在代碼進入生產環境之前發現這些類型可以捕獲 bug,於是也開始使用類型。這無意中就讓 Hack 的類型系統進入了第二階段(其他用戶也受到了影響)。不過,新的類型註釋也帶來了一些問題,於是我們展開了一場有關標準靜態類型與動態類型的討論。通過討論和積累經驗,我們達成了一個大致的共識——加大類型使用規模利大於弊,並且大多數團隊都選擇使用類型。在第二階段,我們做出了更大的努力進行代碼遷移,讓新加入的代碼可以使用靜態類型。
  • 困難的部分留給了第三階段。對 Slack 自身的內部對像變量進行合理化調整,並對一些複雜的核心模塊進行轉換,這些都是非常耗時的。

Vitess

Vitess 是一個用來進行 MySQL 水平擴展的集群系統,在進行數據分片策略演化過程中,我們選擇了它。

  • 第一階段開始時,我們對 Vitess 的能力進行了嚴格的評審。我們花了很多時間在手動管理自有分片解決方案上,而 Vitess 的自動化能力解決了我們的大部分痛點。 Vitess 團隊最終確信這項技術是值得採用的。
  • 將一些低風險的工作負載(如 RSS 提要)遷移到 Vitess 的工作始於第二階段。在第二階段早期不太需要 Vitess 團隊之外的人參與,但因為是一個新的數據存儲系統,所以仍然需要運維支持。隨著越來越多的表被遷移到 Vitess,我們逐漸降低了風險,讓 Vitess 滿足了我們的應用場景需求。我們開發了用於回填的工具,制定了一些在遷移過程中會用到的術語(例如把重複數據叫作“暗讀”),總結了可能會出現的各種問題。
  • 我們已經遷移了數百個表,總計超過了查詢工作負載的 50%,但在第三階段仍然要處理一些“難表”和“長尾表”。一些關鍵表存在復雜的依賴關係和查詢模式,它們比在第二階段遷移的表更難處理。另外,我們有一些“長尾表”,進行逐張手動遷移很不值得,因此我們正在開發工具進行批量遷移。

LibSlack

與以上那些經歷了各個採用階段的技術相比,我們的跨平台 C++ 客戶端庫並沒有走到第三階段,並且最終停止了。

  • 在第一階段,LibSlack 工程師驗證了業務邏輯和數據緩存可共享客戶端庫的概念,並深入開展了編譯和交付跨平台庫的相關工作。
  • 然而,該項目在第二階段並沒有取得進展。庫與我們的桌面客戶端之間的技術和戰略不兼容性變得很明顯。事實證明,在我們的 iOS 和 Android 客戶端中使用 LibSlack 庫重新實現現有的邏輯和緩存會非常麻煩。不過,由於 Windows 手機的停產,Slack 需要維護的客戶代碼庫減少了一份。

到最後我們並沒有進行全面的遷移。我們將從 LibSlack 中學到的東西以各種方式應用到移動和桌面客戶端開發工作中。代碼工件並沒有被長期採用,但這個項目讓我們學會了應該如何構建客戶端以及如何組織我們的工程團隊。

曲線分析

需要注意的是,這個模型是描述性的,而不是說明性的。我們並不是想要強迫人們接受這個 S 型曲線,儘管我們想要這樣。實際上,這是一個自然的過程。早期的探索階段不可能像中期階段那樣迅速地進行,最後的階段也不可能像中期階段那樣迅速地進行全面採用。這三個階段並不是任何里程碑、過程、工具或 Slack 工程人員作用的結果。它們是技術變革的一部分,不管我們有沒有註意到它們,它們都會存在。

現在,我們已經註意到了它們,我們可以利用它們,讓我們的努力更加卓有成效。每個階段的戰術和戰略是不一樣的。

第一階段:探索

第一階段是無門檻進入。當工程師第一次開始調研他們感興趣的技術時,不需要任何許可授予過程或儀式。在 Slack,這樣的事情一天可能會發生幾十次:有人發現了新技術,或者發明了新東西,然後就開始研究它們。他們可能是因為讀過關於 Elixir、Cassandra、WebAssembly 或 TCR 的博文,或者下載了一些軟件,編譯打包,四處看看,瀏覽一些介紹性的材料,還可能嘗試把它們應用到日常工作中。

大多數的探索工作都在這個階段進行。在這個階段,為了避免在不必要的事情上花費太多精力,我們要懂得放棄掉一些東西。不過確實還是有一些東西被用到了實際的工作流程和代碼庫中。有時,工程師可以直接應用某些解決方案,因為它們解決了一些局部問題。有時候會出現更加令人興奮的結果:某些解決方案也解決了其他團隊所面臨的問題。這個階段的工程師相信他們知道一些其他人不知道的東西:一些事情可以用更好的方式來做。一旦他們的工作開始影響到其他人,我們就進入了第二階段。

第二階段:擴張

進入第二階段,工程師就有點可憐了!因為他們現在正試圖改變其他工程師的行為,這將涉及溝通、說服,如果一切進行得順利的話,他們還需要做大量的技術工作。對於大多數項目來說,第二階段是最困難、最耗時、最令人沮喪的階段。在技​​術週期中,這是“產品與市場匹配”階段,很多進入該階段的項目無法成功地走完這個階段。

在 Slack,用戶團隊可以自由選擇是否要依賴你的系統,很少有例外。如果你習慣了“基礎設施驅動”的公司,那麼我們的情況可能會讓你大吃一驚。在其他公司,領導層會在第二階段產品市場適應得出結論之前就選出了贏家和輸家。他們之所以這麼做,是為了提供清晰的信息(比如未來會怎樣、我們應該採用哪個系統)和降低成本,因為在這一階段需要支持更多的做事方式。

雖然這些都是合理的目標,但 Slack 並沒有選擇這種方式。我們優先考慮的是適應潮流的能力,而不是適應的速度。因此,我們(有意地)將促進其他團隊採用新技術的主要負擔放在了小部分人身上。雖然這可能會讓這些人感到沮喪,但我們知道沒有其他更好的辦法。為了清除這一障礙,我們不得不選擇更加有效的方法。如果新技術真的像我們所希望的那麼美妙,那麼它們應該能夠幫助依賴它們的團隊順利完成工作。反過來,這種結果會促使他們採用和推廣這些新技術。

第二階段的一些工作更像是產品工作,而不是工程工作。你需要研究用戶,以便確定哪些問題是重要的。你需要按照用戶所期望的方式將你的解決方案的價值與之前的實踐聯繫起來。你需要想辦法縮小當前實踐與你正在做出的改變之間的差距,讓用戶更容易接受。

在第二階段取得的成功最終會導致一些自發式的採用,用戶可以自由地選擇是否採用新技術。當新系統成為事實上的標準,第二階段就接近尾聲了。偶然性地遇到這種採用情況是很不尋常的,因為這真的很難,而且並不是每個工程師都具備相關的技能。

第三階段:遷移

自發式的採用最終會逐步減少,最後剩下一些頑固的人,他們似乎很抵制新的做事方式。一些後台運行的系統尤其沒有動力去做出改變,因為它們的開發度不夠活躍。在某些情況下,我們到了後期才發現一些舊系統在某些方面按照舊有方式運行會更好。最後,總是會有一些頑固的用戶堅持使用老方法。

雖然我們一直在討論“技術採用曲線”,但實際上在第三階段會出現一個分岔口。即使非常成功的項目也不可能徹底採用全新的技術。例如,在 Slack,我們已經廣泛地採用 gRPC 作為內部的 API 技術。但是,我們不太可能構建一個全新的基於 gRPC 的 memcached。 memcached 的自定義協議很不錯,並得到了客戶端的良好支持。這種例外並不意味著採用 gRPC 是失敗的。

對於其他一些情況,採用多種技術的成本(工程師的認知負擔、運行舊系統的負擔)太高,所以我們有必要徹底採用新技術。對於這樣的項目,我們需要製定一個應對頑固派的計劃,不同的情況需要採用不同的策略。長時間沒有發生改動的系統可能需要使用代理,並逐步對其進行遷移。如果頑固派的存在是因為新系統缺乏必要的功能,那就需要增強新系統,或者對新系統進行封裝,模擬舊系統的功能。

對於對舊系統產生了情感依戀的情況,進行面對面的溝通通常比高風險的公開辯論有效得多。請溫柔些,如果你的新系統足夠成功,總有一天它也會成為舊系統。

技術人的期望

作為 Slack 的工程師和工程負責人,我們對彼此的期望是什麼呢?

  • 首先,我們要去做一些探索工作。外面的世界很大,我們偶爾也要抬頭看看外面發生了什麼。但沒有人可以做到探索一切,也沒有人可以做到一直在探索新事物。我們有對外部的承諾和內部的路線圖,這些事情仍然具有高優先級。不過,我們還是要分出一些能量用於探索新事物。
  • 在成為其他團隊技術產品的用戶時,我們要講道理。提供底層技術支持的團隊需要將他們的系統向前推進,有時候,這會給上層的用戶帶來成本。如果這種成本是不合理的,或者當它朝著與你的團隊需求相反的方向發展時,你需要以他們能夠理解的方式與他們溝通。
  • 有時候,我們需要避免依賴無法滿足我們需求的底層技術。在設定團隊技術發展方向時需要注意這一點。不過,這並意味著一定是他們做錯了什麼,我們需要用成熟和專業的方式來應對這種依賴分離。
  • 當我們試圖推動他人做出改變時,我們會對試圖理解和使用新系統的團隊抱持以用戶為中心的態度。他們的快樂是你成功的唯一晴雨表,包括溝通、需求收集、反饋、迭代、有目的性的培訓和技能分享。

如果有疑問,請記住:你要對技術採用的成功與否負責,而從長遠來看,這是由你的產品的用戶來決定的。

英文原文

How Big Technical Changes Happen at Slack