卡桑德拉数据模型适用于简单消息应用程序

16

我正在尝试学习Cassandra,发现最好的方法是从创建一个非常简单和小的应用程序开始。因此,我正在创建一个基本的消息应用程序,它将使用Cassandra作为后端。 我想要做到以下几点:

  • 用户将使用用户名、电子邮件和密码创建帐户。电子邮件和密码可以随时更改。
  • 用户可以将另一个用户添加为他们的联系人。用户可以通过搜索他们的用户名或电子邮件来添加联系人。联系人不需要是相互的,这意味着如果我添加一个用户,他们就成为我的联系人,我不需要等待他们像在Facebook中那样接受/批准任何东西。
  • 从一个用户发送消息到另一个用户。发送者需要能够查看他们发送的消息(按时间排序)和发送给他们的消息(按时间排序)。当用户打开应用程序时,我需要检查数据库是否有该用户的新消息。我还需要标记是否已读取该消息。

由于我来自关系型数据库领域,我的关系型数据库看起来会像这样:

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

我不知道如何创建下一个部分。 对于消息传递,我考虑使用这个表格,因为它创建了宽行,从而使消息排序变得更容易。 我需要确保消息能够回答两个问题。 首先,它需要能够向用户显示所有已收到的消息并能够显示新的未读消息。 这是一个基本模型,但我不确定如何使其更有效率?

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列来“粘合”用户和消息,以及使用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];

干杯

2个回答

15

从关系数据库背景转换到Cassandra时,适应Cassandra的限制总是一件困难的事情。由于我们尚无法在Cassandra中执行联接操作,因此您通常希望将尽可能多的内容压缩到单个表中。在您的情况下,这将是users_by_username表。

Cassandra有一些功能可以帮助您实现这一点。

由于您是新手,您可以使用目前处于beta发布状态的Cassandra 3.0版本。在3.0版本中有一个很好的功能叫做“materialized views”。这将允许您将users_by_username作为基本表,并创建users_by_email作为材料化视图。然后,当您更新基本表时,Cassandra会自动更新视图。

另一个有助于您的功能是用户定义类型(在C* 2.1及更高版本中)。您可以将跟随者和消息的结构作为UDT创建,而不是为其创建单独的表,然后在用户表中保留这些类型的列表。

因此,您的模式的简化视图可能如下所示(我没有显示一些字段(如时间戳)以使其简单,但很容易添加)。

首先创建您的UDT:

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

CREATE TYPE msg (
    from_user text,
    body text
);

接下来我们将创建您的基础表:

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

现在让我们来看看表格中有什么:

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!'}]

希望这能给您一些可用的想法。查看有关集合(即列表、映射和集合)的文档,因为它们可以帮助您在一个表中保留更多的信息,并且有点像表中的表。


2
对于Cassandra或NoSQL数据建模初学者,有一个涉及数据建模应用的过程,如下:
1- 了解您的数据,设计概念图 2- 详细列出所有查询 3- 使用定义的规则和模式映射您的查询,最适合Cassandra 4- 创建逻辑设计,表格中包含从查询派生的字段 5- 现在创建架构并测试其接受性。
如果我们进行良好的建模,则易于处理诸如新的复杂查询、数据超载、数据一致性等问题。
在完成这个免费的在线数据建模培训后,您将会更加清晰。
祝好运!
链接:https://academy.datastax.com/courses/ds220-data-modeling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接