Модель данных Cassandra для простого приложения для обмена сообщениями

Я пытаюсь изучить 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];

Приветствия

Ответы

Ответ 1

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

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

Поскольку вы новичок в Cassandra, вы, вероятно, можете использовать Cassandra 3.0, который в настоящее время находится в бета-версии. В 3.0 есть хорошая функция, называемая материализованными видами. Это позволит вам иметь user_by_username в качестве базовой таблицы и создать user_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', '[email protected]', '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 | [email protected] |        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='[email protected]'; 

 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='[email protected]';

 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

Удачи!