Twitter 广告上线记:消失的推文与那个被放过的 bug

加入广告团队

2010年春天,我收到了Twitter的工作邀请。我是Twitter的忠实用户,对能在这家公司工作感到兴奋。我接受了,甚至不知道自己会加入哪个团队。

签约后,招聘人员问:“你更喜欢分析团队还是变现团队?”我刚完成了一门我非常喜欢的数据可视化课程,所以分析似乎是个不错的选择。

“分析,”我回答。不久后我收到回复,“抱歉,这不是一个选项。”后来我了解到原因,是因为在邮件1和邮件2之间,Twitter有收购了一家公司那是新的分析团队。

所以就是变现——广告。我加入后,广告前端团队规模翻了一番。就在我加入之前推特启动搜索结果中的推广推文(“推广”是推特用来指代广告的术语)。

我加入团队的那周,推出了推广趋势(趋势中的广告)。接下来的几个月里,我们推出了推广账户(在“谁该关注”栏目投放广告)。我对此几乎没有参与。

作为一名初级工程师、新毕业生和新员工,我与更有经验的工程师搭档,帮助完善了发布。在最初的几个月里,我为Hootsuite、TweetDeck和Google等合作伙伴构建了联合发行API,用于展示广告。

我还修复了漏洞,响应了紧急问题,比如当我最喜欢的一位高管带着问题来到广告部门时。“我们不能,”他说,“麦当劳花8万美元推广这个趋势,结果搜索结果排在最前面的推文却是'老鼠肉'#McRib。”

凤凰与晋升教皇

与此同时,一群人正在重写 Twitter 的整个网页客户端。这个项目被称为 Phoenix,后来改名为 #NewTwitter。这是一个单页应用,开创了许多新技术,但没有关键技术,这些技术还需要多年才能实现。

它通过 URL 片段实现客户端路由;history.pushState/popstate直到2013年才被广泛支持。它有自己的手写类别系统(twttr.klass);课程于 2015 年加入了 JS。

我们用 Ruby 中的大量正则表达式捆绑了应用程序;当时还没有 AMD、requirejs 和 ES 模块。它被渲染在客户端,以提高响应速度,减轻 Ruby 应用的负担;JavaScript 应用的服务器端渲染还不存在(Node 0.2.0 版本刚刚发布)。

Phoenix 有组件,但它们是业务逻辑、事件和通过 Mustache 渲染模板的混合体;React 及其概念还要等三年。

对我来说,这意味着要面对庞大、复杂且快速变化的代码库,而不必参与变更团队。广告和网页团队在快速变化和对新广告团队普遍矛盾的情况下合作得不好。

一天早上,我们接到呼叫,说教皇被当作广告投放。问题是广告相关代码最近变更,我们不知道。当有人以广告形式发布趋势时,趋势会先出现。

网页团队有人重写了趋势模块,误解为“第一个趋势是广告”。那天,圣座成为新闻焦点,我们最终看到了一位晋升的教皇。

在晋升教皇之后,广告团队坚持在凤凰城实现对广告本身的支持。其中之一,我最终重新在搜索结果中加入了广告。一个持续的摩擦源是广告是推文,但它们的行为不像普通推文。

比如,当你在搜索结果中看到新推文时,它们会显示在屏幕顶部。但广告留下即使有新结果出现,搜索结果也排在搜索结果顶部。我不得不把这种行为黑进TweetStream类。

通过各种前后钩子,当我们收到新推文时,我会检查是否有广告,抓取它,然后放回流媒体顶部。

为时间线干杯!

谷歌在搜索广告方面赚了大钱。但人们搜索Twitter的频率远远不及谷歌搜索。你必须把广告放在用户所在的位置,所以计划是把广告添加到用户的首页时间线。

这很有争议,大多数员工不喜欢,这也是我的工作。

每次我需要帮助或获取事实信息的工程讨论,都变成了一场关于是否应该在推特上投放广告的小型公投。

“嘿,你能帮我弄清楚怎么缓存这些广告吗——”
“我们真的要这么做吗?”
“这里是给广告服务器添加RPC的正确地方吗?”
“补充一句什么?”

首页时间线是推特的顶级API,负责其可靠性的团队对在代码中添加广告RPC并不热衷。我们协商了一个相当激进的超时——大约50毫秒——作为纳入的条件。

我把数值输入到服务配置中,并让他们批准。直到几周后我才意识到我已经合并了0.5这让我们的预算只有500毫秒,是我们约定的10倍。

“我会发布PR来修复它,”我说。“Ryan,不行!”我的技术负责人说。“一旦你有了延迟,就不能还回去!”(我把部分延迟还给了我。)

对现代读者来说这似乎难以置信,但当时,考虑到我们所做的选择和运行的堆栈,将推文渲染成JSON的成本足够高,所以我们对它进行了缓存。

对于广告,我们需要一个独特的展示ID来追踪用户互动。所以我不得不编写一个“片段后处理程序”,从memcached中提取渲染的推文JSON,并使用字符串替换输入唯一的印象ID。

那是一个紧张而混乱的时期。我们试图打造一个能让公司维持运营的业务,同时还有许多人努力维持服务的在线。在我为主页时间线布线的初始版本广告后,我添加了更广泛的员工反馈按钮,并将分支机构放入部署队列。

当天晚些时候,有公告宣布部署被冻结。我们正忙着搬到新的数据中心,除了维持站点在线所需的变更外,我们不会部署任何分支机构。

我的分支机构在部署队列中停留了三个月。

销售团队正在旧金山集合,准备进行现场演示。我的经理问我是否可以在主时间线上给他们演示广告。我立刻答应了,因为我喜欢演示。

后来我才意识到,我根本不知道该如何让演示变得好。搜索结果中的广告已经存在;销售团队知道广告长什么样;我会展示完全相同的内容,只是屏幕稍有不同。

感觉就像一个60秒的演示。

当天到来时,我去了三楼的自助餐厅,站在大约50个销售代表面前,准备迎接冷淡的反响。我挑了一条推文,做成广告,并标记进时间线。

“你知道我们是怎么把广告放进搜索结果的吗?”我一边说,一边给他们看我们现有的产品。我切换到另一个标签页,有些不好意思地说:“嗯,你们大概能猜到这会是什么样子......”我选中的推文变成了广告。

现场顿时沸腾,我完全没准备好。事后看来,他们看法和我不同;这是他们能用来卖出数十亿广告的功能。

消失的推文

经过多次试用后,我们准备上线,但有一个小问题:偶尔用户的整个主时间线会完全变黑。我自己也见过一次。我的屏幕变白了,我心想“哎呀”,于是刷新了页面。

尽管你很想在上线前发现并修复bug,但有些你真的希望没看到,因为你知道它会耗尽你的一天。我脑海中开始有两个声音竞争。这是我代码的bug吗?

可能由任何原因引起。网页客户端太复杂了!真的那么糟吗?刷新后修好了!
与这只会在你的功能激活时发生。情况非常糟糕。屏幕完全一片空白。

不要忽视这个——它迟早会反噬你!
.于是我做了任何负责任的初级工程师都会做的事:真心希望这种事不会再发生。

当然,这种事又发生了。但更糟的是:对于我团队中其他开启广告功能标志的人,而且只是偶尔出现。偶尔有人会说,“嘿,我的屏幕完全是白色的。”经过足够多的随意举报后,我知道我必须弄清楚为什么广告开启时推文会消失。

那时我相信,就像现在一样,一旦你有了复刻,漏洞基本上就解决了。如果你能确定性地重现问题,那么这只是对你耐力和专注力的考验,去执行过程中看看你的预期和现实有何出入。

在这个案例中,我花了一周时间才做出一个复刻。

我仔细研究代码,但没有什么特别吸引我的。我不确定自己在找什么。一个随机调用一个叫做removeAllTweetsFromTimeline?我和遇到过这个bug的人聊过,问他们怎么做的。

“我收到新推文,屏幕变白了。”其实,当我收到新推文时,屏幕并没有变白,那到底是怎么回事?基于观察到人们在收到新推文时会遇到这个bug,我写了一个脚本来抓取假新推文并反复显示。

最后,我明白了:当我们抓取新推文时,会显示一个蓝色的“显示新推文”横幅。一旦你看到广告,如果你收到新推文并点击了那个横幅8次你的家乡时间线会消失。

有了那个复制,我还是花了三天才弄明白问题(我怀疑资深工程师能更快解决)。复制脚本很关键,因为我可以立刻进入问题场景,逐步完成,而不是等八次新推文。

最终,问题出在我几个月前写的代码,加上Phoenix网页客户端的两种行为。有时网页客户端会发出重复或重叠的API请求(在一个长寿命客户端中很难跟踪所有正在运行的内容!)。

当这些请求返回时,我们还是把收到的推文转回主时间线。这可能导致重复,但作为一种“腰带和吊带”策略,渲染时我们跳过了重复。如果两个API调用返回了推文123,你只会看到一个。

客户端还采取了一些措施,避免使用过多内存。有时用户会保持应用开启,投票获取数百甚至数千条新推文。但无论你收到多少推文,我们每次只显示100条新推文。

如果你想看更多,可以滚动查看!

主时间线的广告和搜索不同。在搜索中,广告会一直放在顶部。在主时间线上,广告会保持原位。但我不小心把搜索中的行为——也就是把广告移回时间线顶部——在代码里留下了。

它在TweetStream的基础类里。在主时间线中,每当你收到更多推文时,我们会把广告复制到顶部并保留它。但你从未看到这个bug,因为我们在渲染时去了复制的广告。

在你看到广告并获取新推文后,时间线上出现了两个广告,一个可见,一个不可见。在更多新推文后,你突然出现了4个广告,一个可见,3个不可见。

熟悉计算机的人会看到这种指数增长的趋势:获得8条新推文后,你有128条广告,1条可见,127条隐形。但我们只展示了前100条推文,而这些推文都被重复了,导致屏幕完全空白。

一旦我理解了这些,修复错误很简单:不要复制推文。

最棒的是,这也是搜索中的一个bug!实际上我们收到过广告商的投诉,说他们的屏幕变黑了,但我们关闭了,因为无法重现。除了那些急切想看到有人谈论他们推广趋势的广告商,没有人在搜索结果上保持8+次刷新。

我重新打开了那个bug,并关闭了它,表示已修复。

我的fix在2011年6月上线,我们在主时间线上推出了推广推文几周后.我记得刚发布时大多是兴奋的,但整体还是有些喜忧参半。我喜欢Twitter,不太喜欢广告;但我也为自己所做的工作感到自豪;同时也希望Twitter能成为一个可持续的业务,能够持续存在;同时我还能获得报酬。

这些事情很多,但不是全部。我加入Twitter的前一年,公司广告收入为零。我离开时,我们的年收入达到20亿美元。团队规模大了许多,虽然你从这段经历中看不出来,但我的整体贡献其实很小。

当我们在家庭时间线上投放广告时,团队已经很庞大——Anand、Alan、Andrew、Animitra、Avi、Bailey、Ben、Cara、Ed、Eric、Evelyn、Lennon、Luke、Mark、Niels、Ron、Sara、Srini、Tom、Utkarsh、整个销售团队以及许多其他人——但能在起点就在这里,令人兴奋。

参与讨论