用于简单消息传递应用程序的 Cassandra 数据模型

Cassandra data model for simple messaging app

我正在尝试学习 Cassandra,并且总是发现最好的方法是从创建一个非常简单的小型应用程序开始。因此,我正在创建一个将使用 Cassandra 作为后端的基本消息传递应用程序。我想做以下事情:

因为我来自关系数据库的世界,所以我的关系数据库看起来像这样:

UsersTable
    username (text)
    email (text)
    password (text)
    time_created (timestamp)
    last_loggedIn (timestamp)
------------------------------------------------ 
ContactsTable
    user_i_added (text)
    user_added_me (text)
------------------------------------------------     
MessagesTable
    from_user (text)
    to_user (text)
    msg_body (text)
    metadata (text)
    has_been_read (boolean)
    message_sent_time (timestamp)

阅读了几本 Cassandra 教科书后,我想到了如何对数据库建模。我主要关心的是以一种非常有效的方式对数据库进行建模。因此,我试图避免二级索引等。这是我目前的模型:

CREATE TABLE users_by_username (
    username text PRIMARY KEY,
    email text,
    password text
    timeCreated timestamp
    last_loggedin timestamp
)

CREATE TABLE users_by_email (
    email text PRIMARY KEY,
    username text,
    password text
    timeCreated timestamp
    last_loggedin timestamp
)

为了均匀分布数据并读取最少数量的分区(希望只有一个),我可以根据用户名或电子邮件快速查找用户。这样做的缺点显然是我的数据翻了一番,但存储成本非常便宜,所以我发现这是一个很好的权衡而不是使用二级索引。上次登录也需要写入两次,但 Cassandra 的写入效率很高,所以我相信这也是一个很好的权衡。

对于联系人,我想不出任何其他方法来对此进行建模,因此我对其建模非常类似于我在关系数据库中的建模方式。根据我读过的书,我认为这是一个非常非规范化的设计,应该对性能有好处?

CREATE TABLE "user_follows" (
  follower_username text,
  followed_username text,
  timeCreated timestamp, 
  PRIMARY KEY ("follower_username", "followed_username")
);

CREATE TABLE "user_followedBy" (

  followed_username text,
  follower_username text,
  timeCreated timestamp,
  PRIMARY KEY ("followed_username", "follower_username")
);

我不知道如何创建下一部分。对于消息传递,我正在考虑这个 table,因为它创建了宽行,可以对消息进行排序。 我需要消息来回答两个问题。它首先需要能够向用户显示他们拥有的所有消息,还能够向用户显示 新消息和未读消息。这是一个基本模型,但不确定如何使其更有效?

CREATE TABLE messages (
    message_id uuid,
    from_user text,
    to_user text,
    body text,
    hasRead boolean,
    timeCreated timeuuid,
    PRIMARY KEY ((to_user), timeCreated )
) WITH CLUSTERING ORDER BY (timeCreated ASC);

我也在考虑使用诸如 STATIC 列之类的东西将用户和消息 'glue' 在一起,以及使用 SETS 来存储联系人关系,但从我目前的狭隘理解来看,我提出的方式更多高效的。我问是否有任何想法可以提高这个模型的效率,是否有更好的做法来做我想做的事情,或者这个设计是否有任何隐藏的问题?

总而言之,我正在尝试围绕查询建模。如果我使用关系数据库,这些基本上就是我要回答的查询:

To Login:
SELECT * FROM USERS WHERE (USERNAME = [MY_USERNAME] OR EMAIL = [MY_EMAIL]) AND PASSWORD = [MY_PASSWORD];
------------------------------------------------------------------------------------------------------------------------
Update user info:
UPDATE USERS (password) SET password = [NEW_PASSWORD] where username = [MY_USERNAME];
UPDATE USERS (email) SET password = [NEW_PASSWORD ] where username = [MY_USERNAME];
------------------------------------------------------------------------------------------------------------------------ 
To Add contact (If by username):
INSERT INTO followings(following,follower)  VALUES([USERNAME_I_WANT_TO_FOLLOW],[MY_USERNAME]);
------------------------------------------------------------------------------------------------------------------------
To Add contact (If by email):
SELECT username FROM users where email = [CONTACTS_EMAIL];
    Then application layer sends over another query with the username:
INSERT INTO followings(following,follower)  VALUES([USERNAME_I_WANT_TO_FOLLOW],[MY_USERNAME]);
------------------------------------------------------------------------------------------------------------------------
To View contacts:
SELECT following FROM USERS WHERE follower = [MY_USERNAME];
------------------------------------------------------------------------------------------------------------------------
To Send Message:,
INSERT INTO MESSAGES (MSG_ID, FROM, TO, MSG, IS_MSG_NEW) VALUES (uuid, [FROM_USERNAME], [TO_USERNAME], 'MY MSG', true);
------------------------------------------------------------------------------------------------------------------------
To View All Messages (Some pagination type of technique where shows me the 10 recent messages, yet shows which ones are unread):
SELECT * FROM MESSAGES WHERE TO = [MY_USERNAME] LIMIT 10;
------------------------------------------------------------------------------------------------------------------------
Once Message is read:
UPDATE MESSAGES SET IS_MSG_NEW = false WHERE TO = [MY_USERNAME] AND MSG_ID = [MSG_ID];

干杯

是的,来自关系数据库背景的人总是很难适应 Cassandra 的局限性。由于我们还没有在 Cassandra 中进行连接的奢侈,因此您通常希望尽可能多地塞入一个 table 中。在你的情况下,那将是 users_by_username table.

Cassandra 的一些功能可以让您做到这一点。

由于您是 Cassandra 的新手,您可能会使用目前处于测试版的 Cassandra 3.0。在 3.0 中有一个很好的特性叫做物化视图。这将允许您将 users_by_username 作为基础 table,并创建 users_by_email 作为物化视图。然后 Cassandra 将在您更新基础 table.

时自动更新视图

另一个对您有帮助的功能是用户定义的类型(在 C* 2.1 和更高版本中)。无需为关注者和消息创建单独的 table,您可以将它们的结构创建为 UDT,然后在用户 table 中保留这些类型的列表。

所以您的架构的简化视图可能是这样的(我没有显示时间戳等一些字段来保持简单,但这些很容易添加)。

首先创建您的 UDT:

CREATE TYPE user_follows (
    followed_username text,
    street text,
);

CREATE TYPE msg (
    from_user text,
    body text
);

接下来我们创建你的基地table:

CREATE TABLE users_by_username (
    username text PRIMARY KEY,
    email text,
    password text,
    follows list<frozen<user_follows>>,
    followed_by list<frozen<user_follows>>,
    new_messages list<frozen<msg>>,
    old_messages list<frozen<msg>>
);

现在我们创建一个按电子邮件分区的实体化视图:

CREATE MATERIALIZED VIEW users_by_email AS
    SELECT username, password, follows, new_messages, old_messages FROM users_by_username
    WHERE email IS NOT NULL AND password IS NOT NULL AND follows IS NOT NULL AND new_messages IS NOT NULL
    PRIMARY KEY (email, username);

现在让我们试一试,看看它能做什么。让我们创建一个用户:

INSERT INTO users_by_username (username , email , password )
    VALUES ( 'someuser', 'someemail@abc.com', 'somepassword');

让用户关注另一个用户:

UPDATE users_by_username SET follows = [{followed_username: 'followme2', street: 'mystreet2'}] + follows
    WHERE username = 'someuser';

让我们向用户发送消息:

UPDATE users_by_username SET new_messages = [{from_user: 'auser', body: 'hi someuser!'}] + new_messages
    WHERE username = 'someuser';

现在让我们看看 table:

中有什么
SELECT * FROM users_by_username ;

 username | email             | followed_by | follows                                                 | new_messages                                 | old_messages | password
----------+-------------------+-------------+---------------------------------------------------------+----------------------------------------------+--------------+--------------
 someuser | someemail@abc.com |        null | [{followed_username: 'followme2', street: 'mystreet2'}] | [{from_user: 'auser', body: 'hi someuser!'}] |         null | somepassword

现在让我们检查实体化视图是否正常工作:

SELECT new_messages, old_messages FROM users_by_email WHERE email='someemail@abc.com'; 

 new_messages                                 | old_messages
----------------------------------------------+--------------
 [{from_user: 'auser', body: 'hi someuser!'}] |         null

现在让我们阅读电子邮件并将其放入旧邮件中:

BEGIN BATCH
    DELETE new_messages[0] FROM users_by_username WHERE username='someuser'
    UPDATE users_by_username SET old_messages = [{from_user: 'auser', body: 'hi someuser!'}] + old_messages where username = 'someuser'
APPLY BATCH;

 SELECT new_messages, old_messages FROM users_by_email WHERE email='someemail@abc.com';

 new_messages | old_messages
--------------+----------------------------------------------
         null | [{from_user: 'auser', body: 'hi someuser!'}]

希望这能给您一些可以使用的想法。查看有关集合(即列表、映射和集合)的文档,因为它们确实可以帮助您将更多信息保存在一个 table 中,有点像 table 中的 [=] 52=].

对于 cassandra 或 noSQL 数据建模初学者,有一个过程涉及对您的应用程序进行数据建模,例如

1-了解你的数据,设计概念图
2- 详细列出您的所有问题
3- 使用定义的规则和模式映射您的查询,最好是 suitable for cassandra
4- 创建一个逻辑设计,table 使用从查询中派生的字段
5- 现在创建一个模式并测试其接受度。

如果我们建模得好,那么就很容易处理新的复杂查询、数据过载、数据一致性设置等问题。

参加这个免费的在线数据建模培训后,您会更加清晰

https://academy.datastax.com/courses/ds220-data-modeling

祝你好运!