Case Study - Analytics as a Service App Using Multi Tenant Redshift#
Keywords: AWS, Amazon, Redshift, Multi Tenant
Tags: Case Study Big Data Analytics Data Warehouse Redshift
Background#
Rapid7 InsightVM 是一个漏洞扫描产品. 它能扫描你在 AWS 云上的资源的漏洞, 并生成实时 (数据从产生到显示到面板上的延迟不超过 5-10 分钟) 监控面板, 报表数据, 以及列出漏洞的详情以及修复方法.
它在全球有 5000 多家企业客户. Rapid7 主要使用 Redshift 来储存扫描数据. 平均下来, 每天会产生 2.5 Trillion 行数据, 大约压缩后有 50 GB. 它们的客户在各自的 SAAS 平台上只能看到自己的数据. 这是一个典型的多租户的 Use Case. 此外, 它们有些大客户还有将多个 Team 拥有的不同的 Rapid7 账户下的数据合并到一个 Dashboard 上的需求, 要求能对账户的排列组合进行灵活的配置.
目前 Rapid7 的 Redshift 是用了一个 database, 并为每个客户的表都加了客户名的前缀, 每个客户有 10 张表. 现在集群遇到了一些问题. 例如:
数据延迟随着客户的增加以及数据量的增加越来越慢了, 已经经常出现超过 10 分钟的数据延迟的情况. 在随机出现的 Peak 时候会更慢 (超过 30 分钟), 而 Peak 的时候恰恰是 Security 更容易出问题的时候, 这样的数据延迟会影响它们的客户信任.
它们的客户群还在快速增长, 而目前 5000 个客户已经占用了 5000 * 10 张表了. 而 Redshift 一个 Cluster 最多有 200000 张表 (hard limit), 它们担心现有架构无法支撑未来的增长.
它们客户要求的能对账户的排列组合进行灵活的配置这个需求 Rapid7 在现有架构上不知道如何实现. 这样会导致 Rapid7 的客户需求无法被满足.
所以, 客户寻求资深 Redshift 架构师来帮助他们解决这些问题.
Technical Challenge#
以上的问题可以被总结为以下几个技术问题:
现在 5000 个企业客户总共平均每秒钟产生 30 million 行数据, 如何设计表结构以应付高频率大量的写入. 总体的扫描数据从被生成出来到可以再 Redshift 中被查询的延迟要在 5 - 10 分钟. 这里还有个情况根据客户的 App 遇到的随机事件写入的时候会有 peak, 并且 peak 的时间段是不确定的, 但是 peak 时的写入量会是平时的 10 倍左右.
对于 95% 的查询, 响应时间要在 5 秒以内. 因为 95% 的查询都是在 Dashboard 上展示指标. 而对于另外 5% 可能耗时较长的查询, 也要能在可接受的时间内完成.
能满足每秒钟 15 个并行查询, 今后的要求可能会更高. 因为目前 5000 个用户平均每 5 分钟查询一次. 这也就是 5000 / 300 ~= 17 个并行查询, 实际没有这么多.
对于不同的用户要能做到数据隔离. 不能允许一个用户在看自己的 dashboard 的时候看到了其他用户的数据的情况.
有的大客户有多个账号, 它们想要能对不同的账号灵活的排列组合并将数据汇总.
How do I solve Technical Challenge#
- 首先经过我的分析, 我认为所有的需求的核心是 Data Modeling. 这是实现数据隔离以及高效查询的关键. 现有的基于分表的方式不是长久之计. 于是我调研了 Multi tenant 用例的常用数据模型, Pool Model, Silo Model 和 Bridge Model. 这里我简单解释一下, pool model 就是将不同 tenant 的数据放在一个 table 里, 用一个字段 tenant 来标记 owner. model silo 则是给每个 tenant 一个单独的 database, 也就是不同 tenant 在物理意义上的隔离. bridge model 则是介于两者之间, 所有的 tenant 都在一个 database 下, 但给每个 tenant 一个单独的 schema, 每个 schema 中的表的结构都类似. 我详细比较了几种方法:
首先我们要满足数据隔离, 因为这是没得商量的需求, 所以我们从隔离级别最高的 silo 开始评估.
首先排除 silo, 因为 redshift 只支持最多 60 个 database (hard limit), 这样算下来就要 5000 / 60 ~= 84 个 redshift cluster, 这会让运维变得非常麻烦.
其次考虑了 bridge model, Redshift 支持一个 database 9900 个 schema (hard limit), 也就是最多支持 9900 个 tenant, 未来可能会有问题. 好处是这样每个 tenant 的表都不大, 查询的速度比较快. 但是在写入数据的时候每个 tenant 需要单独 commit, 这样导致写入过于频繁, 大量占用资源, 会影响其他 tenant 的查询.
最后考虑了 pool model, 这样做可以支持几乎无限数量的 tenant. 并且写入数据时可以在写入前把多个 tenant 的数据 merge 成大批量数据进行 bulk insert, 写入性能较高. 但是需要对大表进行合理的设计才能保证查询性能.
最终我决定使用 pool model, 这样能满足不断增长的 tenant 数量, 以及写入性能的要求. 我有信心能通过合理的设计来保证查询性能.
- 为了保证数据写入性能, 我做了以下几点:
我认为要最大化利用 pool model 的优势, 来将来自于不同 tenant 的写入聚合成较大的数据块再写入. 我用 AWS Lambda 对进来的数据进行扫描, 然后根据数据文件的大小将大大小小的数据分组, 确保每一组的总大小差不多. 然后为每一组创建一个 manifest 文件然后用 COPY Command 来并行写入数据 (一个 manifest 中的多个文件是并行写入的), 这样就大幅提高了写入吞吐量.
我发现 Rapid7 并没有对它们的数据进行极致的优化, 我认为还有很多的优化空间. 我为每个字段选择了最优的压缩算法, 例如我对 tenant_id 字段用整数对实际值进行了映射 (Byte-dict 只支持 256 种以下的情况, 而我们有 5000+ tenant), 对 event_time 字段使用了 Delta compression, 对一些 measurement 字段使用了 run length 来压缩, 因为这些字段在时间序列上很长一段时间都保持不变.
- 为了保证查询性能, 我调研了如何通过选择合理的 sort key. 因为在查询数据的时候, 通常在 where 中会包含两个字段
tenant_id = 'a1b2c3'
和event_time BETWEEN ...
我比较了以下四种方案, 并且测量了 benchmark, 最终决定使用(tenant_id, event_time)
方案. 因为 tenant_id 是第一个 filter 优先级, 并且很可能会用于 JOIN, 所以放在第一位. event_time 也是一个常用的 filter, 放在第二位. 这样可以保证查询性能. tenant_id
event_time
(tenant_id, event_time)
(event_time, tenant_id)
- 为了保证查询性能, 我调研了如何通过选择合理的 sort key. 因为在查询数据的时候, 通常在 where 中会包含两个字段
以上的设计已经能满足 95% 的查询在 5 秒以内完成, 而对于 5% 的查询, 我启用了 Concurrency Scaling (是 Redshift 的一个 feature) 使得能够在需要额外计算资源做复杂度较高的查询时自动启用额外计算资源来保证任务能够尽快完成. 为此我帮客户做了 Workload Management 的配置, 定义了什么 Query 要交给 Concurrency Scaling 来执行. 并且 Concurrency Scaling 还能提高繁忙时期的 COPY Command 的写入性能.
- 为了进一步的提升查询性能, 我还提出了一些额外方案, 例如:
用 materialized view table 来提升查询性能, 缓存中间态的数据表.
为 dashboard 的查询设计了智能化的缓存, 并对 redshift 进行了优化从而能检测到何时需要 expire 缓存, 提高了一些常用操作的响应时间, 例如刷新.
为了保证用户数据隔离, 我开发了一个 middleware 用于创建包含有合适的
WHERE tenant_id = 'a1b2c3'
的查询, 并且使用了 ORM 框架以预防 SQL 注入. 在执行查询之前, 使用 SAAS 平台登录的 token 获取对应的 tenant_id 并最终生成 SQL 进行查询.为了解决拥有多个账号的用户想要灵活的对账号数据进行聚合的需求, 我设计了一张额外的表 tenant_group 用于记录 tenant_id 和 tenant_group_id 之间的对应关系, 用户只需要把想要聚合的账号给放到一个 tenant_group 下即可. 默认情况下每个 tenant_id 都会有一个自动创建的独立的 tenant_group. 然后进行一些简单的 JOIN 就能够实现基于 tenant_group 的数据隔离.
Non Technical Challenge#
跟我对接的客户的头是 chief product officer (CPO), 这个 CPO 刚上岗 2 个月, 他只能从产品的角度理解遇到的困难的表象, 但由于对后台系统不够熟悉, 无法给我提供更精确的信息帮我找到底层原因.
我在设计方案的时候, 客户无法给我权限直接访问它们的 Production Redshift Cluster, 这样我就很难验证我的设计能在这么高的压力下依然能正常工作.
How do I solve None Technical Challenge#
我用选择题的方式提问引导 CPO, 在没有足够信息的情况下, 让他知道可能有哪些原因. 然后引导他帮我找到合适的人员对接. 我在对接之后将搜集到的信息和结论用可视化和浅显易懂的方式呈现给他. 这样也建立了客户信任.
虽然出于数据安全的原因, 我无法利用他们的生产数据. 但我用技术手段模拟了和他们生产数据相同的流量 (用 StepFunction orchestrator + Lambda function worker). 由于我已经获取了他们的信任, 他们也增加了 budget, 供我创建更多的资源来模拟生产数据.
Result#
Rapid7 Result:
总体的数据延迟重新回到了 5-10 分钟以内. 并且该架构设计能确保在数据量是现有的 10 倍的情况下依然能保证 5-10 分钟的数据延迟.
总体查询性能更快了, 能允许更高的并发量, 用户体验更好了.
可以轻松应对无法预测的 peak 写入流量和查询请求, 减少了 complain ticket 的数量, 提高了 customer satisfaction.
尽快满足了大客户的需求, 加强了大客户对他们的信任, 锁定了非常客观的利润.
BMT Result:
这个项目在 3 个月内为 BMT 带来了 $250,000 的收入.
收获了对方 Rapid7 CPO 的信任. 他们还在告诉发展期, 需要发布更多创新的产品. 他们的新 CPO 在新加入的这段时期也获得了很多来自于 BMT 的帮助, 他们希望将 BMT 作为他们的长期合作伙伴.
Reference#
How Rapid7 built multi-tenant analytics with Amazon Redshift using near-real-time datasets: Rapid7公司是如何实现 Multi tenant 近实时数据分析的.
Implementing multi-tenant patterns in Amazon Redshift using data sharing: 详细介绍了如何使用 Redshift Data Sharing 实现 Multi tenant.