Cassandra二维数据建模

时间:2017-10-22 00:07:20

标签: cassandra bigdata data-modeling cql cassandra-3.0

用例:

对于游戏,我正在收集每场比赛的结果。总是A队对阵B队。每支队伍由5名队员组成,每队都选一名队员,比赛的可能结果是一队赢或输或两队都平局。

我想找出最好的冠军组合我希望根据每支球队所选择的冠军组合创建输赢/平局统计数据。总共有100名冠军球员可以选择。因此,有许多不同的冠军组合可能。

更多(奖励)功能:

  • 我想弄清楚一个组合是如何对另一个特定组合进行的(简而言之:对抗一个非常强大的冠军组合的最佳组合是什么)
  • 随着平衡变化应用于游戏,有可能按特定时间范围选择/过滤统计数据(例如仅过去14天) - 每日精度适合

我的问题:

我想知道根据冠军组合收集统计数据的最佳方法是什么?数据建模将如何?

我的想法:

  1. 在组合中创建所有championId的哈希值,它实际上代表championCombinationId,它是团队使用的冠军组合的唯一标识符。

  2. 创建一个二维表,允许跟踪组合与组合统计。像这样的东西: enter image description here

  3. championId的时间范围(每日日期)和实际combinationId在那里丢失。

    我尝试自己创建一个满足上述要求的模型,但我完全不确定。我也不知道需要指定哪些键。

    CREATE TABLE team_combination_statistics (
      combinationIdA text, // Team A
      combinationIdB text, // Team B
      championIdsA text, // An array of all champion IDs of combination A
      championIdsB text, // An array of all champion IDs of combination B
      trackingTimeFrame text, // A date?
      wins int,
      losses int,
      draws int
    );
    

2 个答案:

答案 0 :(得分:1)

您可以创建一个统计表,其中包含特定日期冠军的游戏统计数据。

CREATE TABLE champion_stats_by_day (
    champion_ids FROZEN<SET<INT>>,
    competing_champion_ids FROZEN<SET<INT>>,
    competition_day DATE,
    win_ratio DECIMAL,
    loss_ratio DECIMAL,
    draw_ratio DECIMAL,
    wins INT,
    draws INT,
    losses INT,
    matches INT,
    PRIMARY KEY(champion_ids, competition_day, competing_champion_ids)
) WITH CLUSTERING ORDER BY(competition_day DESC, competing_champion_ids ASC);

你可以从特定日期开始询问冠军的统计数据,但你必须在客户端进行排序/聚合:

SELECT * FROM champion_stats_by_day WHERE champion_ids = {1,2,3,4} AND competition_day > '2017-10-17';

 champion_ids | competition_day | competing_champion_ids | draw_ratio | draws | loss_ratio | losses | matches | win_ratio | wins
--------------+-----------------+------------------------+------------+-------+------------+--------+---------+-----------+------
 {1, 2, 3, 4} |      2017-11-01 |         {2, 9, 21, 33} |       0.04 |     4 |       0.57 |     48 |      84 |      0.38 |   32
 {1, 2, 3, 4} |      2017-11-01 |         {5, 6, 22, 32} |      0.008 |     2 |       0.55 |    128 |     229 |      0.43 |   99
 {1, 2, 3, 4} |      2017-11-01 |       {12, 21, 33, 55} |       0.04 |     4 |       0.57 |     48 |      84 |      0.38 |   32
 {1, 2, 3, 4} |      2017-10-29 |         {3, 8, 21, 42} |          0 |     0 |      0.992 |    128 |     129 |     0.007 |    1
 {1, 2, 3, 4} |      2017-10-28 |         {2, 9, 21, 33} |       0.23 |    40 |       0.04 |      8 |     169 |      0.71 |  121
 {1, 2, 3, 4} |      2017-10-22 |        {7, 12, 23, 44} |       0.57 |    64 |       0.02 |      3 |     112 |       0.4 |   45

更新&amp; insert的工作原理如下。您首先选择该日期和冠军ID的现有统计信息,然后进行更新。如果行不在表格中,那么Cassandra执行时不会出现问题,在这种情况下会UPSERT

SELECT * FROM champion_stats_by_day WHERE champion_ids = {1,2,3,4} AND competing_champion_ids = {21,2,9,33} AND competition_day = '2017-11-01';
UPDATE champion_stats_by_day
    SET win_ratio = 0.38, draw_ratio = 0.04, loss_ratio = 0.57, wins = 32, draws = 4, losses = 48, matches = 84
    WHERE champion_ids = {1,2,3,4}
    AND competing_champion_ids = {21,2,9,33} 
    AND competition_day = '2017-11-01';

我还添加了示例CQL命令here。 让我知道你的想法。

答案 1 :(得分:1)

这个问题很长,所以在建议我的方法之前,我会谈论不同的主题,为长期答案做好准备:

  1. 数据规范化
  2. 具有相同值轴的二维表
  3. 数据规范化

    存储数据的总量很有用,但是由它排序并不是因为订单不能确定组合是否与另一个组合是好的,它确定了大多数时间赢得/输掉的组合与相反,但游戏的总量也很重要。

    订购结果时,您需要按先前两个的赢率,拉伸比率,松散比率进行排序,因为第三个是线性组合。

    具有相同值轴的二维表

    二维表上的问题,其中两个维度代表相同的数据,在这种情况下是一组5个冠军,是你要么制作一个三角形表,要么你有数据加倍,因为你必须存储cominationA vs combinationB和组合B与组合A,组合X是5个冠军的特定组合。

    这里有两个方法,使用三角表或手动加倍数据:

    1。三角桌:

    您创建一个表格,其中右上半部分为空或左下角为空。然后你在应用程序中处理哪个哈希是A,哪个是B,你可能需要交换他们的订单,因为没有重复的数据。例如,您可以考虑字母顺序,其中A&lt; B总是。如果您以错误的顺序请求数据,则无法获得数据。另一个选择是制作A对B和B对A查询,然后加入结果(显然交换胜利和失败)。

    2。手动加倍数据:

    通过制作两个具有反射值的插入(A,B,获胜,绘制,失败和B,A,失败,绘制,获胜),您将复制数据。这使您可以以任何顺序查询,但代价是使用两倍的空间并需要双重插入。

    利弊:

    一种方法的优点是另一种方法的缺点。

    三角桌的优点

    • 不存储重复数据
    • 需要插入一半

    将数据加倍的优点

    • 应用程序并不关心您提出请求的顺序

    我可能会使用三角表方法,因为应用程序复杂性的增加并不大,但可扩展性确实很重要。

    提议的架构

    使用你想要的任何键空间,我从stackoverflow中选择。根据需要修改复制策略或因子。

    CREATE KEYSPACE so WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};
    

    冠军名表

    冠军表将包含有关不同冠军的信息,现在它只会保留名称,但您可以在将来存储其他内容。

    CREATE TABLE so.champions (
        c boolean,
        id smallint,
        name text,
        PRIMARY KEY(c, id)
    ) WITH comment='Champion names';
    

    boolean被用作分区键,因为我们希望将所有冠军存储在一个分区中以提高查询性能,并且将会有一小部分记录(~100)我们将始终使用{{1} }}。 c=True选择smallint为2 ^ 7 = 128接近实际的冠军数量,并为未来的冠军留出空间而不使用负数。

    在查询冠军时,你可以通过以下方式获得所有冠军:

    id

    或通过以下方式请求特定的:

    SELECT id, name FROM so.champions WHERE c=True;
    

    历史匹配结果表

    此表将存储匹配的结果而不会混淆:

    SELECT name FROM so.champions WHERE c=True and id=XX;
    

    对于历史数据表的分区,正如您所提到的每日精确,CREATE TABLE so.matches ( dt date, ts time, id XXXXXXXX, teams list<frozen<set<smallint>>>, winA boolean, winB boolean, PRIMARY KEY(dt, ts, id) ) WITH comment='Match results'; 似乎是一个不错的分区键。 date列用作排序原因的第一个聚类键并完成时间戳,如果这些时间戳属于结束或结束时刻,则无关紧要,选择一个并坚持下去。在聚类密钥中需要一个额外的标识符,因为2个游戏可能在同一时刻结束(时间有纳秒精度,这基本上意味着丢失重叠的数据将是非常微不足道的,但是你的数据源可能不会有这种精确性,从而使这最后一个关键栏是必要的)。您可以使用此列所需的任何类型,可能您已经拥有了一些标识符,其中包含您可以在此处使用的数据。你也可以选择一个随机数,一个由应用程序管理的增量int,或者甚至是第一个玩家的名字,因为你可以确定同一个玩家不会在同一秒内开始/完成两个游戏。

    time列是最重要的列:它存储游戏中玩的冠军的ID。使用两个元素的序列,每个团队一个元素。内部(冻结)集合用于每个团队中的champs id,例如:teams。我尝试了几种不同的选项:{1,3,5,7,9}set< frozen<set<smallint>> >tuple< set<smallint>> , set<smallint> >。第一个选项并不存储球队的顺序,所以我们无法知道谁赢了比赛。第二个不接受使用此列上的索引并通过list< frozen<set<smallint>> >进行部分搜索,因此我选择了第三个保留订单并允许部分搜索的内容。

    另外两个值是代表谁赢了比赛的两个布尔值。您可以添加其他列CONTAINS,但如果您想存储游戏的长度,那么这一列不是必需的或draw boolean(我不使用Cassandra&#39; s {{ 1}}在purpouse上输入,因为它只需要几个月或至少几天)duration time / duration,如果你想在分区和群集密钥中存储你没有使用的那个等等。

    部分搜索

    在团队中创建索引以便您可以查询此列可能很有用:

    end timestamp

    然后我们可以执行以下start timestamp状态:

    CREATE INDEX matchesByTeams ON so.matches( teams );
    

    第一个将选择任何球队选择该组合的比赛,第二个将进一步过滤到今天的比赛。

    统计缓存表

    使用这两个表,您可以保存所有信息,然后请求计算所涉及统计数据所需的数据。计算完一些数据后,您可以将此信息存储在Cassandra中作为&#34;缓存&#34;在另一个表中,以便当用户请求显示某些统计信息时,首先检查它们是否已经计算,以及它们是否已计算。该表需要为用户可以输入的每个参数添加一列,例如:冠军组成,开始日期,最终日期,敌方团队;以及统计数据本身的其他列。

    SELECT

    按胜率/宽松比率排序:

    要按比例而不是敌方团队获得结果排序,您可以使用物化视图。

    SELECT * FROM so.matches WHERE teams CONTAINS {1,3,5,7,9};
    SELECT * FROM so.matches WHERE teams CONTAINS {1,3,5,7,9} AND dt=toDate(now());
    

    注意: 在我回答的过程中,我意识到引入&#34; patch&#34;的概念。在DB内部,以便不允许用户确定日期,但补丁可能是更好的解决方案。如果您对评论感兴趣,我将编辑答案以包含补丁概念。这意味着稍微修改CREATE TABLE so.stats ( team frozen<set<smallint>>, s_ts timestamp, e_ts timestamp, enemy frozen<set<smallint>>, win_ratio float, loose_ratio float, wins int, draws int, looses int, PRIMARY KEY(team, s_ts, e_ts, enemy) ) WITH comment="Already calculated queries"; CREATE MATERIALIZED VIEW so.statsByWinRatio AS SELECT * FROM so.stats WHERE team IS NOT NULL AND s_ts IS NOT NULL AND e_ts IS NOT NULL AND win_ratio IS NOT NULL AND enemy IS NOT NULL PRIMARY KEY(team, s_ts, e_ts, win_ratio, enemy) WITH comment='Allow ordering by win ratio'; 表,但是会有很小的变化。