Appboy 基于 MongoDB 的数据密集型实践
摘要:Appboy 正在过手机等新兴渠道尝试一种新的方法,让机构可以与顾客建立更好的关系,可以说是市场自动化产业的一个前沿探索者。在移动端探索上,该公司已经取得了一定的成功,知名产品有 iHeartMedia、PicsArt、Etsy 等。
【编者按】本文摘录自 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 四方面内容,本文系 OneAPM 工程师编译整理。
以下演讲摘录:
为了支撑其营销自动化平台,Appboy 为其分析和定位引擎使用了 MongoDB 作为其主要数据存储层。时下,Appboy 每天需要处理上万用户的数十亿数据点。本文将分享 Appboy 关于 MongoDB 的最佳实践,看看该公司如何在规模迅速扩大后仍然保持敏捷。本文将谈及诸多话题,如文档随机抽样、多变量测试及其 Multi-arm bandit optimization、Field tokenization,以及 Appboy 如何在一个个体用户基础上存储多维数据从而优化以最佳的时间给终端用户提供信息。
Part 1:Statistical Analysis
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
db.users.find({random: 9043}).count() == ~1000
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 仪表盘上提供了卓越的体验,用户可以通过添加和删除选择标准并立即看到统计数据更新,从而用户可以进行交互式探索。
Part 2:多变量测试和比率限制
多变量测试的快速入门
在当今竞争激烈的市场中,用户细分是必不可少的。随着经验与品牌继续快速转向移动等新兴渠道,对营销来说,信息定制化和关联性性比以往更加重要,这就是用户分类为什么会成为与客户交互的先决条件。
因此一旦定义了一个划分,下一个目标是优化消息推送使其转换最大化,多变量测试则是实现这一目标的一种途径。多变量测试是一个实验,用来比较用户对相同营销活动多个不同推广手段的反应。这些版本拥有相同的营销目标,但在措辞和风格上有所不同,而多变量测试的目标就是为了确定哪个版本能达到最好的转换。
例如,假设您有3个不同的推送通知消息:
- Message 1:This deal expires tomorrow!
- Message 2:This deal expires in 24 hours!
- Message 3:Fourth of July is almost over! All deals end tomorrow!
此外,除下消息,通常还会测试大量的图片搭配合文本。
使用多变量测试,机构可以发现哪种措辞产生更高的转化率。在下次发送推送式通知谈生意时,就可以知道哪种语气和措辞更有效。更好的是,可以通过限制测试的大小,比如在一小部分听众内,找出哪些消息更有效,然后发送这些有效的消息给其他人。
在进行一个多变量测试时,消息推送的目标是测试全体,但是同一细分中的其他用户不会收到该条消息。从而,机构可以通过对比两种反应来进行评估。
技术应用
从技术的角度来看,接收消息的人应该是随机的。也就是说,如果你有100万用户且想要发送一个测试给50000人,这50000人应该是随机分布在你的用户群里(你还想要另一组5000用户为对照组)。同样的,如果你想测试10到50000用户,随机性有助于确保每个测试组的用户都不同。
思考这个问题,它与1个消息中的比率限制问题是一行。许多客户想要给一小群用户发送一条消息。比如,一个电子商务公司想随机的在用户群中发放50000个优惠码。这在概念上,是同样的问题。
为了实现这一点,这里可以通过每个文档上的随机值来扫描用户:
Appboy 会在不同的随机范围内通过随机值用并行处理的方式来管理用户。并追踪全局状态,因此可以知道何时达到比率的极限。对于多变量测试而言,随后还会通过发送比率或者是随机地选择一个消息版本。
注意
那些有数学思维的人可能已经注意到,如果在随机字段中使用统计分析,并基于相同的随机字段选择个体接收消息,那么在某些情况下,将会产生偏差。为了阐释这一说法,假定使用随机 bucket 值为10来选择所有用户,给他们随机发送消息。这意味着,在这个用户 bucket 中收到消息的用户将不再是随机分布。作为一个简单的解决方案,Appboy 对用户使用多个随机值,注意不要为了多个目的使用相同的随机值。
Part 3:模式灵活——可扩展的用户配置文件
在每个用户打开 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。
Part 4:数据密集型算法
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 工程师编译整理。想阅读更多技术文章,请访问 OneAPM 官方博客。
Appboy 基于 MongoDB 的数据密集型实践的更多相关文章
- 基于 DataLakeAnalytics 的数据湖实践
随着软硬件各方面条件的成熟,数据湖(Data Lake)已经越来越受到各大企业的青睐, 与传统的数仓实践不一样的是,数据湖不需要专门的“入仓”的过程,数据在哪里,我们就从哪里读取数据进行分析.这样的好 ...
- 基于 DolphinScheduler 的数据质量检查实践
今天给大家带来的分享是基于 Apache DolphinScheduler 的数据质量检查实践,分享的内容主要为以下四点: " 为什么要做数据质量检查? 为什么要基于 DolphinSche ...
- mongoose基于mongodb的数据评论设计
var CommentSchema = { data:{type: ObjectId, ref:'Data'}, //Data数据表,此处存数据id from:{type: ObjectId, ref ...
- 基于 K8S 构建数据中心操作系统
在 12 月 22 日 ECUG 的下午场 ,七牛云容器计算部技术总监袁晓沛为大家带来了主题为<基于 K8S 的 DCOS 之路>的精彩分享,向大家介绍了七牛容器云目前 K8S 的状况和产 ...
- MongoDB丢数据问题的分析
坊间有很多传说MongoDB会丢数据.特别是最近有一个InfoQ翻译的Sven的一篇水文(为什么叫做水文?因为里面并没有他自己的原创,只是搜罗了一些网上的博客,炒了些冷饭吃),其中又提到了丢数据的事情 ...
- 基于Open vSwitch的OpenFlow实践
Open vSwitch(下面简称为 OVS)是由 Nicira Networks 主导的,运行在虚拟化平台(例如 KVM,Xen)上的虚拟交换机.在虚拟化平台上,OVS 可以为动态变化的端点提供 2 ...
- 数据密集型 和 cpu密集型 event loop
Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速.可伸缩的网络程序.Node.js采用的事件驱动.非阻塞I/O模型使它既轻量又高效,是构建 ...
- 基于MongoDB分布式存储进行MapReduce并行查询
中介绍了如何基于Mongodb进行关系型数据的分布式存储,有了存储就会牵扯到查询.虽然用普通的方式也可以进行查询,但今天要介绍的是如何使用MONGODB中提供的MapReduce功能进行查询. ...
- 基于MongoDb官方C#驱动封装MongoDbCsharpHelper类(CRUD类)
近期工作中有使用到 MongoDb作为日志持久化对象,需要实现对MongoDb的增.删.改.查,但由于MongoDb的版本比较新,是2.4以上版本的,网上已有的一些MongoDb Helper类都是基 ...
随机推荐
- vlan知识
为什么需要VLAN 1. 什么是VLAN? VLAN(Virtual LAN),翻译成中文是“虚拟局域网”.LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算机构成的企业网络.VLAN ...
- 7款震撼人心的HTML5文字特效
1.CSS3五彩文字特效 文字带阴影效果 这是一款非常具有卡通形象的CSS3五彩文字特效,虽然没有迷人的动画效果,但是五彩缤纷的文字展现在屏幕上也是非常酷的,再加上每一个文字都有不同角度的阴影效果,加 ...
- springmvc 精华
Spring Mvc简介: Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求 ...
- 济南学习 Day 3 T1 am
NP(np)Time Limit:1000ms Memory Limit:64MB题目描述LYK 喜欢研究一些比较困难的问题,比如 np 问题.这次它又遇到一个棘手的 np 问题.问题是这个样子的:有 ...
- C++封装常用对象和对头文件以及预编译机制的探索
在C++实际开发中,难免会使用到一些你极为常用的算法(比如笔者经常使用的多线程技术),实现这些算法的类或是全局函数或是命名空间等等经常都要被使用多次,你会有哪些办法来使用呢?笔者有4个办法. 第一个方 ...
- .NET4.5可以给所有线程设置默认的Culture了
How to set CurrentCulture for all threads in a domain in .NET 4.5 Before .NET 4.5 if we wanted to se ...
- 【风马一族_Android】android的新发现
Intent intent = new Intent(); intent.setAction("android.intent.action.VIEW");这一段句话.可以调用出系统 ...
- 获取SilverLight.Web项目中路径Uri
方法一: //获取指定要呈现的xaml内容的包活xaml文件Uri var strFullUrl = Application.Current.Host.Source.AbsoluteUri; if ( ...
- php下载文件的代码示例
php下载文件的代码示例,需要的朋友可以参考下 <?php $file = 'monkey.gif'; if (file_exists($file)) { header('Content- ...
- python杂记-6(time&datetime模块)
#!/usr/bin/env python# -*- coding: utf-8 -*-import timeprint(time.clock())##返回处理器时间,3.3开始已废弃 , 改成了ti ...