6

我是 Cassandra 的新手,正在寻找关于如何对具有以下一般结构的数据进行建模的最佳实践:

数据是基于“用户”的(每个客户),每个都提供大约 500K-2M 条目的大数据文件(每天定期更新几次 - 有时完全更新,有时只有增量)

每个数据文件都有某些强制性数据字段(约 20 个强制性),但可以自行决定添加其他列(最多约 100 个)。

不同用户的附加数据字段不一定相同(字段名称或这些字段的类型)

示例(csv 格式:)

user_id_1.csv

| column1 (unique key per user_id)  |  column2  |  column3 |   ...   |  column10  |  additionalColumn1  |  ...additionalColumn_n |
|-----------------------------------|-----------|----------|---------|------------|---------------------|------------------------|
| user_id_1_key_1                   |  value    |  value   |  value  |  value     |                ...  |  value                 |
| user_id_1_key_2                   |  ....     |  ....    |  ....   |  ....      |                ...  |  ...                   |
| ....                              |  ...      |  ...     |  ...    |  ...       |                ...  |  ...                   |
| user_id_1_key_2Million            |  ....     |  ....    |  ....   |  ....      |                ...  |  ...                   |


user_id_XXX.csv (notice that the first 10 columns are identical to the other users but the additional columns are different - both the names and their types)

|             column1 (unique key per user_id)              |  column2  |  column3 |   ...   |  column10  |  additionalColumn1 (different types than user_id_1 and others)  |  ...additional_column_x |
|-----------------------------------------------------------|-----------|----------|---------|------------|-----------------------------------------------------------------|-------------------------|
| user_id_XXX_key_1                                         |  value    |  value   |  value  |  value     |                                                            ...  |  value                  |
| user_id_XXX_key_2                                         |  ....     |  ....    |  ....   |  ....      |                                                            ...  |  ...                    |
| ....                                                      |  ...      |  ...     |  ...    |  ...       |                                                            ...  |  ...                    |
| user_id_XXX_key_500_thousand (less rows than other user)  |  ....     |  ....    |  ....   |  ....      |                                                            ...  |  ...                    |

我考虑过的几个选项:

选项1:

  1. 创建一个“全局”键空间
  2. 创建一个包含所有内容的大表“数据”
  3. 将 user_id 列连接到大表的所有其他列(包括非强制列)。主键变为 user_id + "column_1"(column_1 每个 user_id 都是唯一的)

                                     Keyspace
    +--------------------------------------------------------------------------+
    |                                                                          |
    |                                                                          |
    |                                      Data_Table                          |
    |                +  +--------+-------+--------------------------+-----+    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |    many rows   |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |     Many columns         |     |    |
    |                |  |        |       +------------------------> |     |    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                v  +-------------------------------------------------+    |
    |                                                                          |
    +--------------------------------------------------------------------------+
    

我马上注意到的几件事:

  1. user_id 重复的次数与每个用户的条目一样多
  2. 附加列(空值)的行非常稀疏,因为用户不一定共享它们
  3. 用户数量相对较少,因此附加列的数量并不多(最多 10K 列)
  4. 我可以将每个用户的附加列数据压缩为一个名为“元数据”的列,并为所有用户共享

选项 2:

为每个 User_id 创建 Keyspace

每个键空间创建表“数据”

+-----------------------------------------------------------------------------------+
| column_1 | column_2 | ... | column_n | additional_column_1 | additional_column_n  |
+-----------------------------------------------------------------------------------+

keyspace_user1         keyspace_user2                     keyspace_user_n
+----------------+    +---------------+                  +---------------+
|                |    |               |                  |               |
|                |    |               |                  |               |
|   +-+-+--+-+   |    |    +-+--+--+  |                  |   +--+--+---+ |
|   | | |  | |   |    |    | |  |  |  |   many keyspaces |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  | +------------->  |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  |                  |   |  |  |   | |
|   | | |  | |   |    |    | |  |  |  |                  |   |  |  |   | |
|   +--------+   |    |    +-------+  |                  |   +---------+ |
+----------------+    +---------------+                  +---------------+

笔记:

  1. 许多键空间(每个用户的键空间)
  2. 避免为每行添加“user_id”值(我可以使用键空间名称作为用户 ID)
  3. 每个键空间的表很少(在此示例中,每个键空间只有 1 个表)

选项 3:

1)创建一个全局键空间 2)为每个 user_id 创建一个表(强制列以及每个表的附加列)

+---------------------------------------------------------------+
|                            Keyspace                           |
|                                                               |
|       user_1        user_2                         user_n     |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |
|                                                               |
|                                                               |
+---------------------------------------------------------------+

笔记

  1. 全局键空间
  2. 每个 user_id 一个表(“许多”表)
  3. 避免每行重复用户 ID

选项4:(这有意义吗?)

创建多个键空间(例如“x”个键空间),每个键空间包含一系列表(每个用户的表)

                      keyspace_1                                                                                keyspace_x
+---------------------------------------------------------------+                         +---------------------------------------------------------------+
|                                                               |                         |                                                               |
|                                                               |                         |                                                               |
|       user_1        user_2                        user_n/x    |                         |     user_n-x      user_n-x+1                       user_n     |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |                         |    +--+------+   +--+--+--+                      +--+--+--+   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |        "X" keyspaces    |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   | +---------------------> |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    |  |   |  |   |  |  |  |                      |  |  |  |   |                         |    |  |   |  |   |  |  |  |                      |  |  |  |   |
|    +--+---+--+   +--+--+--+                      +--+--+--+   |                         |    +--+---+--+   +--+--+--+                      +--+--+--+   |
|                                                               |                         |                                                               |
|                                                               |                         |                                                               |
+---------------------------------------------------------------+                         +---------------------------------------------------------------+

笔记:

  1. 多个键空间
  2. 每个用户多个表
  3. 需要“查找”来确定哪个键空间包含所需的表

选项 5:

将数据拆分到多个表和多个键空间

注意:1.在某些情况下需要从多个表中“加入”信息2.似乎更复杂


所有场景的一般说明:

  1. 写入比读取少一个数量级
  2. 每天数百万次阅读
  3. 每个 user_id 的流量波动 - 一些 user_id 的流量很大,而一些 user_id 的流量要少得多。需要根据这个指标进行调整
  4. 一些 user_id 的更新(写入)比其他的更频繁
  5. 我们在不同地区拥有多个数据中心,应该同步
  6. 每个主键都有一个长尾(一些键被多次访问,而另一些键很少被访问)
4

2 回答 2

4

这种类型的集成挑战通常通过关系系统中的EAV(实体属性值)数据模型来解决(就像 Ashrafaul 所展示的那样)。考虑 EAV 模型时的关键考虑因素是列数不受限制。当然,EAV 数据模型可以在 Cassandra 或 ScyllaDB 等 CQL 系统中进行模仿。EAV 模型非常适合写入,但在读取时会遇到挑战。您还没有真正详细说明您的阅读注意事项。您需要返回所有列还是需要每个用户返回特定列?

文件

话虽如此,Cassandra 和 ScyllaDB 还存在一些固有的考虑因素,这些考虑因素可能会将您指向一个统一的 EAV 模型,而不是您在问题中描述的一些设计。Cassandra 和 ScyllaDB 都将键空间和数据库布局为磁盘上的文件。文件数基本上是键空间数乘以表数的乘积。因此,您拥有的键空间、表或两者的组合越多,磁盘上的文件就越多。这可能是文件描述符和其他操作系统文件杂耍问题的问题。由于您提到的访问的长尾,可能是每个文件都一直打开的情况。这不是那么理想,尤其是从冷启动开始时。

[为清楚起见进行编辑]在所有条件相同的情况下,一个键空间/表生成的文件总是比许多键空间/表少。这与存储的数据量或压缩策略无关。

宽行

但是回到数据模型。Ashraful 的模型有一个主键(userid)和另一个集群键(key->column1)。由于每个用户文件中的“条目”数量(500K-2M)并假设每个条目是由平均 60 列组成的行,因此您基本上要做的是为每个分区键创建 500k-2m * 60 平均列行创建非常大的分区。Cassandra 和 Scylla 通常不喜欢非常大的分区。他们可以处理大分区,当然。实际上,大分区会影响性能,是的。

更新或版本控制

你提到更新。基本 EAV 模型将仅代表最近的更新。没有版本控制。您可以做的是添加时间作为集群键,以确保您随着时间的推移维护列的历史值。

读取

如果您想要返回所有列,您可以将所有内容序列化为一个 json 对象并将其放在一个列中。但我想这不是你想要的。在 Cassandra 和 Scylla 等基于键/值的系统的主键(分区键)模型中,您需要知道键的所有组件才能取回数据。如果您将column1唯一的行标识符放入主键中,您将需要提前知道它,如果其他列名也放入主键中,则同样如此。

分区和复合分区键

分区的数量决定了集群的并行度。总分区数或总语料库中的分区基数会影响集群硬件的利用率。更多分区 = 更好的并行性和更高的资源利用率。

我在这里可能做的是PRIMARY KEYcolumn1. 然后我将column用作集群键(它不仅指示分区内的唯一性,还指示排序顺序 - 所以在你的列命名约定中考虑这一点)。

在下表定义中,您需要在子句中提供useridcolumn1作为等式。WHERE

CREATE TABLE data (
    userid bigint,
    column1 text,
    column text,
    value text,
    PRIMARY KEY ( (userid, column1), column )
);

我也有一个单独的表,也许columns_per_user,记录每个的所有列userid。就像是

CREATE TABLE columns_per_user (
    userid bigint,
    max_columns int,
    column_names text
    PRIMARY KEY ( userid )
);

其中max_columns是该用户的总列数,column_names是实际的列名。您可能还会有一列用于记录每个用户的条目总数,例如user_entries int基本上是每个用户 csv 文件中的行数。

于 2017-09-27T01:29:19.920 回答
0

尝试以下架构:

CREATE TABLE data (
    userid bigint,
    key text,
    column text,
    value text,
    PRIMARY KEY (userid, key)
);

这里

userid  -> userid
key     -> column1
column  -> column name from column2
value   -> column value

插入以下数据的示例:

| column1 (unique key per user_id)  |  column2      |  column3        |
|-----------------------------------|---------------|-----------------|
| key_1                             |  value12      |  value13        | 
| key_2                             |  value22      |  value23        |

插入语句:

INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_1', 'column2', 'value12');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_1', 'column3', 'value13');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_2', 'column2', 'value22');
INSERT INTO data (userid , key , column , value ) VALUES ( 1, 'key_2', 'column3', 'value23');
于 2017-09-25T12:01:55.587 回答