Reddit帝國建立在一個有瑕疵的演算法之上

周昌鴻發表於2013-12-18

Reddit的原始碼中存在一個bug。這個bug目前還存在他們的平臺產品之中,並且已經存在了多年。這個bug與應用於整個站點最重要的演算法之一有關——針對“熱點”連結受歡迎度的排序演算法。這個bug也導致了真實顯見的負面影響,並且這個問題多次報告給Reddit的技術組,但是一直沒有被修復。

 

缺陷

Reddit需要判斷當前哪篇文章比較熱門。新文章比舊文章更熱門一些,擁有更多正面投票的文章比有很少投票的文章更好,而大部分投負面票的文章則墊底。這些規則比較容易計算。針對釋出時間和投票可以確定一個確切的數值,然後與一個常量係數,就能計算出每篇文章的的受歡迎程度1

魔鬼在於細節,根據GitHub中的原始碼,目前的實現是這樣的。

seconds是根據時間變化的變數,基於Unix時間戳。這樣做合情理,時間總是累加的,所以每一個新文章釋出都較之前釋出的文章在時間上擁有更高的分數值。

投票部分計算根據公式有兩個部分,sign變數簡單地記錄總投票數是正面還是反面的。如果文章收到的正面評分比負面評分更多,sign就是1;如果負面評分更多,sign的值就是-1.其他變數,order是投票分數絕對值的取log102對數。

真正的問題是,和許多問題一樣,從兩個角色的交換。

這裡我們計算得到了最終的分數。Seconds是一個很大的正值,而order總是返回為正——由於這裡使用了的絕對值,所以,儘管如一個負數-389也會得到和order值為389一樣的結果,我們需要通過sign來調整我們的結果,這樣,負面意見的文章會被排在後面。但是這裡的程式碼使用了sign乘於時間seconds,而不是sign乘於order。

而對於正面評價的文章,則沒有影響,由於符號位為1,所以order和seconds相加,計算沒有問題。

對於負面評價文章又發生了什麼呢?sign值為-1,所以值比較大的seconds變數會變為負值,而使用一個正面評價的order值與之相加,因此會導致幾個問題。

假設兩篇文章,相距5秒釋出。每一個都收到兩個父母評價,seconds對於新文章值更大一些,但是因為sign為負值,新的文章評分反而比舊的文章低。

假定還有兩篇文章同時釋出,一篇文章收到了10個差評,另外一篇文章收到五個差評。由於seconds一樣,符號值sign都是-1,但是得到-10評價的文章order值更高。所以,-10評分的文章反而排序在得到-5評分的文章前面,儘管人們討厭它兩倍。

現在假定一篇文章在一年前釋出,另外一篇文章剛剛才釋出。去年釋出的文章有兩個正面評分,今天剛釋出的文章有兩個差評。這裡有點不同——今天釋出的文章可能得到了差評開始,不過也可能接下來收到好評。不過,在當前的實現3中,由於今天文章有兩個差評,從而導致其熱度評分反而比去年的文章更低。

 

後續

這並不是一個假設的漏洞,我好奇地去看了一些Reddit的公共庫的程式碼是否也應用在他們當前的產品中,我找到近期釋出的一些不太活躍的文章,給它差評使得它的總分是負分。就是這樣的文章,不但跌出了第一頁(這一頁當中還有幾個月前的文章),這個是熱度排序演算法的結果。我覺得這個結果比較糟糕,又移除了我的差評,但是這個文章並沒有恢復到排名之中4

事實上,通過操作查詢字串,你可以發現一個奇怪的陷阱,糟糕的文章在莫名的角落緩慢地腐化5(譯者:我猜作者的意思應該是讓人討厭的文章總數顯示在顯眼的地方)。下面的截圖來自iphone版的subReddit這些不幸的文章。

這些文章是一些傷心的、害怕、孤獨的文章。但是在顯著位置,並且按時間順序排列,就如預料的一樣。

這個缺陷為一些故意破壞這個系統的人提供了一扇門。假設有一個subReddit,/r/BirdPics,投票給一些鳥類圖片6。攻擊者不喜歡海雀,並且希望把所有的海雀圖片都驅逐出首頁,這個攻擊者可以對每一張海雀圖片都給差評,不過這也可能被那些喜歡海雀的人給好評而消弱。平均來說,任何時刻檢視首頁的人的大概是350人,為了阻止這種惡意差評需要許多的評價較量。

不過,攻擊者可以小心留意那些最近釋出的圖片,這個時候一旦有海雀圖片被髮布,攻擊者立即給他一個差評。如果攻擊者先投票,那麼這篇文章總分就是一個差評並且會被逐出首頁,在首頁上再也看不到這篇文章。攻擊者唯一需要擔心的是有人會去檢視最新排序文章列表,這裡投票並不影響排序。我們假定350人中只有10人會去檢視最新文章列表,因此攻擊者只需用使用10個虛假賬號來應對這些人,而不是需要近300個賬號來對付首頁上的所有人。就這樣,攻擊者可以清除subreddit上的所有海雀圖片,這些海雀就再也沒人注意。

 

補救措施

我不是第一個發現這個問題的人,Jonathan Rochkind在他的發表的一篇言辭詳盡的話題中談及了該問題。不過一位Reddit的開發者告訴他,他弄錯了,當前的演算法沒有問題。

我在開發者社群釋出了一篇請求修復該bug的文章,也被一個開發者告知,就是這樣設計的。我不懂,也沒有得到一個令人滿意的解釋,在何種意義上這種荒謬的行為將是“就是這樣設計”的。但很明顯,Reddit對解決這個問題不感興趣,這一行為可能會持續許多年。

 

結局

程式設計師往往圍繞著規則的一致性尋找公平正義。這也是為什麼我們中的許多人發現世俗領域關係或政治如此棘手的,以及為什麼我們中的許多人第一時間被電腦科學吸引。在計算機世界,一切都是嚴格確定的。如果發生了一些未定義的事情,那也只是因為我們對系統的理解不完整7。對於正確性,capital-R就是一個可理解的系統並且精確執行。

我們擁有這樣的世界觀,而一個明顯的bug被散播看起來是不公正的。我和其他發現這個問題的開發者看起來對這個演算法的理解比那些Reddit回應我們的員工更深入一些。對於這個沒有被修補令人驚訝且違反直覺的行為,我們肯定是對的。我們是對的,而Reddit搞錯了。因為Reddit是一個流行站點,並且擁有大量的使用者,以及大量的現金。這些都建立在一個明顯錯誤的元件之上。

這裡的道德是什麼?也許就是一個沒有有效測試使得這個系統不能被理解,或者最終變為一個“可以工作,但是停止問問題”的系統。也許追求完美是好的一面的敵人,更糟反而更好8,這樣的分歧使我遠離喋喋不休的爭吵9。也許好產品和好的技術實現存在一定的距離,而硬性的資料總是能保證正面的體驗。

也許這沒有道德,是Reddit搞砸了。這原本會傷及它自身,但是沒有,也可能不會。他們錯了,但是沒有導致實際的錯誤,因為沒有一個大寫W Wrong。我們都應當編寫有責任的程式碼,這裡沒有一個數學上帝可以有一天評判Reddit在做演算法時的罪行。世界就是一個有缺陷的世界,曾經是,也一直都會如此。

 

腳註:

  1. 這個想法簡單而有效,你可以根據這個演算法構建不同的站點,但是使用不同的常量。如果你想要一個站點展現一些很舊的文章,可以將時間變數的權重調低。需要一個twitter站點,將vote變數的權重設定為0.
  2. 這個對數計算使得對Reddit的投票在權重上相差懸殊。1個投票和11個投票的差距比10001個投票和10011個投票的差距要大得多。
  3. 這個計算使得時間seconds的值很大,相對於order始終擁有更大的值。在Reddit的實現中是這樣。
  4. 通過測試,我無法確定是投票統計的波動導致評分變化究竟發生了什麼。我現在知道了,這個是一個投票混淆演算法,有點類似反垃圾郵件功能。
  5. 我不能提供關於這個缺陷的持久化連結,這個索引好像一天後就會失效,不過這也容易找到。首先,查詢最近負面評分的文章,記錄下它的ID值,這個可以從URL中找到,比如:http://www.reddit.com/r/birdpics/comments/1s33tt/fear_the_shrike/,我們可以得到ID值1s33tt。然後,插入下面的連結,取代必要的部分:http://www.reddit.com/r/SUBREDDIT/?count=9999&after=t3_ID,。我們的URL會變成http://www.reddit.com/r/birdpics/?count=9999&after=t3_1s33tt。這裡ID值被t3_字首展開,然後你可以隨意改變count值,這個值可以手動調節。
  6. 這個當然存在,見:連結
  7. 我懷疑,這也是為什麼海森伯格測不準是電腦科學家最害怕也最討厭的問題了。參見:釋放Zalgo
  8. “糟糕反而更好”來自於Richard Gabriel關於研究C的興起和Lisp的衰微的開創性文章。這篇文章和後續的一些續篇是關於計算科學迄今最好的一些文章。
  9. 你能猜出這些類比我是針對哪一個錯誤的嗎?

相關文章