当前位置:网站首页>去哪儿网BI平台建设演进史
去哪儿网BI平台建设演进史
2022-06-26 06:00:00 【Impl_Sunny】
一、引言
通过 BI 平台取数、看数、分析数成为辅助决策、精细运营等非常重要的手段,然而随着去哪儿网业务不断发展,产品、运营等同学对这方面有更高的要求,例如简单易用的拖拽式报表、取数方便的自由式分析、查询速度的秒级响应、观测指标数据的准确可信等等。
面对用户的个性化诉求以及海量数据,在平台体系化建设和技术实现上有一定的挑战性,本文将介绍去哪儿网BI平台的建设历程及实践,通过打造全场景的BI平台为业务增长赋能。
二、建设历程
从2015年至今BI平台的建设,经历了多年迭代发展,始终结合业务需要遵循以下几个原则:
用户尽可能的自助完成,使开发同学尽可能少的介入,即提升取数看数分析数效率;
平台功能上,操作方便门槛要低、取数方式要丰富,即提升平台易用性;
系统性能上,查询速度要快,即提升查询性能;
数据指标准确可信,即提升数据可信度。
BI 平台建设时间线如上图,根据实际业务需要上线相应模块,总体大致分为三个阶段:
原始阶段,2016年之前,一路到底式的报表系统 V1 ;
发展阶段,2016年—2018年,配置式的报表系统 V2 、自助分析、 OLAP ;
体系建设阶段,2019年—至今,即席查询、自助邮件报表、数据报表系统 V3 、 OLAP(CRM) ;子系统互联互通、全面对接指标系统建设标准化指标。
2.1 原始阶段
关键词:一路到底式、数据体量小、快速完成需求开发
原始时期,业务处于快速发展阶段,公司绝大多数的精力在业务上,用户的取数看数分析数诉求基本依赖数据部门排期开发的报表,为满足这种大量的数据需求,全部数据开发资源投入业务需求,进行一路到底式的报表开发,从收集解析日志、ETL、导数、写报表平台前后端等,如下图所示。
通过 Hive 解析日志,进行 ETL ,按业务逻辑将计算结果导出到关系型数据库 MySQL ;
报表系统的工程里写后端逻辑从 MySQL 数据库里查询;
写前端页面实现各种个性化的图和表来展示数据。
这种方式属于初级阶段,还谈不上平台建设,虽能以快速满足业务需求为目的但也暴露出很多问题:
这种一路到底式的开发模式,导致消化需求并没有想象中的迅速,反而效率低,而且代码风格不一质量不一;
每个需求有自己的个性,同样也有共性,结果是无论数据还是图表,出现大量的个性化而且互相冗余。
总结后我们发现,业务的快速发展不是借口,这种方式并不快而是痛苦,亟需明确分工,通过平台建设将大量堆积的数据需求转化成用户部分自助。
2.2 发展阶段
关键词:分工明确、部分自助
随着业务和数据团队的发展,数据仓库和数据平台建设同样重要,从此分化两个方向,一个是偏向于业务数据的团队,可以将更多的精力放在业务和数据本身以及数仓模型建设上;另一个是偏向于数据平台的团队,将报表、权限等等系统重构并专门负责,有利于将数据平台建设得更加易用,提升用户取数看数分析数效率。因此此阶段除了重构报表系统将报表的配置工作交给用户外,还搭建了自助数据分析系统和 OLAP 系统,进一步提升取数看数分析数的自助率。
2.2.1 报表系统 V2
数据开发同学根据需求将数仓最终产出的 ADS 层数据导入 PostgreSQL ,这里用到 PostgreSQL 是因为其有丰富的分析函数,在统计方面效率表现很好;
产品等用户首先配置维度、指标和筛选项作为数据单元,此数据单元可在不同报表中复用,然后在报表中引用后,发布成最终的报表展示数据。
2.2.2 自助数据分析
①场景痛点
报表系统引入的是数仓 ADS 层数据表,是固化口径计算好的结果,然而并不能很好得支持多样的分析场景;
从 DWD 到 ADS 的过程需要数据开发同学排期完成,对于不会写 SQL 的产品运营同学,临时性、灵活且简单的聚合分析想获取数据,需要提需求,周期太长,如果能够基于 DWD 明细数据,用户直接自由分析可以很大程度提升数据分析效率,数据开发同学也能从琐碎的数据需求中释放;
规范化埋点的实时数据(包括行为和订单数据),也需要数据开发同学排期做实时 ETL ,是否可以做到全程自动化,在平台上能分析实时的埋点数据。
如下图的界面功能是从 SQL 语法中抽象而来,用户只需点选即可自助完成常规的聚合查询分析。
②整体架构
结合实际痛点分析出有以下必要的诉求点:
需要支持实时数据插入、更新和读取
需要支持直接读取离线数仓表和实时数据表,查看截止到当前时刻时间段的数据,而且需要支持表关联进行分析
自助分析需要对多个维度指标查询,且需较快的响应
针对同时查离线和实时数据的诉求,首先得有个统一的查询入口,然后对亿级别以内量级的数据做数据分析要保障效率,由此可以想到 Impala+Kudu 和 Impala+HDFS( Parquet )组合( Kudu 只存当天的实时数据,离线数据从原有的 HDFS 上读取)。这个方案相对把两类数据都导入到某个其他引擎中,从存储和实现成本上是较小的。
Apache Kudu 是介于 Hadoop 和 Hbase 之间的,既能满足高吞吐量的数据分析需求又能满足快速的数据随机读写需求。基于 Impala 和 Kudu 的系统架构图如下:
数据写入过程:
离线数据无需写入,数据存在 HDFS 上,Impala 连 Hive 可直接读表查询,减少离线数据处理环节节省成本,这也是选择 Impala 的原因之一;
实时数据来源包括实时数仓、规范化埋点实时数据等,通过 Kafka 由 Flink 实时写入 Kudu 表作为热数据,同时写一份到 HDFS 做为冷数据和备份;
在 Hive 表和 Kudu 表基础上建 Impala 视图,将离线和实时数据表 Union 在一起,以供查询。
数据读取过程
用户在分析页面选择维度分组、筛选条件、统计周期、指标运算等点击查询;
查询模块首先从缓存中取,如果是重复请求直接返回,如果不是则解析请求参数,拼接查询Impala的SQL;
对于多指标分析,为提升整体查询效率,拆分成多条SQL并行执行;
对于跨多天的非去重查询,将查询结果按天存为碎片缓存,减少后续的重复查询,提升查询效率;
将查询Impala的数据和从碎片缓存取出来的数据,合并后返回到页面展示。
2.2.3 实时OLAP
业务上出现了两个典型的 OLAP 场景:
- 收益团队需要对历史全量订单,亿级别的数据量进行全维度分析后制定策略,待策略上线后,再进行实时监控成单收益效果,通过多维分析定位到具体哪个渠道、城市、星级等等维度效果不佳,指导及时调整策略。
- 酒店业务团队,我们知道用户预定酒店的过程中涉及搜索、列表页、详情页、预定页、成单等主要环节,需要针对各个阶段的业务系统进行实时顺畅度监控,就是将用户每次请求在各个阶段的顺畅度情况实时收集,这个数据量是亿级别的,然后进行多维统计分析和实时监控,有问题能够及时告警甚至需要阻断发布和自动报故障,辅助业务团队定位问题解决问题,提升用户在预定酒店过程的体验感受。
从这些场景中可以提炼出一些关键点:数据实时性要求高,数据量为亿级别,维度指标上百个,数据存储适合大宽表,查询要秒级响应。
①计算引擎
为满足上述诉求OLAP引擎的选择是关键,我们对比当时常用的引擎如下:
Kylin在十几个维度情况下与Druid是差不多的,但我们遇到的场景是几十个维度,Kylin的Cube膨胀率很高,查询性能也达不到预期;再一个业务需求变化得重建Cube不够灵活,Kylin比较适合固定20个维度内,且业务逻辑计算很复杂需要预计算的场景;
Presto不支持实时数据,且秒级的交互式查询也满足不了;
ES在亿级别数据量下多维分析查询性能不佳,写入效率也不高;
Kudu+Impala方案看起来比较合适,但当前需求不关心数据更新,更多关注的是在亿级别的数据量之下查询响应的时长。
通过多种维度的比较,我们最终选择了能支撑亿级别数据量、支持实时数据、在近百个维度指标情况下查询性能依然很高的Apache Druid,来支撑这类实时 OLAP 场景。
②数据可视化
数据可视化方面选择开源的 Superset ,主要原因是其深度支持 Apache Druid,并支持其他众多数据源,能很好的兼容历史数据。Superset 具有较强的数据分析能力,且有丰富的可视化图表类型,另外也支持将图表配置成数据看板,将固化的分析口径以报表的形式呈现。
至此 OLAP 解决方案如下:
离线数据通过 Hive 同步到 PostgreSQL ,这条链路是报表系统 V2 的统计数据源,Superset 可直接接入,对可视化图表类型有更高要求的用户有了一个不错的选择;
实时数据通过 Kafka Index Server 摄入 Druid 集群, Superset 连接 Druid ,看板里设置刷新频率或手动刷新查看实时数据;
我们对 Superset 进行了二次开发,内容包括接入公司 SSO 登录、统一的权限系统、集成 ECharts 使可视化图表更加丰富,例如漏斗图、国家地图等。
2.2.4 阶段总结
在此阶段通过明确的分工,将特定资源集中在数据平台建设上,解决了用户大部分取数看数分析数场景的诉求,包括报表配置、自助数据分析、实时 OLAP 等,用户能够通过工具自助获取,不再完全依赖数据开发同学,效率相对有很大的提升;数据开发同学也大大减少了临时性琐碎的取数需求,把更多的精力放到业务本身和数仓建设上。
然而我们面对用户多种多样的诉求,不断投入专门的资源来满足,不断推翻迭代造成资源浪费,这就引发了接下来的BI平台体系化建设。
2.3 体系建设阶段
关键词:多元化、个性化、标准化、体系化
用户的诉求是多样化的,但又不可能都得开发相应的系统来对应满足,伴随以下痛点我们当前需要统筹思考、整体设计、一劳永逸,做体系化建设。
报表系统,可视化图表类型不够丰富,添加新类型图表需要定制开发,这里涉及大量的前端开发工作;
Superset的看板可以部分替代数据报表来用,其分析功能强大,数据分析同学非常喜爱,但产品运营同学觉得使用门槛高以至于不想用;
OLAP场景诉求更加个性化,大宽表模式已无法满足需求;
对于极其个性的取数看数诉求不得不自行写SQL通过AdHoc来获取;
业务指标口径不一,各方看到的同一个指标数据结果对不齐,需要找开发、产品等一遍遍的对口径。
经历之前两个阶段后 BI 平台雏形已现,下图中展示了当前阶段 BI 平台的整体架构概略设计,本文将着重介绍本阶段的建设和实践,接下来分场景模块来介绍。
2.3.1 即席查询与自助邮件报表
即席查询在之前基本通过登录客户机或开源的 HUE 来写 SQL 取数,这种方式会面临很多问题,例如权限控制无法很好地保障有数据安全风险、SQL 脚本无法管理随着人员流失就流失掉了、写 SQL 用到的日期变量没有灵活的支持等等个性化需求,因此结合业务诉求搭建了即席查询与自助邮件报表系统。
即席查询和自助邮件报表用户自行编写 SQL ,即席查询用户手动点击运行,自助邮件报表通过配置的定时或调度依赖自动触发查询请求;
服务模块首先做语法检测,然后解析 SQL 获取要查询的表和分区数量,调用权限系统校验用户是否有表的访问权限,再根据用户等级来限制允许加载的分区数量、校验允许同时执行的任务数量、任务 MR 并行度等,最终提交查询到执行模块;
执行模块通过 JDBC 连接并执行 SQL ,然后将数据展示在前端页面预览或下载到本地或以邮件的形式发送报表。
2.3.2 数据报表模块
数据报表模块是迭代的第三个版本,除了贴合业务需求外,我们在重构前需要思考第三版能用多久,带着这个问题提炼出以下原则:
使用角色 | 原则 |
数据平台开发 | 以平台建设思维,一次性搭建,将图表组件 |
数据开发 | 开发维护好数据模型,基于此可支撑各种口径各种类型图表的配置 |
产品运营 | 不必写SQL,通过拖拽,所见即所得 |
内部管理 | 业务内部管理权限以及数据看板等繁杂事务,完全自助 |
①架构设计
数据源层
离线数据支持从 MySQL 、离线数仓以及指标系统中同步,也支持业务系统的监控数据同步;
实时数据从实时数仓提供的 Kafka 接入,基本是 DWD 层数据;
数据接入层
专门的导数平台,支持离线和实时数据导入到各种存储中;
离线数据导数任务完成后会进行数据校验,失败则告警给导数任务的开发同学以及引用本数据源的图表负责人;
实时数据由 Kafka 导入 ClickHouse 或 Apache Druid 。
存储/引擎层
根据不同场景选择不同的存储,离线结果数据推荐 PostgreSQL ,数据量大推荐 GP ;实时统计数据存入 Druid ,多维数据分析场景存入 ClickHouse ;
为提升查询效率,离线导数任务成功后,触发引用当前数据源的图表数据刷入缓存中,另外用户自主查询后的结果也都存入临时性缓存;
数据模型层
基于导入的底层数据表,设置维度、指标定义数据模型,根据业务需求抽象合理的模型,这是数据报表模块的核心,也是数据开发同学工作的重点;
数据展示层
可视化图表,提供了常用图表模板近十种类型,基于数据模型可以自由拖拽维度指标;最上层将多图表组成数据看板用于报表展示,支持常用的上卷下钻;对于实时数据大屏场景,通过拖拽也可高效完成。
系统管理
数据看板作为资源接入统一的权限系统进行管理
离线导数任务通过调度系统周期调起任务
各层均有系统性能监控,时刻关注平台性能状态
用户使用平台的行为通过埋点记录,用于掌握用户使用情况,尤其是对无人访问的看板做下线参考
支持图表和看板嵌入到第三方系统,目前已嵌入机酒业务多个平台中
②自助拖拽配置
针对可视化图表,由前端实现拖拽效果,用户在前端的所有拖拽和配置信息构建成一个 Json 形式的 Config 中,传到后端存储;打开可视化图表时前端获取 Json 形式的 Config ,解析后渲染展示。
自由拖拽实际上是降低了图表配置门槛,提升了配置效率。原报表系统 V2 配置步骤繁杂,大部分还是由数据开发同学配置的,开发工期长。为提升整体效率,首先将此模块抽象成四部分,存储/引擎、数据模型、可视化图表、看板/大屏,上一节已详细介绍过。
其次明确分工,数据开发同学主要做的事情是,根据需求场景将数据引入到合适的存储/引擎中,根据需求内容抽象合理的数据模型,剩下的配置可视化图表和看板皆由产品、运营等自助拖拽完成。
③业务自主管理
各业务整合后面对的用户涉及全司全业务,各业务对报表在组织和权限管理方面差异很大,希望能够独立自主管理,因此我们加入了 BU 的概念,按 BU 从逻辑上完全隔离开,包括导入后的存储和引擎、数据模型、可视化图表、数据看板,以及在权限系统中所有相关的资源。
④亮点功能
作为数据报表系统,除了常规的功能例如看板/大屏、上卷下钻、同环比等之外,还重点支持了以下几个重要的功能点。
多指标计算
对于已有指标 PV、UV,需要二次计算 PV/UV 得出人均浏览次数这种新指标。
监控告警
针对某个特定(或一批)维度,对任意多个指标设置组合规则进行报警,支持发送报警信息到 Qunar 内部即时通讯 QTalk 和企业微信。
血缘信息
用户在看板中可对某个指标或某个图表,查看上游的血缘信息,包括底表生产信息、底表质检信息、底表接入信息,做到了血缘信息一目了然,提升了数据可信度。数据有问题也方便定位,提升了问题解决效率。
2.3.3 OLAP模块
①需求场景
基于原实时 OLAP 模块的升级,以酒店 CRM 系统数据部分为例。每逢节日做活动,运营、销售、管理层等角色的用户,需要在活动期间实时分析所负责酒店在各种维度下的各种数据指标,以便做策略调整和决策。
具体形式如上所示(截取了部分),针对酒店用户任意勾选二十多个维度、近百个指标,要求 3 秒内出结果展示图表。通过对需求的详细分析归纳,得出以下技术挑战点:
任意个数的维度聚合计算 UV 指标,要保证精确去重,所以不能预聚合,只能将数据存成明细,即时查询;
酒店维度表通常思路可以做成大宽表的方式提前关联上,例如酒店所在城市,但对于动态信息字段例如 HOS 分会经常变化,需求是得根据查询时间段拿最后一天的酒店 HOS 分,只能在查询时即时关联维度表;
需要根据维度指标任意排列组合得去查询,而且要求 3 秒内出结果,但整体数据量级在百亿。
②引擎选型
引擎选型调研 ES、Presto、Kylin 在前面对比过结论是不适用当前场景,本次选型主要对比了在用的 Apache Druid、Impala 和新增的 Apache Doris、ClickHouse。
Apache Druid 对实时数据支持良好,基于数据预聚合模型查询性能强,但当前场景用户的查询灵活度很高,不得不把数据存成明细,而且需要计算 UV 这种精确去重指标以及不得不关联维表查询,因此 Druid 不是最好的选择,后续也没必要参与做实际的性能测试了;
Impala 对于我们来讲最大的好处在于可与离线数仓的现有数据无缝集成,不需要导入操作,所以查询性能受制于底层存储系统 HDFS ,尤其对于复杂场景的复杂 SQL 性能下降严重,在性能上不达标;
Apache Doris 相对 Druid 和 Impala 比较适合,支持 MySQL 访问协议 ,也支持高并发和高吞吐查询且响应及时,但在当前场景需要百亿级别的数据量下性能相对ClickHouse稍有逊色,另外其相对 ClickHouse 社区不够活跃成熟。
对 Impala、Doris、ClickHouse 三种引擎做 Benchmark ,保证相同的数据表(需求相关的真实数据和量级)和相同的 SQL(按需求实际需要编写),在各个引擎上做了简单的测试(Impala 用 Parquet,ClickHouse 用 MergeTree 表引擎),查询多次取均值结果如下:
通过直观的性能对比结果,ClickHouse 的查询性能表现很好,另外实际测试发现随着查询指标数量的增多对 ClickHouse 的性能影响也并不是很大,再结合我们的实际场景需求(主宽表查询,带小表 Join )、硬件条件、开发成本以及业界经验综合对比,ClickHouse 成为了不错的选择。
③架构设计
数据写入
离线数据通过导数平台的 Waterdrop 从 Hive 中导入ClickHouse;实时数据通过导数平台 ClickHouse 的 Kafka Engine从Kafka 中实时消费,再由物化视图将数据实时写入 MergeTree 的单机表,然后基于此建分布式表。
数据读取
用户在页面上任意勾选想要看的维度和指标,提交查询到数据查询服务,然后解析、拼装查询 SQL ,提交到 ClickHouse 执行 SQL ,最后拿到结果数据返回到前端页面展示成图表。
④场景应用和优化
整个集群部署如上图,访问入口由 Nginx 做负载均衡, CHProxy 代理用于管理集群用户、限制并发、设置请求超时等,而集群的大部分分布式功能,则需要通过 ZooKeeper 来完成。结合 CRM 项目本身诉求以及性能要求设计了两种表,整体原则是充分利用 ClickHouse 的单机计算性能强的优势。
第一种分布式表,通常用来存储指标数据和关联用的维度字段(如日期及酒店维度字段加到订单流量数据),这种表通常数据量很大( TB 甚至 PB 级别),需要用多台机器分散存储。分布式表需要设置 Sharding Key 来决定,由于涉及到查询优化,Sharding Key 最好是对应场景中出现频率最高的查询维度(比如日期),这样能够保证 Group By 的时候同一组维度数据一定在同一台物理机上,然后通过修改配置 distributed_group_by_no_merge=1 将所有的聚合转成本地操作,避免了额外的网络开销,提升查询性能。
第二种单机表,通常用来存储非静态的维表,这类维表包含随时间更新的维度(比如酒店星级,HOS 分等),需要在查询的时候取维表数据和主表进行 Join 操作。通过设置一表多备的方式,我们让每一台机器都持有全量且一致的维表数据,这样在Join的时候就可以将 Shuffle Join 优化成 Local Join (因为每一个 Join Key 对应的右表全量数据一定都在本地)来提升查询的整体性能。
2.3.4 标准化指标
基于 OneData 方法论,数仓建模通过指标系统最终产出的是标准化指标,定义和统一口径,数仓同学为标准化指标数据负责。QBI 各个模块从指标系统中获取标准化数据,或分析或展示,以保障所有人看到同一个指标时数据是一致的,从根源上进一步提升了数据可信度。具体关系的细节如下图所示。
①数据分析场景
数据分析模块引入指标系统管理的 DIM 表、DWD 表明细数据,获取指标系统构建的原子指标、复合指标、派生指标信息,用户在进行事件分析时可自由选择来自指标系统的标准化指标,实际查询相应底层的明细表进行分析,使用效果如下图所示。
②数据报表场景
指标系统产出的标准化ADS表,通过导数平台导入数据报表模块,然后根据指标系统里定义的维度指标自动生成数据模型,基于此可实现自由拖拽可视化报表配置看板,相反在看板的图表里可以查看底表和指标的来源信息,使用效果如下图所示。
2.3.5 互联互通
QBI 已形成多个模块的全场景数据消费形态,但模块之间并不是割裂的反而是互联互通的,而且关系非常紧密,围绕标准化的指标系统为核心如下图所示。
数据分析模块,基于指标系统产出的明细表以及派生、复合、原子指标进行深入探索分析,分析的固化结果可以保存到数据看板模块
数据报表模块,展示指标系统产出的业务指标数据,从数据分析模块保存来的看板可回到数据分析模块中继续探索分析
即席查询模块,可以直接查询通过指标系统建模产出的数仓表,SQL 结果可以直接发邮件报表,亦可固化保存至数据看板模块
QBI 各个模块均可从指标系统获取标准化数据,相反也可回溯到指标系统中查看指标元信息。
三、QBI总结
QBI 目前服务于 Qunar 全司十几条业务线,整体 MAU 三千,现已形成较为完善的产品矩阵,包括以下场景:
即席查询模块,适用于自由编写 SQL 跑数;日执行次数五千,平均执行时长一分钟内。
邮件报表模块,适用于自由编写 SQL 自助发邮件报表,简单的数据需求不必再提可完全自助完成;
OLAP 模块,适用于针对某业务数据维度指标,自由勾选进行多维分析并即时响应,目前支撑了百亿明细数据,维度指标上百个,查询 P99 在 2 秒内。
数据分析模块,适用于对数据自由探索,例如上卷下钻探查深入分析,查询 P95 在 8 秒内;
数据报表模块,适用于数据丰富的图表展示,看日常业务指标。MAU 两千,可视化图表 1万+ ,展示数据 P90 在 1 秒内。
四、总结
该文章讲述了去哪儿网的BI平台的建设历程,建设的每个阶段的内容都是非常清晰和通俗易懂,可以为建设数据平台或者BI平台提供有力的建设方案参考。
参考资料:
边栏推荐
- 在web页面播放rtsp流视频(webrtc)
- Redis多线程与ACL
- numpy.tile()
- [intra group questions semester summary] some reference questions for beginners
- Unicloud cloud development obtains applet user openid
- Machine learning 07: Interpretation of PCA and its sklearn source code
- Getting to know concurrency problems
- Summary of JVM interview focus (II) -- garbage collector (GC) and memory allocation strategy
- Status mode, body can change at will
- The difference between overload method and override method
猜你喜欢
5 minutes to learn regular expressions
通俗易懂的从IDE说起,再谈谈小程序IDE
MySQL-09
5分钟包你学会正则表达式
The purpose of writing programs is to solve problems
Record how to modify the control across threads
Redis底层数据结构
421- binary tree (226. reversed binary tree, 101. symmetric binary tree, 104. maximum depth of binary tree, 222. number of nodes of complete binary tree)
Solve the problem that Cmdr cannot use find command under win10
MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
随机推荐
数据可视化实战:数据可视化
How to use the tablet as the second extended screen of the PC
REUSE_ALV_GRID_DISPLAY 事件实现(DATA_CHANGED)
Written before father's Day
机器学习 07:PCA 及其 sklearn 源码解读
Implementation of third-party wechat authorized login for applet
canal部署、原理和使用介绍
Kolla ansible deploy openstack Yoga version
跨域的五种解决方案
Consul service registration and discovery
"= =" difference from "equals"
numpy. random. choice
Multi thread synchronous downloading of network pictures
组合模式、透明方式和安全方式
SQL Server 函数
通俗易懂的从IDE说起,再谈谈小程序IDE
力扣 875. 爱吃香蕉的珂珂
MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
Force buckle 875 Coco, who likes bananas
Feelings of virtual project failure