【编者按】 本文摘录自Appboy联合创始人兼CIO Jon Hyman在 MongoDB World 2015 上的演讲。Appboy正在过手机等新兴渠道尝试一种新的方法,让机构可以与顾客建立更好的关系,可以说是市场自动化产业的一个前沿探索者。在移动端探索上,该公司已经取得了一定的成功,知名产品有iHeartMedia、PicsArt、Etsy、Samsung、Urban Outfitters等。本文主要包括Statistical Analysis、Multivariate Testing and Rate Limiting、Flexible Schemas: Extensible User Profiles和Data Intensive Algorithms四方面内容。
以下演讲摘录:
为了支撑其营销自动化平台,Appboy为其分析和定位引擎使用了MongoDB作为其主要数据存储层。时下,Appboy每天需要处理上万用户的数十亿数据点。本文将分享Appboy关于MongoDB的最佳实践,看看该公司如何在规模迅速扩大后仍然保持敏捷。本文将谈及诸多话题,如文档随机抽样、多变量测试及其Multi-arm bandit optimization、Field tokenization,以及Appboy如何在一个个体用户基础上存储多维数据从而优化以最佳的时间给终端用户提供信息。
Appboy适用于各种大小的客户群体,其中包括了只有数万用户的初级客户,也有客户已经拥有了数千万用户。但是毫无疑问的是,通过Appboy营销自动化技术,即使拥有上亿用户规模的客户仍然可以便捷地收集和储存用户数据。
Appboy平台的核心是customer segmentation(客户细分)。segmentation允许机构根据行为数据、消费史、技术特性、社交概要等来定位。创新和智能的使用Segmentation和信息自动化使机构可以无缝地、轻松地将安装用户转化为活跃的用户,从而斩获kpi,Segments可以按需定制。
当客户使用Appboy仪表板定义segment时,Appboy可以在一些特征上做实时计算,比如群体大小、开通消息推送的用户规模、用户平均消费能力。这些计算需要实时和交互式的,而在不具备谷歌规模的基础设施上,在这种规模上做交互式分析是极具挑战的。这里存在的挑战是如何更有效率的支撑如此规模,以及如何服务于各种体积的用户。基于这些原因,随机抽样是个不错的选择。
关于统计抽样
在现实世界中,随机的统计抽样时刻发生着,比如针对美国总统的舆情调查不可能去单独的问每个人,全国收视率统计也并不是靠评级机构查看每个用户的电视机。相反,这些数来源于统计抽样,统计抽样通过抽样小部分群体来获得更大群体的特征。通过统计数据,1个小样本就可以对大规模群体做出准确的评估。许多政治民意调查只调查几千成年人就可以估计数以百万计的公民的政治倾向。但调查机构的报告与统计也经常带有所谓的置信区间,也称为偏差。
统计抽样的使用
相同的原则可以运用到这里。与传统分析数据库相比,抽样用户有一个明显的优势,因为这里可以从用户的整体行为上进行抽样,而不是从原始事件流中取样。需要注意的是,Appboy只针对segment 交互式反馈做抽样,从而在网络仪表板反馈。当做营销活动或对Segment进行分析作为Facebook Custom Audience 时,准确用户会被计算,而这些原则并不适用。
在开始时,会在已知范围document 内添加一个随机数字,称之为“bucket”。选择一个合理的小用户群,从而有可能聚焦每个用户,需要注意的是,这个抽样规模乘以bucket的数量必须覆盖该范围。举个例子,只选择了1到10的抽样显然不可以支撑上亿规模,1到100万显然是个不错的选择。在Appboy共拥有1万个“bucket”,所以应该是0到9999。
假设这里有1000万个documents(代表用户),首先将给这些文档加个数字并对其索引。
{ random: 4583, favorite_color: “blue”, age: 29, gender: “M”, favorite_food: “pizza”, city: “NYC”, shoe_size: 11 } db.users.ensureIndex({random:1})
第一步是获得1个随机样本。1000万个document ,10000随机的buckets,每个buckets应该有1000个用户:
db.users.find({random: 123}).count() == ~1000<br>db.users.find({random: 9043}).count() == ~1000<br>db.users.find({random: 4982}).count() == ~1000
如果抽取整个用户基础的1%,那就是10万的用户。为了实现这一点,必须选择一个随机范围来“托管”这些用户。举个例子,这些都是可以的:
db.users.find({random: {$gt: 0, $lt: 101}) db.users.find({random: {$gt: 503, $lt: 604}) db.users.find({random: {$gt: 8938, $lt: 9039}) db.users.find({$or: [ {random: {$gt: 9955}}, {random: {$lt: 56}} ])
在有了随机样本后,下一步就是对其分析。要衡量其真正的大小,首先需要进行一个计数,因为鉴于随机性这里不可能精确到100000。
在并行的方式,这里可以在样本上添加任意查询,这里拿找出最喜欢蓝色的男性用户比例。
sample_size = db.users.find({random: {$gt: 503, $lt: 604}).count() observed = db.users.find({random: {$gt: 503, $lt: 604}, gender: “M”, favorite_color: “blue”).count()
假如,样本大小设定是100000,观察数是11302。从这里可以推断出,在1000万用户中有11.3%的用户符合标准。要称为优秀的统计学家,还应该提供一个置信区间来估计偏离值。置信区间背后的数学有点复杂,但是,如果想自己尝试的话,有无数样本sizecalculators可供参考。本文使用的案例中,置信区间为+ / - 0.2%。
优化
在实践中,当执行统计抽样时,Appboy基于这些高等级概念概念做了大量优化。首先,Appboy使用MongoDB聚合框架,并且大量使用缓存。因为这里使用的是内存映射存储引擎,对于这种抽样,使用MongoDB的好处是一旦将随机样本加载到内存就可以运行任意查询。这么做为web仪表盘上提供了卓越的体验,用户可以通过添加和删除选择标准并立即看到统计数据更新,从而用户可以进行交互式探索。
多变量测试的快速入门
在当今竞争激烈的市场中,用户细分是必不可少的。随着经验与品牌继续快速转向移动等新兴渠道,对营销来说,信息定制化和关联性性比以往更加重要,这就是用户分类为什么会成为与客户交互的先决条件。
因此一旦定义了一个划分,下一个目标是优化消息推送使其转换最大化,多变量测试则是实现这一目标的一种途径。多变量测试是一个实验,用来比较用户对相同营销活动多个不同推广手段的反应。这些版本拥有相同的营销目标,但在措辞和风格上有所不同,而多变量测试的目标就是为了确定哪个版本能达到最好的转换。
例如,假设您有3个不同的推送通知消息:
此外,除下消息,通常还会测试大量的图片搭配合文本。
使用 多变量测试 ,机构可以发现哪种措辞产生更高的转化率。在下次发送推送式通知谈生意时,就可以知道哪种语气和措辞更有效。更好的是,可以通过限制测试的大小,比如在一小部分听众内,找出哪些消息更有效,然后发送这些有效的消息给其他人。
在进行一个多变量测试时,消息推送的目标是测试全体,但是同一细分中的其他用户不会收到该条消息。从而,机构可以通过对比两种反应来进行评估。
技术应用
从技术的角度来看,接收消息的人应该是随机的。也就是说,如果你有100万用户且想要发送一个测试给50000人,这50000人应该是随机分布在你的用户群里(你还想要另一组5000用户为对照组)。同样的,如果你想测试10到50000用户,随机性有助于确保每个测试组的用户都不同。
思考这个问题,它与1个消息中的比率限制问题是一行。许多客户想要给一小群用户发送一条消息。比如,一个电子商务公司想随机的在用户群中发放50000个优惠码。这在概念上,是同样的问题。
为了实现这一点,这里可以通过每个文档上的随机值来扫描用户:
Appboy会在不同的随机范围内通过随机值用并行处理的方式来管理用户。并追踪全局状态,因此可以知道何时达到比率的极限。对于多变量测试而言,随后还会通过发送比率或者是随机地选择一个消息版本。
注意
那些有数学思维的人可能已经注意到,如果在随机字段中使用统计分析,并基于相同的随机字段选择个体接收消息,那么在某些情况下,将会产生偏差。为了阐释这一说法,假定使用随机bucket值为10来选择所有用户,给他们随机发送消息。这意味着,在这个用户bucket中收到消息的用户将不再是随机分布。作为一个简单的解决方案,Appboy对用户使用多个随机值,注意不要为了多个目的使用相同的随机值。
在每个用户打开Appboy的任意一个应用,一个丰富的用户概要文件都会被创建。用户的基本字段看起来是这样:
{ first_name: “Jane”, email: “jane@example.com”, dob: “1994-10-24”, gender: “F”, country: “DE”, ... }
Appboy客户端还可以存储每个用户的“自定义属性”。作为一个例子,一个体育应用程序可能想存储用户“最喜欢的球员”,而电子商务应用程序可能会存储客户最近购买的品牌等。
{ first_name: “Jane”, email: “jane@example.com”, dob: 1994-10-24, gender: “F”, custom: { brands_purchased: “Puma and Asics”, credit_card_holder: true, shoe_size: 37, ... }, ... }
这么做一个巨大的好处是,这些自定义属性可以在其他属性更新时直接插入。因为MongoDB提供灵活的模式,添加任意数量的自定义字段都很容易而,且不用担心它的类型(boolean、string、intege、float又或是什么)。MongoDB会处理这一切,而查询自定义属性也很容易理解。针对一个value列,这里不提供复杂的连接,而在传统关系型数据库中你往往需要提前定义。
db.users.find(…).update({$set: {“custom.loyalty_program”:true}}) db.users.find({“custom.shoe_size” : {$gt: 35}})
这么做的缺点是,如果用户不小心在客户端中使用非常长的名称来定义(“this_is_my_really_long_custom_attribute_name_it_represents_shoe_size”)或者是被MongoDB,在MongoDB的早期版本中它会占用大量的空间。另外,因为类型不是强制的,这里也可能出现跨documents值类型不匹配问题。1个document可能列出某人{ visited_website: true},但是如果不小心,另一个就可能是{ visited_website: “yes”}。
Tokenization
为了解决第一个问题,通常会使用1个map来切分用户属性名称。通常情况下,这可以是MongoDB中的一个documents,比如将“shoe_size”值映射成一个独特的、可预测的短字符串。只要使用MongoDB的自动操作,就可以生成这种映射。
在映射中,通常会使用数组进行存储,数组索引是“token”。每个客户至少有一个document会包含list的数组字段。当首次添加一个新的自定义属性时,可以自动把它放到列表最后,生成索引(“token”),并在第一次检索后缓存:
db.custom_attribute_map.update({_id: X, list: {$ne: "Favorite Color"}}, {$push: {list: "Favorite Color"}})
可能会有这样一个列表:
[“Loyalty Program”, “Shoe Size”, “Favorite Color”] 0 1 2
MongoDB最佳实践需要警示documents的不断增长,而自Appboy定义起,documents就存在无限增长的情况。实际上,这个潜在的问题已经被考虑,而这里则是通过限制数组大小来让用户使用多个documents。当给列表添加新的项时,如果数组长度小于一定规模,更新操作只能局限于$push。如果更新操作没有生成1个新$push,一个自动的$findAndModify可以用来创建一个新文档并添加元素。
Tokenization确实增加了一些间接和复杂性,但它可以自定义映射属性,从而在整个代码库传递。这个解决方案同样可以应用到其他问题上,可以是数据类型文档中不匹配。在这里同样可以使用映射来追踪数据类型。例如,记录“visited_website”是一个boolean,只接受true或者false。
Intelligent Selection和Multi-Arm Bandit Multivariate Testing
多变量测试目标是,在最短的时间内寻找最高的转化率,当下已经可以在大量平台上发现,客户会定期进行测试,并发现最好的那个。
Appboy一种叫做Intelligent Selection(智能选择)的特性,该特性可以分析多变量测试的性能,并依据统计算法自动调整接收到不同版本消息的用户比例。这里的统计算法可以确保获得最真实的性能,而不是随机的可能性,该算法被称为themulti-arm bandit。
multi-arm bandit背后的数学算法非常复杂,本文不会阐述,这里不妨着眼剑桥大学数理统计学教授Peter Whittle 在1979年的发言:
[The bandit problem] 是二战期间被正式提出的,为了解决它,盟军分析师绞尽脑汁,备受折磨,以至于建议把这个问题抛给德国,作为知识战的终极手段。
但提出这个算法的理由表明,为了有效地运行,the multi-arm bandit算法需要输入大量的数据。对于每个消息版本,该算法会计算接受者以及转换率。这就是MongoDB发光的地方,因为可以使用 pre-aggregated analytics 实时地自动积累数据:
{ company_id: BSON::ObjectId, campaign_id: BSON::ObjectId, date: 2015-05-31, message_variation_1: { unique_recipient_count: 100000, total_conversion_count: 5000, total_open_rate: 8000, hourly_breakdown: { 0: { unique_recipient_count: 1000, total_conversion_count: 40, total_open_rate: 125, ... }, ... }, ... }, message_variation_2: { ... } }
通过这样1个模式,可以快速查看每天和每小时的转换变化,打开并发送。Appboy模式稍微复杂一点,因为这里还存在其他需要考虑的因素,这里需要注意。
预聚合 documents允许快速终止实验。鉴于为每个公司对collection进行分片,这里可以以扩展的方式同时优化某个公司的全部活动。
智能交付
Appboy提供给客户的另一个专有算法称为智能交付。当规划消息活动部署时,Appboy分析出给每个用户发送消息的最优时间,并在正确的时刻提供可顾客。比如Alice更可能在晚上获取应用程序推送信息,而Bob则喜欢把这个事情放在早上上班前,那么Appboy将会在他们最乐意的时间推送消息。这是个能创造奇迹的特性,正如Urban Outfitters CRM和Interactive Marketing负责人Jim Davis所称赞的:
对比使用前后的打开比率,可以看到表现提升了1倍。针对男性Urban On members一周活动目标提升了138%。此外,细分功能也值得称赞,提升了3个月以上不活跃用户94%。
这种算法无疑是数据密集型,要智能地预测出给每个客户发送消息的最佳时间,必须清楚的分析出这个用户的行为特征。除此之外,Appboy每天将发送数千万智能交付消息,所以这里需求一个非常高的预测。
这里的方法是类似于Intelligent Selection,主要通过实时地在每个用户的基础上预聚合多个维度完成。有了MongoDB,每个用户都有很多documents,类似下面代码:
{ _id: BSON::ObjectId of user, dimension_1: [DateTime, DateTime, …], dimension_2: [Float, Float, …], dimension_3: […], ... }
当所关心的用户维度数据进入时,应使其正规化,并保存documents的副本。通过{_id: “hashed”}对每个documents进行,从而让读写分布的更优化。当需要用Intelligent Delivery发送一条消息,这里可以快速地查询一系列documents,并送到机器学习算法。在这个方面,MongoDB带来的帮助很大,其可扩展性当下已支撑系统的数十个维度。而随着新的维度被添加,这个机器学习算法被不停的修改,而这个过程一直受益于MongoDB的灵活。
本文讨论了系统的主要设计思想,而这一切都得益于MongoDB灵活的模式和强扩展性。而通过MongoDB,Appboy在数据密集型工作上取得了一个很大的成功。
原文链接: Remaining Agile with Billions of Documents: Appboy’s Creative MongoDB Schemas (译者/ OneAPM 工程师 责编/仲浩)