我在ad tech工作,我们当前的基础架构使用MySQL来存储点击和转换日志。到目前为止,MySQL对我们运行针对点击数据的即席查询非常有用。 我们正在考虑切换到Cassandra,因为我们在高峰时段收到巨大的流量峰值。不仅如此,我们正在以非常快的速度增长,我们偶尔每秒获得大约500-1000次点击(持续时间延长,有时持续20-30分钟)。 我一直是可用的选择,到目前为止,我的研究让我相信在写性能方面没有什么比Cassandra更好。 我目前正在创建用于存储点击次数的数据模型。 任何点击的主要组成部分如下:
现在,我需要支持以下查询:
1. SELECT * FROM clicks WHERE campaign_id=?
2. SELECT * FROM clicks WHERE campaign_id=? AND date_time>=? AND date_time <=?
3. SELECT * FROM clicks WHERE campaign_id=? AND pub_id=? AND AND date_time>=? AND date_time <=? AND event_code=?
等 这很简单,可以使用MySQL,之后我只是从csv文件中获取这些查询的所有数据。 但是,如果我要根据第一个查询对表进行建模,这意味着我需要在Cassandra中创建一个表,如下所示:
CREATE TABLE clicks_by_campaign(
camp_id int,
pub_id int,
date_time timestamp,
creative_id int,
event_code int,
//other fields like ip, user agent ,device etc,
PRIMARY KEY(camp_id,pub_id,date_time,event_code,creative_id))
但是有些广告系列可以拥有数百万行。例如,我们的广告系列具有特定ID,例如id = 3,其点击次数超过700万次。 这不会产生宽行问题吗?据我所知,所有这些活动数据都将作为一个分区存储在一台物理机器上。我的想法在这里是正确的还是我错过了什么?请注意,还必须支持其他查询。例如,我可能必须共享特定发布者的点击日志(与广告系列ID无关)。在这种情况下,查询将如下所示:
SELECT * FROM clicks_by_publisher WHERE pub_id=?
这显然意味着我必须以'clicks_by_publisher'等名称创建另一个表。
我还想指出,我将使用Apache Flink在1分钟的时间窗口内分析,汇总和分组点击信息。这些结果将进一步存储到MySQL中,以尽可能多地支持即席查询。
无论如何,如果有人指出我正确的方向,我将不胜感激。 我还可以使用其他策略吗?我错过了什么吗? 谢谢:))
答案 0 :(得分:1)
您有几个选择。三,我觉得我可以形容。第一个是按如下方式指定列
campaign_id = PRIMARY_KEY
event_code = CLUSTER_KEY
date_time = CLUSTER_KEY
可以在群集密钥上运行大于或等于查询。您的查询将会运行。
你说这会为每个广告系列ID创建一个分区。要解决存储在一台物理计算机上的行,您可以创建一个不同的表,将活动ID与点击表中的行ID相关联。这会减少存储在一台机器上的整体数据。
另一个解决方案是为每个广告系列ID添加一个机器ID。这会平均分配每台机器之间的行数。这意味着为每个查询创建一个前缀为每个机器ID的查询,但允许增长。
这会导致spark。 Spark将处理在多台机器上运行您的查询并自动连接结果,基本上按照上面描述的方式执行,而不会产生开发开销。
我自己与Cassandra合作,我选择了第一种和第二种解决方案的组合,因为它符合我正在使用的数据结构。请记住,Cassandra在写入时非常有效,所以不要过于保守地创建表来帮助过滤查询,并且更加稀疏地存储数据。
通过以日期为前缀的广告系列ID的哈希值来存储点击次数可能对您有用。 编辑:除非禁用,否则Cassandra将使用Murmur3算法自动哈希主键。
答案 1 :(得分:1)
要模拟快速读取和分布式权限的要求,请使用下表定义 -
CREATE TABLE clicks_by_campaign(
camp_id int,
createdon bigint,
pub_id int,
creative_id int,
event_code int,
//other fields like ip, user agent ,device etc,
PRIMARY KEY((camp_id,createdon),event_code))
这有助于在分区之间均匀分布数据。这也将解决我们的第二和第三个问题 -
2. SELECT * FROM clicks WHERE campaign_id=? AND date_time>=? AND date_time <=?
Query will be -
SELECT * FROM clicks_by_campaign WHERE token(camp_id, createdon) > token(100, '1111111111111') AND token(camp_id, createdon) <= token(100, '22222222222222')
3. SELECT * FROM clicks WHERE campaign_id=? AND pub_id=? AND AND date_time>=? AND date_time <=? AND event_code=?
The query will be -
SELECT * FROM clicks_by_campaign WHERE token(camp_id, createdon) > token(100, '1111111111111') AND token(camp_id, createdon) <= token(100, '22222222222222') AND event_code=10
第一次查询 -
1. SELECT * FROM clicks WHERE campaign_id=?
这实际上是cassandra的反模式。我会做什么,分批处理活动数据,每小时 - 每周 - 每周 - 每年。再次考虑广告系列ID,我们是否必须一次处理所有数据。 “clicks_by_publisher”也是如此。
编辑1
Could you elaborate on what you mean by 'token' ?
Cassandra使用分区键对行进行分区。在上面的表定义中,我们结合了camp_id和createdon值(camp_id并创建了类似于RDBMS中的composit主键)来形成分区键。 cassandra分区器计算组合camp_id和createdon的哈希值,并确定该行所在的分区。要检索相同的行,分区程序需要重新计算哈希值。函数toke(),就是这样。
时间戳表示发生点击事件的时间,此值以毫秒为单位。使用createdon(long long)将有助于在分区之间均匀分布行。
例如对于插入语句
1. INSERT INTO clicks_by_campaign (camp_id,createdon ,....) values 100,1111111111111,......) the calculated hash, lets say 111 (combining values 100,1111111111111 ) -- this will go in partition 1
2. INSERT INTO clicks_by_campaign (camp_id,createdon ,....) values (100,2222222222222,......) the calculated hash, lets say 222 (combining values 100,2222222222222 ) -- this will go in partition 2
Java具有将日期转换为毫秒的API。以毫秒为单位的日期可以使用任何时区转换为任何格式。
事实上,您的用例是设计时间序列数据模型的合适人选。