waq*_*waq 3 sql database postgresql mongodb cassandra
我正在尝试构建一个类似于 slack 聊天的聊天应用程序,我想了解他们是如何设计数据库的,当有人加载聊天时,它会立即返回如此多的信息,哪个数据库适合这个问题,我添加了屏幕截图一样的,供参考。
最初,当我开始考虑这个问题时,我想继续使用 PostgreSQL 并始终保持表规范化以保持干净,但随着我继续进行,规范化开始感觉像是一个问题。
用户表
| ID | 姓名 | 电子邮件 |
|---|---|---|
| 1 | 约翰 | 约翰@gmail.com |
| 2 | 相同的 | sam@gmail.com |
频道表
| ID | 频道名称 |
|---|---|
| 1 | 频道名称1 |
| 2 | 频道名称2 |
参加者表
| ID | 用户身份 | 频道号 |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
聊天桌
| ID | 用户身份 | 频道号 | 父 ID | 消息文本 | 回复总数 | 时间戳 |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 无效的 | 第一条消息 | 0 | - |
| 2 | 1 | 2 | 1 | 第二条消息 | 10 | - |
| 3 | 1 | 3 | 无效的 | 第三条消息 | 0 | - |
聊天表的列名称为parent_id,它告诉我它是父消息还是子消息我不想使用递归子消息,所以这很好
表情符号表
| ID | 用户身份 | 消息ID | emoji_uni 代码 |
|---|---|---|---|
| 1 | 1 | 12 | U123 |
| 2 | 1 | 12 | U234 |
| 3 | 2 | 14 | U456 |
| 4 | 2 | 14 | U7878 |
| 5 | 3 | 14 | U678 |
一个人可以对同一条消息使用多个表情符号做出反应
当有人加载时,我想获取插入表中的最后 10 条消息,其中包含对每条消息和回复做出反应的所有表情符号,就像您在图像中看到的那样,其中显示 1 个带有个人资料图片的回复(这可以超过 1 个)
现在要获取这些数据,我必须连接所有表,然后获取数据,考虑到这种情况会非常频繁,这在后端可能是一项非常繁重的工作。
我的想法是我会在聊天表中添加两列,即 profile_replies 和 emoji_reactions_count ,两者都是bson数据类型来存储类似这样的数据
这适用于 emoji_reactions_count 列
这也有两种方式,一种是仅计数方式
{
"U123": "123",// count of reactions on an emoji
"U234": "12"
}
Run Code Online (Sandbox Code Playgroud)
当有人做出反应时,我会更新计数并从表情符号表中插入或删除行,这里我有一个问题,任何消息上的表情符号更新过于频繁可能会变得很慢?因为每次有人用表情符号做出反应时我都需要更新上表中的计数
或者
像这样存储用户 ID 和计数,这看起来更好我可以完全摆脱表情符号表
{
"U123": {
"count": 123, // count of reactions on an emoji
"userIds": [1,2,3,4], // list of users ids who all have reacted
},
"U234": {
"count": 12,
"userIds": [1,2,3,4],
},
}
Run Code Online (Sandbox Code Playgroud)
这适用于 profile_replies 列
[
{
"name": 'john',
"profile_image": 'image url',
"replied_on": timestamp
},
... with similar other objects
]
Run Code Online (Sandbox Code Playgroud)
这看起来是不错的解决方案吗?或者我可以做些什么来改进,或者我应该切换到一些 noSQL 数据库,如 mongodb 或 cassandra?我考虑过 mongodb,但这看起来也不是很好,因为当数据呈指数级增长时,连接速度很慢,但相对而言,这种情况在 sql 中不会发生。
小智 6
尽管这实际上更像是一次讨论,而且这样的问题没有完美的答案,但我会尝试指出重建 Slack 时您可能需要考虑的事项:
现在针对您的实际数据库布局问题:
create table message
(
id bigserial primary key,
workspace_id bigint not null,
conversation_id bigint not null,
parent_id bigint,
created_dt timestamp with time zone not null,
modified_at timestamp with time zone,
is_deleted bool not null default false,
content jsonb
)
partition by hash (workspace_id);
create table message_p0 partition of message for values with (modulus 32, remainder 0);
create table message_p1 partition of message for values with (modulus 32, remainder 1);
create table message_p2 partition of message for values with (modulus 32, remainder 2);
...
Run Code Online (Sandbox Code Playgroud)
因此,基本上,每当用户加入新对话时,您对数据库的查询将是:
SELECT * FROM message WHERE workspace_id = 1234 ORDER BY created_dt DESC LIMIT 25;
Run Code Online (Sandbox Code Playgroud)
当你开始向上滚动时,它会是:
SELECT * FROM message WHERE workspace_id = 1234 AND conversation_id = 1234 and id < 123456789 ORDER BY created_dt DESC LIMIT 25;
Run Code Online (Sandbox Code Playgroud)
等等...正如您已经看到的,如果您另外添加一个索引(如果您使用分区,则可能会有所不同),您现在可以通过工作区和对话非常有效地选择消息:
create index idx_message_by_workspace_conversation_date
on message (workspace_id, conversation_id, created_dt)
where (is_deleted = false);
Run Code Online (Sandbox Code Playgroud)
对于消息格式,我会使用与 Twitter 类似的格式,有关更多详细信息,请查看他们的官方文档: https ://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet
当然,例如您的客户端 v14 应该知道如何“渲染”从 v1 到 v14 的所有对象,但这就是消息格式版本控制的伟大之处:它是向后兼容的,您可以随时启动支持更多功能的新格式,的原始示例content可能是:
{
"format": "1.0",
"message":"Hello World",
"can_reply":true,
"can_share":false,
"image": {
"highres": { "url": "https://www.google.com", "width": 1280, "height": 720 },
"thumbnail": { "url": "https://www.google.com", "width": 320, "height": 240 }
},
"video": null,
"from_user": {
"id": 12325512,
"avatar": "https://www.google.com"
}
}
Run Code Online (Sandbox Code Playgroud)
在我看来,一个非常复杂的问题是有效地确定每个用户已阅读哪些消息。我不会详细介绍如何发送推送通知,因为这应该由后端应用程序完成,而不是通过轮询数据库来完成。使用之前从“获取最近的工作区对话”收集的数据(类似于SELECT * FROM user_conversations ORDER BY last_read_dt DESC LIMIT 25应该做的事情,在您的情况下,您必须在参与者表中添加last_read_message_id和last_read_dt),然后您可以执行查询以获取哪些消息尚未被读取:
最后但并非最不重要的一点是,我强烈建议不要尝试重建 Slack,因为还有很多主题需要涵盖,例如安全性和加密、API 和集成等等......