本文主要介绍 Hulu 用户分析平台使用的 OLAP 引擎 Nesto(Nested Store), 是一个提供近实时数据导入, 嵌套结构 TB 级数据量秒级查询延迟的分布式 OLAP 解决方案, 包括一个交互式查询引擎和数据处理基础设施
1. 项目背景
Nesto 起源于用户分析团队, 业务上需要一个面向用户分析型的产品, 提供任意维度的 Ad-Hoc 交互式查询导出数据, 供运营产品第三方数据公司使用
一个典型场景是: 导出 2018 年 1 月看过冰与火之歌第 7 季第 7 集 (S7E7) 超过 5 次的新注册用户, 包括用户名 email 两个域, 用于发送营销邮件
2. 数据平台 pipeline
在正式介绍 Nesto 之前, 有必要先介绍其产生的背景, 所以先介绍数据平台 pipeline
如下图所示, 用户分析平台的最核心的资产是一套 pipeline, 通过整合公司内多个团队的数据, 使用 HBase 集中存储起来, 提供一个 UI Portal 让用户简单的描述需求, 把需求存储于 metadata db 中, 然后定期运行一个 Spark Job 去 scan HBase, 进行批量的计算, 最终把有价值的数据服务出去, 例如作为用户标签服务的上游方等
先说存储, 用户维度数据使用 HBase 存储的原因在于:
1 大宽表 KV 模型可以看做一个多维稀疏嵌套的 sorted map
2 横向扩展能力按照 userId 反转作为 row key, 可以自动 sharding 分区, 横向扩展到 PB 级别
3 随机读写能力强可进行随机的查询, 以及数据修复
4 高吞吐的导入能力异步批量写入速度快, LSM 结构保证写入高吞吐, 还可以使用 bulk load 绕过 Region Server 导入
5 可以和大数据技术栈无缝融合, 包括 Hadoop/Spark 等
HBase 中一行就是一个用户的全部信息, 有些值是上游直接使用, 有些需要二次加工, 逻辑上可以看做下面的结构
- {
- "uid": 100,
- "raw_attributes": {
- "email": {"value": "jack@hulu.com"},
- "gender": {"value": "m"},
- "signup_date": {"value": 1507359323},
- "age": {"value": 49}
- },
- "behaviors": {
- "watch": [
- {
- "cid": 9800,
- "duc": "Living Room",
- "dlc": "CONSOLE",
- "seventid": 800,
- "genre": ["Documentaries"],
- "video_type": "feature_film",
- "timestamp": 1273774176
- },
- {
- "cid": 9801,
- "duc": "Living Room",
- "dlc": "CONSOLE",
- "sid": 801,
- "genre": ["Animation and Cartoons", "Family","Kids"],
- "video_type": "feature_film",
- "timestamp": 1373774176
- },
- {
- "cid": 9802,
- "duc": "Computer",
- "dlc": "EMBED",
- "sid": 802,
- "genre": ["News and Information"],
- "video_type": "clip",
- "timestamp": 1473774176
- }
- ]
- }
- }
uid 是 rowkey,raw_attributesbehaviors 当做 column family,birth_dateage 当做 column qualifer, 其中 age 是经过 birth_date 二次计算而来的, timestamp 可以看做业务有意义的时间信息, 值就是存储在 Hbase 中的 Cell Value, 每一个 Cell 都是一个带有 schema 的 Proto, 便于高效序列化与反序列化 watch 是观看行为, 是嵌套的, 利用 HBase 的多版本实现, 这样一个用户的多个观看行为就封装到了一行中
UI Portal 可以方便的通过拖拽生成一个 DSL 下图展示的用户输入某个过滤条件的截图, 对应的查询场景是: 导出 2018 年 1 月看过冰与火之歌第 7 季第 7 集 (S7E7) 超过 5 次的新注册用户
目前我们支持使用 Json 或者 SQL++ , 来表示 DSLJson 的 DSL 见最后的附录, 为了表达清楚只介绍 SQL DSLSQL++ 是一个开源的嵌套数据查询 SQL, 其他类似的还是有 Drill 等, 也支持嵌套数据查询, 他们功能类似, 语法会有所不同例如典型场景生成的嵌套查询 SQL 如下:
- SELECT reg.raw_attributes.username, reg.raw_attributes.email
- FROM RegUsers AS reg
- WHERE
- ARRAY_COUNT(
- (SELECT w.cid
- FROM reg.behaviors AS b
- UNNEST b.watch AS w
- WHERE w.cid = 9800
- AND w.ts>= '2018-01-01 00:00:00' AND w.ts <'2018-02-01 00:00:00')
- )> 5
- AND
- reg.raw_attributes.reg_at>= '2018-01-01 00:00:00' AND reg.raw_attributes.reg_at <'2018-02-01 00:00:00';
这个 SQL 中暂时没有包含 GROUP-BY 聚合处理, 但是使用了 ARRAY_COUNT 这个针对嵌套类型的 UDF, 子查询用 UNNEST 语句 flatten 下层的 watch 行为, 关于语法的详细描述, 可以参考 SQL++
原始的 pipeline 依赖一个自研的 predicate lib, 处理流程如下, 将 HBase 中某一行转化为一个嵌套的数据结构, 在这个数据结构上做 predicate 依托分布式计算引擎, 例如 Spark, 就可以扫描 HBase 中存储的所有用户, apply 一个 predicate, 符合条件的即输出结果列目前 predicate lib 实现了很多的 UDF, 包括时间范围 If-else 逻辑处理等
Array[User] -> Predicate(SQL++ or JSON DSL) -> Array[Field1, Field2 Field N]
但是 pipeline 本身不具备 OLAP 的能力, 不能实现聚合排序 TOP-K 等算子
3. 数据规模
目前用户数据规模如下,
1HBase 1500 Regions, 占 HDFS 20T (一副本 LZO)
2300 + 列, 其中有 50 多是嵌套的结构, 例如观看行为
31 亿 + 用户数据
4 历史全量近 1000 亿观看行为, 最近一年近 300 亿次
4. Nesto 的诞生
为了满足 OLAP 的需求, 包括
1 支持 filter, projection 和 aggregation 和自定义 UDF
2Ad-hoc 查询, 普通列响应时间秒级, 嵌套列小于百 s
3 数据从进入 OLAP 到能够被查询到, 延迟要在小时级别
我们对比了如下开源方案,
1ROLAP
类似 SparkSQLPrestoImpala 等, 它们都必须把数据抽象为关系型的表, 可以使用表达丰富的 SQL, 不存在数据冗余, 在实际运行期间往往会经过 SQL 词法解析语法解析逻辑执行计划生成和优化, 再到物理执行计划的生成和优化, 会存在数据 shufflejoin 这与现有的用户分析平台已存数据模型大宽表, 不一致, 需要经过 ETL 做数据转换另外我们的另一个目标支持近实时的数据导入, 而这些方案的 OLAP 目前都不支持
2MOLAP
类似 DruidKylin 百度 Palo 等, 他们都支持多维查询, 通过预聚合的方式来提升查询性能, 但是需要抽象出维度列指标列, 某个维度分区等, 同时可能会由于数据立方体的存在造成数据膨胀过大同样数据模型和大宽表不一致另外, 用户分析平台往往涉及一个用户所有行为数据, 查询请求往往就是要查询若干月, 甚至若干年之前的, 涉及大量 fact 数据的全表 scan, 这也不能很好的 match 这种物化视图或者上卷表的模式
5. Nesto 的基础
Nesto 的实现依赖于一些已有的技术和理论
1 存储模型采用嵌套模型, 非关系型
2 存储格式列式存储, 对于 OLAP, 可以跳过不符合条件的数据, 降低 IO 数据量, 加大磁盘吞吐通过压缩编码可以降低磁盘存储空间只读取需要的列, 甚至可以支持向量运算, 能够获取更好的扫描性能 Nesto 采用 Parquet 作为存储格式, 是 Google Dremel 的开源实现 Parquet 对嵌套数据结构实现了打平和重构算法, 实现了按行分割(形成 row group), 按列存储(由多个 page 组成), 对列数据引入更具针对性的编码和压缩方案, 来降低存储代价, 提升 IO 和计算性能
3MPP 架构大规模并行处理架构, 可以支持查询的横向扩展, 为海量数据查询提供高性能解决方案, 实际上 Nesto 借鉴了 Presto 一个 Parquet 文件是 splittable 的, 因此利用 DAC 分治的思想, 把大问题划分为小问题, 分布式并行解决
4RPC 选型分布式系统的 RPC 通信是基础, Presto 大量采用 RESTFul API 解决, 而 Nesto 选择使用 Thrift 进行封装解决, 提供基于 NIO 全双工非阻塞 I/O 的通信模型, 通过 Reactor 模式实现线程池和串行无锁化来实现服务端 API 的暴露
5 分布式配置 Nesto 中的表结构存储在分布式配置系统中, 可做到热部署更新
6 高可用保证使用 YARN 管理实例, 保证高可用和资源的合理分配使用 Zookeeper 做集群节点变更的通知与分发
7 海量数据近实时查询支持借鉴 Google MESA 的思想, 关于 MESA 的模型, 请参考这篇文章了解 浅谈从 Google Mesa 到百度 PALO
8 其他技术点使用 MySQL 存储已完成任务情况, 使用 web 技术构建管理 Portal 使用 Hadoop 基础设施, 包括 HDFS 存储数据
9 实现语言 Java
6. Nesto 的存储模型
逻辑上, 可以看做一张嵌套的大平表(flat table), 数据按照行存储, 每一行的结构都是嵌套的和第二章提到的 HBase 的模型逻辑上是一致的
物理上, 采用开源列式存储方案, Nesto 选择 Parquet , 它独立于计算框架, 按照 Google Dremel 提到的方案做按行切割, 按列编码压缩一张表对应 1 到 N 个 Parquet 文件
下图是 Parquet 官网 的物理存储图每个 Parquet 文件都是包含若干 row group, 这是做 MPP 的基本分割单元, 一个 MPP 的 sub-task 可以对应 K 个 row group, 一个 row group 包含了若干用户的全部信息, 按照 schema 定义的列, 进行列式存储, 每列包含若干个 page, 每个 page 是最小的编码压缩单元每个列都可以采用自由的编码方式, 例如 run length encodingdict encodingbit packed encoding,delta encoding 等等, 或者他们的组合
用于 MPP 架构的存在, 通常会多增加副本数, 来支持读负载均衡和本地化 locality 查询
表结构不用 DML 描述, 而是使用 Parquet 提供的 proto schema 方式, 目前通过分布式配置中心管理, 通过管理控制台新增表和修改表例如, 在配置中心表 RegUsersAttributes 的 schema 描述如下
7. Nesto 整体架构
Nesto 分为查询引擎和数据处理基础设施两大部分
查询引擎的架构如下
Nesto-portal 客户端, 用于提供基于 web 的查询请求输入和下载结果
Nesto-cli 客户端, 提供命令行交互式的查询
State store 使用 zookeeper 来做集群管理, 进而实现高可用的分布式系统, 任何节点都可以知道整个 Nesto 的拓扑结构
Nesto server 非中心化的设计思想, 类似 Presto , 任意节点分为两种角色, 包括 coordinator 和 worker, 一般来说都是少数的 coordinator 加上大量的 worker 的拓扑每一个部署节点都是一个 Nesto server, 只不过角色有区分目前 nesto-server 均部署在 YARN 上, 做常驻进程, YARN 做高可用保障和资源分配管理
Coordinator 是某一个查询的管理节点, 负责接收客户端的请求, 解析请求, 由于使用大宽表的数据结构, 加上复用 predicate lib, 因此做完词法分析语法分析, 生成 AST 后, 查询计划的生成很简单, 把 filter 的逻辑全部下推到底层的 worker 去执行即可, 只需要做 table 的 split 就可以生成 sub-tasks, 这些 sub-tasks 就是物理执行计划的体现, 所以不存在 stage 或者 fragment 等类似 PrestoImpala 的概念 Coordinator 通过 State store 感知集群的拓扑, 同时和每个 worker 都保持了一个心跳, worker 通过心跳信息上报自己的状态, coordinator 可以进一步了解 worker 的负载和健康状态 Coordinator 通过一定的调度算法, 把 sub-tasks 分发给 worker 去执行, 等待 worker 的结果汇总过来, 如果需要再做一些 aggregation 和 merge 的工作最终, 流式的传输结果给客户端展现或者下载
Worker 接收 coordinator 分发的若干 sub-tasks, 放到线程池中执行(线程池就是槽位 slot), 通过 Parquet 提供的 API, 逐一读取一行的数据, 利用 predicate lib 进行 filter, 通过一个异步的生产者消费模型, 批量处理数据, 然后分批序列化后, 按照 data chunk 的方式, 源源不断的发送回 coordinator 另外, worker 会做一些优化工作, 除了天然的 filter pushdown, 还包括 pre-aggregation,limit pushdown 等等
数据处理基础设施请参考第 9 章节
8. Nesto 的查询引擎
下面针对一个典型的查询执行过程, 进一步展开描述各个组件的工作流程
8.1 State store
主要做高可用, 节点发现上下线通知使用依托 zookeeper, 每个 nesto-server 在启动的时候都会注册自己到 zookeeper 的某个临时节点, 任何想知道集群拓扑的地方, 例如其他 nesto-servernesto-clinesto-portal 都通过订阅 zookeeper 得到集群的拓扑情况当集群发生变化的时候, 可以通过 zookeeper 订阅变化
8.2 Nesto-server 之 Coordinator
API
Nesto server 需要提供两套 API, 一个是客户端与之交互, 另一个是 coordinator 和 worker 通信的 API, 都通过 thrift 进行开发和编写使用 TThreadedSelectorServer 作为通信基础设施, Linux 上使用 I/O 多路复用的 epoll 技术, 同时使用两种线程池, 一个做 accept 连接通信握手技术, 一个做编解码和业务逻辑的处理
解析请求
通过 thrift API 方法提交上来的请求, 针对 JSON 或者 SQL++ DSL 解析, 同时得到抽象语法树 AST, 包含查询表, filter 条件, 表的 schema 信息, 要查询的列, 以及每一列的聚合函数
执行计划生成
由于 Ad-hoc 查询和 MPP 的架构, 因此系统的并发查询能力要做一定的限制, Nesto 可通过参数配置并发执行请求的数量, 在解析请求完毕后, 会把 request 放入带有超时机制的线程池中, 如果查询没有在规定时间内完成, 那么就会取消查询, 并且 revoke 掉所有已经分发的 sub-tasks 线程池中会进行执行计划的生成和后续的处理流程
执行计划的生成分为两步, 第一步把大的表, 也就是 Parquet 文件进行 split, 每个 split 都是一个 sub-task, 这里复用了 parquet-mr 项目中的 ParquetInputFormat, 把一个大的 Parquet 文件 split 为若干个 InputSplit, 对于每一个 split 的大小可以通过参数控制, 也就调整了每个 sub-task 扫描的数据大小, 可以避免 data skew 问题, 例如 1G 一个 split, 这 1G 可以包含多个 Row Group, 而每个 Row Group 可能是 HDFS Block Size, 例如 256MB 第二步, 根据 filter 条件列以及结果列构造新的 schema, 目的是用 Parquet 读取文件的时候需要传入这个 schema, 这样就可以只查询需要的列, 发挥列式存储在 IO 上面的优势
调度分发任务
执行计划生成了本次查询的所有分片信息, 如何调度分片给合适的 worker 去执行, 也就是生成 worker 到多个 Task 的映射, 是调度任务负责的这里可以包含很多策略, 例如可以轮训的 assign task 到每个 node, 也可以按照 HDFS Locality 来进行数据本地化的优化, 还需要综合考虑每个 worker 的负载状况, 把 task 分配给负载较为轻的 worker, 通常负载要考虑的维度, 包括 worker 节点的 CPUIOslot 占用数权重等如下图所示
同样分发任务, 也可以通过线程池来完成, 通过 thrift RPC 调用 worker 的 API 来提交 sub-tasks 给 worker 通过使用响应式编程 (Reactive), 或者带有异步回调机制(例如 Java 中的 Future) 的方式来实现成功和失败的逻辑, 针对失败的分发(例如因为 worker 拒绝或者下线) 可以再分发给别的 worker
另外在分布式系统中不可忽略的因素, 就是长尾效应, 总会存在一些拖后腿的任务, 进而影响整个 query 的查询延迟, 所以 Nesto 还开发了慢请求的检测和重分发机制, 针对 straggler task, 通过一定机制的判断, 最简单的模型就是针对最后 1% 的 sub-tasks, 如果运行时间超过阈值, 则进行 speculative execution, 也可以叫做 duplicate execution, 多分发若干的 sub-tasks 出去, 谁先执行完毕, 就用谁的结果, 进而优化长尾效应
查询执行
当 sub-tasks 都分发出去后, worker 会源源不断通过 thrift RPC 把结果批量的发送回 coordinator, 每一批可以看做是一个 data chunk
coordinator 在内存中维护一个 aggregator 数据结构, 来 merge 所有 worker 返回的 data chunk, 一个 data chunk 就是一个批次的结果, data chunk 使用某种序列化协议(例如 Java 原生或者 Kyro),data chunk 可以支持去重
一个 data chunk 包含了一部分结果列, 例如 select 列为 reg.raw_attributes.username, reg.raw_attributes.email, 则在 coordinator 会追加累积这些数据, 然后再源源不断的推送给客户端
如果结果列包含聚合函数, 例如 GROUP BY cid,COUNT(userid), 那么 worker 会做 pre-aggregation, 把聚合 pushdown 到 worker 执行, 最终给 coordinator 的数据就是聚合后的结果, aggregator 做 combine 即可同样, 常用的 LIMIT pushdown 也是支持的
失败处理
类似 Presto 对于失败的态度就是不太容忍, 对于分发失败的任务, 在超过重试次数后就 failfast 整个 query 对于 worker 返回失败的 data chunk, 包括丢失响应, 或者返回的 data chunk 非法等, 超过一定的重试次数后也 failfast 整个 query
8.3 Nesto-server 之 worker
worker 和 coordinator 类似, 都存在一个 thrift API, 用于接收 coordinator 发送的 sub-tasks 查询请求
worker 充分利用了线程池技术, 池子里就是一些槽位(slot), 每一个 sub-task 会占用一个 slot, 当 slot 占满后就不能执行新的请求了, 从而限制 worker 的计算能力不超负荷
worker 和 coordinator 维持一个心跳, 定期汇报自己的负载信息, 包括 CPUIOslot 占用情况等, 供 coordinator 调度算法使用
和 Presto 类型, 抽象出 connector 的概念, Nesto 的 worker 抽象出了 scanner 的概念, 这是一个可扩展的接口, 目前只支持 Parquet 文件的查询, 后续可以扩展到 CarbonData 等
worker 要做的工作就是根据 Parquet 提供的 client API 读取文件, 解压缩解码文件, 在内存中构造出所有 row group 的行视图, 这个过程是一个流水线式的, 保证尽可能低的使用内存文章最早提到了 pipeline 中已经存在的 predicate lib, 使用这个 lib,apply 到一行, 就可以做 filter, 也就天然实现了 filter pushdown 的功能, 对于符合 filter 条件的行, 取其结果列, 在内部维护一个队列, 批量的构造 data chunk, 序列化后不需要进行 shuffle 落盘, 直接在内存里面, 网络直连的发送回 coordinator 即可这种 filter pushdown 到最底层的方式, 避免了落盘和 JOIN operator, 所以在性能上对于无法剪枝的 scan 型的场景会非常高效具体流程如下图所示
另外, aggregation pushdownlimit pushdown 都是 worker 已经实现的优化
8.4 Portal & Cli
Nesto Portal 提供了 UI, 可以方便 PM 运营人员提交查询, Portal 截图如下例子中使用 JSON DSL 发送请求
Nesto cli 提供了命令行方式, 进行查询, 一次查询的请求如下
- ./nesto-cli -mode i -schema RegUsersAttributes -table_path hdfs://nesto/RegUsersAttributes/sl2-prod-100
- No JAVA command specified AND try TO USE $JAVA_HOME OR just "java" instead.
- _ _ _ _ _
- | \ | | ___ ___| |_ ___ ___| (_)
- | \| |/ _ \/ __| __/ _ \ _____ / __| | |
- | |\ | __/\__ \ || (_) |_____| (__| | |
- |_| \_|\___||___/\__\___/ \___|_|_|
- Welcome TO Nesto-cli. http://test.com/STATUS will be used AS Nesto portal
- Starting...
- Coordinator test.hulu.com:58807 will be used TO serve calls
- TYPE 'help', TYPE 'bye' TO quit> SELECT reg.raw_attributes.username, reg.raw_attributes.email> FROM RegUsers AS reg> WHERE> COUNT(> (SELECT w.cid> FROM reg.behaviors AS b> UNNEST b.watch AS w> WHERE w.cid = 100> AND w.ts>= '2018-01-01 00:00:00' AND w.ts <'2018-02-01 00:00:00')> )> 20> AND> reg.raw_attributes.reg_at>= '2018-01-01 00:00:00' AND reg.raw_attributes.reg_at < '2018-02-01 00:00:00';
- Querying.........
- IF nesto.query.quite SET TO FALSE, visit http://test.com:8090/query.jsp?queryid=query_aaa_60066_106 FOR more info.
- ........
- ScannedCount: 109586, RowCount: 4
- raw_attributes.username.VALUE | raw_attributes.email.VALUE
- ---------------------------------+-------------------------------------
- Jack | jack@abc.com
- Richard | richard@abc.com
- Christy | christy@abc.com
- Jeanette | jeanetteburel@abc.com
- Total records: 4
- Query done. (costs 2 seconds)
- ```
8.5 部署
Nesto 在部署上, 默认采用 YARN 来管理, 以进程常住的形式部署在 YARN Node 节点上, 通过利用 YARN 的编程接口, 开发 ClientAppMaster 等程序, 就可以管理 coordinator 和 worker 常驻进程, 做到高可用保障和合理的资源分配
另外, Nesto 也支持本地 standalone 和 pseudo 伪分布式部署模式, 方便调试和测试
9. Nesto 的数据处理基础设施
前面介绍了 Nesto 的查询引擎, 还包括一个数据处理基础设施
Nesto 支持近实时数据导入, 利用了 Google MESA 提供的数据模型, 由 BaseCumulative delta 和 Singleton delta 组成如下图所示
数据处理基础设施架构图如下
其中 Base 文件定期通过 ETL 任务导出, 由于数据量大(历史全量数据, 1000 亿行 watch 行为, 300 多列), 因此这是一个很重的任务, 而每天变化的数据量又非常小, 因此尽可能低频率的运行在图中体现为 exporter 组件
Delta 文件的生成通过 HBase coprocessor 捕获, 并且发布到 Kafka 中做存储, 多分区, 单分区内数据可保序, 因此单个用户不会存在业务上的乱序影响增量处理程序以小时级的频率生成 delta 在图中体现为 updater 组件
当过多的 delta 存在的时候, 查询引擎会进行更多的随机 I/O, 不利用优化响应延迟, 因此存在一个 job 以若干小时的频率合并 delta 为一个大的 delta, 叫做 cumulative delta 在图中体现为 compactor 组件
Updater 和 compactor 会通过 controller 进行调度运行, 他们都是无状态的, 因此通过 controller 保存和注册生成的 delta, 每个 delta 都要一个 version, 查询引擎在做执行计划生成的时候, 可以访问 controller 查询最优的 query path
在上面几个章节的查询引擎介绍里都简化了处理, 没有考虑增量情况, 便于读者理解
这里要注意, 在查询引擎中使用 delta, 就必须保证和 base 中的 row group 中的行都是 match 的, 这里的任务都会按照某个业务主键, 例如 userid, 来排序文件, 这样在查询引擎内部就可以使用基于最小堆的多路归并排序来做数据的 merge 了
10. 总结和未来计划
目前 Nesto 在 Hulu 内部承担着实时用户数据 OLAP 查询的需求, 供 PM 运营人员使用目前线上仅需部署了 60 个 nesto-server 节点, 最大表的 Parquet 文件大小在 3-4T(单副本), 对于简单列的查询, 在秒级完成, 对于包含 watch 行为的嵌套列, 在百毫秒以内完成
未来 Nesto 的增强点包括使用基于 codegen 技术的 predicate lib(目前内部已完成借助 spark sql catalyst 的改造), 最大化 filter 性能冷热数据区分, 智能表路由, 避免粒度过粗的 full scan 数据拖敏, 更好的安全保障通用化的平台, 支持多租户的使用更好的支持嵌套 SQL 查询
目前 Nesto 由于绑定了内部的 predicate lib, 同时还利用了 Hulu 内部的很多基础实施, 有些实现不是非常的通用, 所以暂时还无法开源出来
总结下, 本文介绍了 Hulu 用户分析平台使用的 OLAP 引擎 Nesto, 它是一个提供近实时数据导入, 嵌套结构 TB 级数据量秒级查询延迟的分布式 OLAP 解决方案, 包括一个交互式查询引擎和数据处理基础设施虽然 Nesto 是一个 in-house 的内部系统, 但是本文还是最大化的展示了一些背后的架构思想实现细节, 以及自研的 tradeoff 和原因, 希望可以给读者一些 OLAP 方面的启发现在的 OLAP 市场百花齐放, 但是其思想和技术都是建立在已经成熟或者前人的基础上的, 通过更好的理解和应用这些思想和技术, 来满足组织和业务的需求, 才是大家的目的, 希望在 OLAP 领域与读者共勉一起学习和进步
附录
1JSON DSL
导出 2018 年 1 月看过冰与火之歌第 7 季第 7 集 (S7E7) 超过 5 次的新注册用户, 保存为 csv, 包括用户名 email 两个域
- {
- "expr":[
- "and",
- ["greater_than",
- [
- "count_list",
- [
- "list_filter",
- "cid",
- ["time_range_filter","in_range",1514764800,1517443199,["get_var","watch"]],
- ["get_function","exist_in"],
- "9800"
- ],
- "cid"
- ],
- 5
- ],
- ["in_range",["get_var","plus_signup_date"],1514764800,1517443199]
- ],
- "results":["reg.raw_attributes.username", "reg.raw_attributes.email"]
- }
来源: http://www.tuicool.com/articles/iABFRjJ