Модель данных 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)

прочитав пару учебников Кассандры, я подумал о том, как смоделировать базу данных. Моя главная задача-очень эффективно моделировать базу данных. Поэтому я стараюсь избегать таких вещей, как вторичные индексы и т. д. Это моя модель до сих пор:

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
)

для распространения данные равномерно и читать минимальное количество перегородок (надеюсь только один) я могу найти пользователя по его имени или электронной почте. Недостатком этого, очевидно, я удваиваю свои данные, но стоимость хранения довольно дешева, поэтому я считаю, что это хороший компромисс вместо использования вторичных индексов. Последний вход в систему также должен быть записан дважды, но Кассандра эффективна при записи, поэтому я считаю, что это хороший компромисс.

для контактов я не могу придумать ни одного другой способ смоделировать это, поэтому я смоделировал его очень похожим на то, как я бы в реляционной базе данных. Это довольно денормализованный дизайн, который должен быть хорош для производительности в соответствии с книгами, которые я прочитал?

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

Я также рассматривал использование таких вещей, как статические столбцы, чтобы "склеить" пользователя и сообщения, а также наборы для хранения контактных отношений, но из моего узкого понимания до сих пор способ, который я представил, более эффективен. Я спрашиваю, есть ли идеи по повышению эффективности этой модели, есть ли лучшие практики делайте то, что я пытаюсь сделать, или если есть какие-то скрытые проблемы, с которыми я могу столкнуться с этим дизайном?

в заключение, я пытаюсь моделировать вокруг запросов. Если бы я использовал базы данных отношений, это были бы по существу запросы, на которые я хочу ответить:

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 ответов


Да, это всегда борьба за адаптацию к ограничениям Кассандры, когда она исходит из реляционной базы данных. Поскольку у нас пока нет роскоши делать joins в Cassandra, вы часто хотите втиснуть столько, сколько сможете в один стол. В вашем случае это будет таблица users_by_username.

есть несколько особенностей Cassandra, которые должны позволить вам сделать это.

поскольку вы новичок в Cassandra, вы, вероятно, можете использовать Cassandra 3.0, который в настоящее время в бета-версии. В 3.0 есть хорошая функция под названием материализованных представлений. Это позволит вам иметь users_by_username в качестве базовой таблицы и создать users_by_email как материализованное представление. Затем Cassandra автоматически обновит представление при обновлении базовой таблицы.

еще одна функция, которая поможет вам, - это пользовательские типы (В C* 2.1 и более поздних версиях). Вместо того, чтобы создавать отдельные таблицы для подписчиков и сообщений, вы можете создать структуру таких как UDT, а затем в таблице user храните списки этих типов.

таким образом, упрощенное представление вашей схемы может быть таким (я не показываю некоторые поля, такие как временные метки, чтобы сохранить это простым, но их легко добавить).

сначала создайте свой 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!'}]

надеюсь, это даст вам некоторые идеи, которые вы можете использовать. Взгляните на документацию по коллекции (т. е. списки, карты и наборы), поскольку они действительно могут помочь вам сохранить больше информации в одной таблице и похожи на таблицы в таблице.


для начинающих cassandra или NoSQL Data modelling существует процесс, связанный с моделированием данных вашего приложения, например

1-Поймите свои данные, создайте концептуальную диаграмму
2 - перечислите все ваши запросы подробно
3-сопоставьте свои запросы, используя определенные правила и шаблоны, лучше всего подходящие для cassandra
4-Создайте логический дизайн, таблицу с полями, полученными из запросов
5 - Теперь создайте схему и проверьте ее принятие.

Если мы моделируем его ну, тогда легко обрабатывать такие вопросы, как новые сложные запросы, данные над загрузкой, согласованность данных setc.

после этого бесплатного онлайн-обучения моделированию данных вы получите больше ясности

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

Удачи!