为Cassandra数据库建模数据的最佳实践

时间:2017-09-25 09:13:41

标签: database cassandra cql database-normalization scylla

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

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

每个数据文件都有一些必需的数据字段(约20个必填项)但可以自行添加其他列(最多约100个)。

其他数据字段 NOT 必须与不同用户相同(字段名称或字段类型)

示例(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. 创建一个"全球" keypace
  2. 创建一个大表"数据"包含一切
  3. 将user_id列连接到大表的所有其他列(包括非强制列)。主键变为user_id +" column_1" (column_1对于每个user_id是唯一的)

                                     Keyspace
    +--------------------------------------------------------------------------+
    |                                                                          |
    |                                                                          |
    |                                      Data_Table                          |
    |                +  +--------+-------+--------------------------+-----+    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |    many rows   |  +-------------------------------------------------+    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |                          |     |    |
    |                |  |        |       |     Many columns         |     |    |
    |                |  |        |       +------------------------> |     |    |
    |                |  |        |       |                          |     |    |
    |                |  +-------------------------------------------------+    |
    |                v  +-------------------------------------------------+    |
    |                                                                          |
    +--------------------------------------------------------------------------+
    
  4. 我立即注意到的一些事情:

    1. user_id自身重复次数为每个用户的条目
    2. 其他列的行非常稀疏(空null 价值)因为用户不必共享它们
    3. 用户数量相对较少,因此附加列数 不是很大(最多10K列)
    4. 我可以将每个用户的附加列数据压缩到一个名为"元数据"的列中。并按所有用户分享
    5. 选项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个表)
      4. 选项3:

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

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

        注释

        1. 全局密钥空间
        2. 每个user_id的表格("许多"表格)
        3. 避免重复每行的用户ID
        4. 选项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. 需要"查找"找出哪个键空间包含所需的表
          4. 选项5:

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

            注意: 1.要求"加入"在某些情况下来自多个表的信息 2.似乎更复杂

            所有情景的一般说明:

            1. 写入次数少于读取次数
            2. 每天数百万次阅读
            3. 每个user_id的流量波动 - 某些user_ids拥有大量流量,而某些user_id的流量则更少。需要根据此指标进行调整
            4. 某些user_ids比其他用户更频繁地更新(写入)
            5. 我们在不同地理位置设有多个数据中心,并且应该同步
            6. 每个主键有一个长尾(一些键被访问很多次,而其他键很少被访问)

2 个答案:

答案 0 :(得分:4)

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

<强>文件

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

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

宽行

但回到数据模型。 Ashraful的模型有一个主键(userid)和另一个聚类键(key-&gt; column1)。由于&#34;条目的数量&#34;在每个用户文件(500K-2M)中并假设每个条目是由平均60列组成的行,您基本上做的是为每个分区键创建500k-2m * 60 avg列行,从而创建非常大的分区。 Cassandra和Scylla一般不喜欢非常大的分区。他们可以处理大型分区吗?在实践中,大型分区会影响性能,是的。

更新或版本控制

你提到了更新。基本EAV模型仅代表最新更新。没有版本控制。您可以做的是将时间添加为聚类键,以确保您随时间保持列的历史值。

<强>读取

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

分区和复合分区键

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

我可以在此处修改PRIMARY KEY以包含column1。然后我会使用column作为聚类键(它不仅指示分区内的唯一性,还指定排序顺序 - 所以请考虑在列命名约定中这样做。)

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

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文件中的行数。

答案 1 :(得分: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');