Cassandra 二维数据建模
Cassandra two dimensional data modelling
用例:
对于一场比赛,我正在收集每场比赛的结果。始终是 A 队对战 B 队。每队由 5 名选手组成,每队选出一名冠军,一场比赛的可能结果是一方获胜/失败或两队平局。
我想找出最佳冠军组合我想根据每支球队选择的冠军组合创建 win/lose/draw 统计数据。总共有大约 100 个冠军可供玩家选择。所以有很多不同的冠军组合可能。
更多(奖励)功能:
- 我想弄清楚一个组合如何对抗另一个特定组合(简而言之:对抗非常强大的冠军组合的最佳组合是什么)
- 随着平衡变化应用于游戏,有可能select/按特定时间范围过滤统计数据(例如仅过去 14 天)是有意义的 - 每日精度就可以了
我的问题:
我想知道根据英雄组合收集统计数据的最佳方法是什么?数据建模会是什么样子?
我的想法:
创建组合中所有 championId
的散列,字面上表示 championCombinationId
,这是团队使用的冠军组合的唯一标识符。
创建一个二维 table 允许跟踪组合与组合统计数据。是这样的:
时间框架(每日日期)和 combinationId
的实际 championId
在那里丢失。
我尝试自己为上述要求创建模型,但我完全不确定。我也不知道我需要指定哪些键。
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
);
您可以创建一个统计数据 table 来保存某个冠军在指定日期的比赛数据。
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
更新和插入工作如下。您首先 select 该日期和冠军 ID 的现有统计数据,然后进行更新。万一,当行不在 table 中时,这不会成为 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。
让我知道你的想法。
这个问题很长,所以在提出我的方法之前我会先讨论不同的主题,请准备好回答一个很长的问题:
- 数据规范化
- 具有相同值轴的二维tables
数据规范化
存储总数据量很有用,但按它排序不是,因为顺序不能确定一个组合是否好于另一个组合,它决定了大多数时候 won/lost 与相反,但游戏的总数量也很重要。
排序结果时,您希望按前两项的胜率、平局率、松率排序,因为第三项是线性组合。
具有相同值轴的二维tables
二维 table 的问题,其中两个维度代表相同的数据,在这种情况下是一组 5 个冠军,要么你制作一个三角形 table 要么你有数据加倍,因为你必须存储 cominationA 与 combinationB 和 combinationB 与 combinationA,因为 combinationX 是一组特定的 5 个冠军。
这里有两种方法,使用三角table或者手动加倍数据:
1。三角形 tables:
您创建一个 table,其中右上半部分为空或左下半部分为空。然后你在应用程序中处理哪个哈希是 A,哪个是 B,你可能需要交换它们的顺序,因为没有重复的数据。例如,您可以考虑字母顺序,其中 A < B 总是。如果您随后以错误的顺序请求数据,您将得不到任何数据。另一种选择是同时进行 A vs B 和 B vs A 查询,然后加入结果(显然交换输赢)。
2。手动加倍数据:
通过使用反映值(A、B、胜、平、输 & B、A、输、平、胜)进行两次插入,您将复制数据。这使您可以按任何顺序查询,但代价是使用两倍 space 并需要两次插入。
优缺点:
一种方法的优点是另一种方法的缺点。
三角形的优点tables
- 不存储重复数据
- 需要一半的插入
数据加倍的优点
- 应用程序不关心您以何种顺序发出请求
我可能会使用三角 table 方法,因为应用程序复杂性的增加并没有那么重要,但可扩展性确实很重要。
提议的架构
使用你想要的任何键space,我从Whosebug中选择。根据需要修改复制策略或因子。
CREATE KEYSPACE so WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};
冠军名字table
冠军 table 将包含有关不同冠军的信息,目前它只会保存名称,但您可以在将来存储其他内容。
CREATE TABLE so.champions (
c boolean,
id smallint,
name text,
PRIMARY KEY(c, id)
) WITH comment='Champion names';
A boolean
用作分区键,因为我们希望将所有冠军存储在单个分区中以提高查询性能,并且记录数量较少(~100)我们将始终使用 c=True
。 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;
历史比赛结果table
此 table 将存储匹配结果而不汇总:
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';
对于一个历史数据的分区table,而且正如你所说的每日精度,date
似乎是一个不错的分区键。 time
列用作第一个聚类键,用于排序原因和完成时间戳,不管这些时间戳属于结束时刻还是结束时刻,选择一个并坚持下去。聚类键中需要一个额外的标识符,因为 2 场比赛可能会在同一时刻结束(时间具有纳秒精度,这基本上意味着重叠丢失的数据将非常微不足道,但您的数据源可能没有这种精度,因此最后一个关键列是必要的)。您可以为该列使用您想要的任何类型,您可能已经拥有一些标识符之王,其中包含您可以在此处使用的数据。您还可以选择一个随机数、一个由应用程序管理的增量整数,甚至是第一位玩家的名字,因为您可以确定同一位玩家不会 start/finish 在同一秒内玩两场游戏。
teams
列是最重要的一列:它存储了游戏中使用过的英雄的 ID。使用两个元素的序列,每个团队一个。内部(冻结)集用于每个团队中的 champs id,例如:{1,3,5,7,9}
。我尝试了几个不同的选项:set< frozen<set<smallint>> >
、tuple< set<smallint>> , set<smallint> >
和 list< frozen<set<smallint>> >
。第一个选项不存储团队的顺序,因此我们无法知道谁赢得了比赛。第二个不接受在此列上使用索引并通过 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());
第一个将 select 任何球队 select 编辑该组合的比赛,第二个将进一步过滤到今天的比赛。
统计缓存table
有了这两个table你就可以掌握所有的信息,然后请求你需要的数据来计算相关的统计数据。一旦你计算了一些数据,你可以将这些信息作为一个 "cache" 存储回 Cassandra 中,在一个额外的 table 中,这样当用户请求显示一些统计数据时,你首先检查它们是否已经计算并且如果他们没有计算。 table 需要为用户可以输入的每个参数设置一列,例如:冠军组成、开始日期、结束日期、敌方队伍;以及统计信息本身的附加列。
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";
按 win/loose 比率排序:
要按比率而不是敌方队伍获取结果顺序,您可以使用物化视图。
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';
注意:
当我回答时,我意识到在数据库中引入 "patch" 的概念,这样用户就不能确定日期,但补丁可能是更好的解决方案。如果您有兴趣发表评论,我将编辑答案以包含补丁概念。这意味着要稍微修改 so.historic
和 so.stats
table,但变化很小。
用例:
对于一场比赛,我正在收集每场比赛的结果。始终是 A 队对战 B 队。每队由 5 名选手组成,每队选出一名冠军,一场比赛的可能结果是一方获胜/失败或两队平局。
我想找出最佳冠军组合我想根据每支球队选择的冠军组合创建 win/lose/draw 统计数据。总共有大约 100 个冠军可供玩家选择。所以有很多不同的冠军组合可能。
更多(奖励)功能:
- 我想弄清楚一个组合如何对抗另一个特定组合(简而言之:对抗非常强大的冠军组合的最佳组合是什么)
- 随着平衡变化应用于游戏,有可能select/按特定时间范围过滤统计数据(例如仅过去 14 天)是有意义的 - 每日精度就可以了
我的问题:
我想知道根据英雄组合收集统计数据的最佳方法是什么?数据建模会是什么样子?
我的想法:
创建组合中所有
championId
的散列,字面上表示championCombinationId
,这是团队使用的冠军组合的唯一标识符。创建一个二维 table 允许跟踪组合与组合统计数据。是这样的:
时间框架(每日日期)和 combinationId
的实际 championId
在那里丢失。
我尝试自己为上述要求创建模型,但我完全不确定。我也不知道我需要指定哪些键。
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
);
您可以创建一个统计数据 table 来保存某个冠军在指定日期的比赛数据。
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
更新和插入工作如下。您首先 select 该日期和冠军 ID 的现有统计数据,然后进行更新。万一,当行不在 table 中时,这不会成为 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。 让我知道你的想法。
这个问题很长,所以在提出我的方法之前我会先讨论不同的主题,请准备好回答一个很长的问题:
- 数据规范化
- 具有相同值轴的二维tables
数据规范化
存储总数据量很有用,但按它排序不是,因为顺序不能确定一个组合是否好于另一个组合,它决定了大多数时候 won/lost 与相反,但游戏的总数量也很重要。
排序结果时,您希望按前两项的胜率、平局率、松率排序,因为第三项是线性组合。
具有相同值轴的二维tables
二维 table 的问题,其中两个维度代表相同的数据,在这种情况下是一组 5 个冠军,要么你制作一个三角形 table 要么你有数据加倍,因为你必须存储 cominationA 与 combinationB 和 combinationB 与 combinationA,因为 combinationX 是一组特定的 5 个冠军。
这里有两种方法,使用三角table或者手动加倍数据:
1。三角形 tables:
您创建一个 table,其中右上半部分为空或左下半部分为空。然后你在应用程序中处理哪个哈希是 A,哪个是 B,你可能需要交换它们的顺序,因为没有重复的数据。例如,您可以考虑字母顺序,其中 A < B 总是。如果您随后以错误的顺序请求数据,您将得不到任何数据。另一种选择是同时进行 A vs B 和 B vs A 查询,然后加入结果(显然交换输赢)。
2。手动加倍数据:
通过使用反映值(A、B、胜、平、输 & B、A、输、平、胜)进行两次插入,您将复制数据。这使您可以按任何顺序查询,但代价是使用两倍 space 并需要两次插入。
优缺点:
一种方法的优点是另一种方法的缺点。
三角形的优点tables
- 不存储重复数据
- 需要一半的插入
数据加倍的优点
- 应用程序不关心您以何种顺序发出请求
我可能会使用三角 table 方法,因为应用程序复杂性的增加并没有那么重要,但可扩展性确实很重要。
提议的架构
使用你想要的任何键space,我从Whosebug中选择。根据需要修改复制策略或因子。
CREATE KEYSPACE so WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};
冠军名字table
冠军 table 将包含有关不同冠军的信息,目前它只会保存名称,但您可以在将来存储其他内容。
CREATE TABLE so.champions (
c boolean,
id smallint,
name text,
PRIMARY KEY(c, id)
) WITH comment='Champion names';
A boolean
用作分区键,因为我们希望将所有冠军存储在单个分区中以提高查询性能,并且记录数量较少(~100)我们将始终使用 c=True
。 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;
历史比赛结果table
此 table 将存储匹配结果而不汇总:
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';
对于一个历史数据的分区table,而且正如你所说的每日精度,date
似乎是一个不错的分区键。 time
列用作第一个聚类键,用于排序原因和完成时间戳,不管这些时间戳属于结束时刻还是结束时刻,选择一个并坚持下去。聚类键中需要一个额外的标识符,因为 2 场比赛可能会在同一时刻结束(时间具有纳秒精度,这基本上意味着重叠丢失的数据将非常微不足道,但您的数据源可能没有这种精度,因此最后一个关键列是必要的)。您可以为该列使用您想要的任何类型,您可能已经拥有一些标识符之王,其中包含您可以在此处使用的数据。您还可以选择一个随机数、一个由应用程序管理的增量整数,甚至是第一位玩家的名字,因为您可以确定同一位玩家不会 start/finish 在同一秒内玩两场游戏。
teams
列是最重要的一列:它存储了游戏中使用过的英雄的 ID。使用两个元素的序列,每个团队一个。内部(冻结)集用于每个团队中的 champs id,例如:{1,3,5,7,9}
。我尝试了几个不同的选项:set< frozen<set<smallint>> >
、tuple< set<smallint>> , set<smallint> >
和 list< frozen<set<smallint>> >
。第一个选项不存储团队的顺序,因此我们无法知道谁赢得了比赛。第二个不接受在此列上使用索引并通过 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());
第一个将 select 任何球队 select 编辑该组合的比赛,第二个将进一步过滤到今天的比赛。
统计缓存table
有了这两个table你就可以掌握所有的信息,然后请求你需要的数据来计算相关的统计数据。一旦你计算了一些数据,你可以将这些信息作为一个 "cache" 存储回 Cassandra 中,在一个额外的 table 中,这样当用户请求显示一些统计数据时,你首先检查它们是否已经计算并且如果他们没有计算。 table 需要为用户可以输入的每个参数设置一列,例如:冠军组成、开始日期、结束日期、敌方队伍;以及统计信息本身的附加列。
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";
按 win/loose 比率排序:
要按比率而不是敌方队伍获取结果顺序,您可以使用物化视图。
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';
注意:
当我回答时,我意识到在数据库中引入 "patch" 的概念,这样用户就不能确定日期,但补丁可能是更好的解决方案。如果您有兴趣发表评论,我将编辑答案以包含补丁概念。这意味着要稍微修改 so.historic
和 so.stats
table,但变化很小。