【问题标题】:Cassandra two dimensional data modellingCassandra 二维数据建模
【发布时间】:2018-04-02 19:37:43
【问题描述】:

用例:

对于一场比赛,我正在收集每场比赛的结果。总是 A 队对阵 B 队。每队由 5 名球员组成,每人选出一名冠军,一场比赛的可能结果是一支球队赢/输或两支球队打平。

我想找出最佳的英雄组合我想根据每支球队选择的英雄组合创建输赢/平局统计数据。总共有大约 100 个英雄可供玩家选择。所以有很多不同的英雄组合可能。

更多(奖励)功能:

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

我的问题:

我想知道根据英雄组合收集统计数据的最佳方法是什么?数据建模会是什么样子?

我的想法:

  1. 创建一个包含所有championId 的组合的哈希,该组合实际上表示championCombinationId,它是团队使用的冠军组合的唯一标识符。

  2. 创建一个允许跟踪组合与组合统计信息的二维表。像这样的东西:

那里缺少时间范围(每日日期)和combinationId 的实际championIds。

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

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
);

【问题讨论】:

  • 这不是 CQL 请求!你用什么工具??你在使用什么 KEYSPACE 和 columFamily ...等
  • 这只是数据建模,它还没有存储到 Cassandra。在这种情况下,我应该使用与 cassandra 不同的标签吗?
  • 我认为是的,因为 cos 并不是真正关于 cassandra,所以我认为你应该谈谈建模数据、merise 或类似的东西,可能你比我有更多的想法,我英语不流利所以 :) 我希望你找到一个其他主题名称,不要让人们在看一些不是真正在谈论的东西时感到困惑。 ;) 非常喜欢。

标签: cassandra bigdata data-modeling cql cassandra-3.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

更新和插入工作如下。您首先选择该日期和冠军 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。 让我知道你的想法。

【讨论】:

  • 对非标准化数据排序只会将最常见的组合放在顶部。
  • @Adirio - 您指的是哪个表和列?提及WITH CLUSTERING ORDER BY 中的所有列被认为是一种很好的做法。这就是为什么我添加了competing_champion_ids
  • 我指的是赢、平和输,它们是整数值,按它们排序不会那么有用。您可能希望将赢率、抽奖率和损失率(或者可能只是其中两个,因为第三个是线性相关的)存储为百分比,并将它们用作聚类键,而将整数作为普通列。
  • @kentor - 反映您的问题: 1. 您可以添加过滤日期,在这种情况下,您应该每天维护赢/平/输字段,这会使写入变得复杂。此外,在您阅读给定冠军的最近比赛后,您还必须在客户端中汇总和排序结果。
  • 3.在 Cassandra 中无法更新集群列,这就是批量添加删除/插入的原因。如果您为表中的任何字段添加计数器,则该表将被视为counter table。计数器表有特殊的规则,所以使用起来有点复杂。例如,您不能在同一个表中使用比率字段和计数器字段。
【解决方案2】:

这个问题很长,所以我会在提出我的方法之前先谈谈不同的话题,准备好回答很长:

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

数据规范化

存储总数据量很有用,但按其排序则没有用,因为顺序不能确定组合是否好与另一个,它确定大多数时候赢/输的组合与相反的组合,但总数玩的游戏数量也很重要。

在排序结果时,您希望按前两个的胜率、平局率、松率进行排序,因为第三个是线性组合。

具有相同值轴的二维表

在两个维度表示相同数据的二维表(在本例中为一组 5 个冠军)的问题是,您要么制作一个三角形表,要么将数据加倍,因为您必须存储组合 A 与组合 B 和组合 B 与组合 A,组合 X 是一组特定的 5 个冠军。

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

1。三角桌:

您创建一个表格,其中右上半部分为空或左下角为空。然后,您在应用程序中处理哪个哈希是 A,哪个是 B,并且您可能需要交换它们的顺序,因为没有重复的数据。例如,您可以考虑始终 A

2。手动加倍数据:

通过使用反射值(A、B、wins、draws、loose & B、A、looses、draws、wins)进行两次插入,您将复制数据。这使您可以以任何顺序查询,代价是使用两倍的空间并需要两次插入。

优点和缺点:

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

三角桌的优点

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

数据翻倍的优点

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

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

提议的架构

使用您想要的任何键空间,我从 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 用作分区键,因为我们希望将所有冠军存储在单个分区中以提高查询性能,并且我们将始终使用 c=True 的记录数量很少(约 100 条)。为id 选择了smallint,因为 2^7 = 128 是为了接近实际的冠军数量,并在不使用负数的情况下为未来的冠军留出空间。

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

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 似乎是一个不错的分区键。 time 列用作排序原因的第一个聚类键并完成时间戳,无论这些时间戳属于结束瞬间还是结束瞬间,选择一个并坚持下去。集群键中需要一个额外的标识符,因为 2 个游戏可能在同一时刻结束(时间具有纳秒精度,这基本上意味着丢失重叠的数据将非常微不足道,但您的数据源可能没有这个精度,因此使这最后一个键列是必要的)。您可以在此列中使用您想要的任何类型,可能您已经拥有一些可以在此处使用的数据的标识符之王。您还可以选择随机数、由应用程序管理的增量 int,甚至是第一个玩家的姓名,因为您可以确保同一玩家不会在同一秒开始/完成两场比赛。

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

另外两个值是两个布尔值,代表谁赢得了比赛。你可以有额外的列,比如draw boolean,但如果你想存储游戏的长度,这不是必需的或duration time(我没有使用Cassandra的duration类型,因为它只值得这需要几个月或至少几天),end timestamp/start timestamp 如果您想将不使用的那个存储在分区和集群键等中。

部分搜索

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

CREATE INDEX matchesByTeams ON so.matches( teams );

然后我们可以执行以下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());

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

统计缓存表

通过这两个表,您可以保存所有信息,然后请求您需要的数据来计算所涉及的统计数据。一旦你计算了一些数据,你可以将这些信息作为“缓存”存储回 Cassandra 中,这样当用户请求显示一些统计信息时,你首先检查它们是否已经计算过,如果它们没有计算过.该表需要为用户可以输入的每个参数提供一列,例如:冠军组成、开始日期、结束日期、敌方队伍;以及统计数据本身的附加列。

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';

注意: 当我回答时,我意识到在数据库中引入“补丁”的概念,这样用户就不能确定日期,但补丁可能是一个更好的解决方案。如果您有兴趣发表评论,我将编辑答案以包含补丁概念。这意味着稍微修改so.historicso.stats 表,但改动很小。

【讨论】:

  • 哇,感谢您提供如此详细的答案。我昨天读了它,我印象深刻的是你已经处理了我什至没有提到的几个细节。我试图阅读有关 Cassandra 的“补丁概念”,但我没有找到任何东西。我也很乐意了解它!
  • 从匹配表开始。我会在那里寻找GROUP BY 查询,以便在过去 30 天内获得顶级冠军组合,在数据库中记录至少 1000 次。你会怎么查询呢?以及查询如何为一组特定的冠军 ID 填充“缓存”表。更准确地说:我不确定如何从技术上弄清楚teams 列表中的哪一组赢得了比赛。另外,在匹配表中按dtts 降序添加聚类顺序不是明智之举吗?使用teams 作为匹配分区键怎么样?
  • @kentor 我所说的补丁是指你提到的不同的平衡变化,而不是 Cassandra (C*)。似乎我们正在谈论一款 MOBA 游戏,而这些平衡性更改通常会与补丁相关联。我弄错了吗?如果我不是,我们可以更改数据按日期排序的方式,改为按补丁排序。 winA=True (=>winB=False) 表示列表中的第一组获胜,winB=True (=>winA=False) 表示列表中的第二组获胜,winA=winB=False 表示获胜平局。
  • @kentor 不,没关系,这就是使用集合的优势,它在内部对元素进行排序并避免重复(MOBA 游戏通常不允许同一个英雄在一个团队中出现两次除了有趣的场景)。如果这些限制中的任何一个不适合您,那就是,如果您需要特定顺序而不是让集合对它们进行排序,或者如果您需要重复,那么您可以将集合交换为列表,然后使用 [3,1,2]跨度>
  • 如果不是,您需要将list&lt;frozen&lt;set&lt;smallint&gt;&gt;&gt; 替换为list&lt;frozen&lt;list&lt;smallint&gt;&gt;&gt;,并用方括号将它们插入所有:[ [1,2,3,4,4] , [5,6,7,8,9] ]。请记住一件事:您将始终需要在应用程序中对列表进行排序,因为如果您使用[1,2,2,3,3] 存储数据并且使用[1,2,3,2,3] 进行查询,它们将不匹配。这个问题通过集合解决,因为它们是在内部订购的。所以如果你只考虑重复的特殊情况,那么请坚持使用该系列,因为团队中的所有冠军都是相互平等的。
猜你喜欢
  • 2017-08-10
  • 2015-12-25
  • 2015-04-09
  • 1970-01-01
  • 2019-09-02
  • 2018-06-16
  • 2018-07-10
  • 2016-08-20
相关资源
最近更新 更多