【问题标题】:Understanding slack chat database design architecture with emojis and replies使用表情符号和回复了解 Slack 聊天数据库设计架构
【发布时间】:2022-01-19 07:22:54
【问题描述】:

我正在尝试构建一个类似于 slack chat 的聊天应用程序,我想了解他们是如何设计他们的数据库的,当有人加载聊天时它会立即返回这么多信息,哪个数据库适合这个问题,我是添加相同的屏幕截图以供参考。

最初,当我开始考虑这个问题时,我想继续使用 PostgreSQL,并始终保持表规范化以保持其干净,但随着我继续进行规范化,我开始觉得这是一个问题。

用户表

id name email
1 John john@gmail.com
2 same sam@gmail.com

频道表

id channel_name
1 Channel name 1
2 Channel name 2

参与者表

id user_id channel_id
1 1 1
2 1 2
3 2 1

聊天表

id user_id channel_id parent_id message_text total_replies timestamp
1 1 1 null first message 0 -
2 1 2 1 second message 10 -
3 1 3 null third message 0 -

聊天表的列名 parent_id 告诉它是父消息还是子消息我不想使用递归子消息,所以这很好

表情符号表

id user_id message_id emoji_uni-code
1 1 12 U123
2 1 12 U234
3 2 14 U456
4 2 14 U7878
5 3 14 U678

一个人可以对同一条消息使用多个表情符号做出反应

当有人加载时,我想获取插入到表中的最后 10 条消息 所有已对每条消息和回复做出反应的表情符号,就像您在图像中看到的那样,上面写着 1 条回复与人的个人资料图片(这可以超过 1 条)

现在要获取这些数据,我必须加入所有表,然后获取数据,这在后端可能是一项非常繁重的工作,考虑到这将非常频繁。

我想我会在 Chat 表中再添加两列,它们是 profile_replies 和 emoji_reactions_count,它们都是 bson 数据类型来存储类似这样的数据

这用于 emoji_reactions_count 列

这也有两种方式,一种是计数方式

{
  "U123": "123",// count of reactions on an emoji
  "U234": "12"
}

当有人做出反应时,我会更新计数并从表情符号表中插入或删除行,在这里我有一个问题,任何消息的表情符号更新过于频繁可能会变慢?因为每次有人对表情符号做出反应时,我都需要更新上表中的计数

像这样存储用户 ID 和计数,这样看起来更好,我可以完全摆脱 Emojis 表

{
  "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],
  },
}

这用于 profile_replies 列

[
  {
    "name": 'john',
    "profile_image": 'image url',
    "replied_on": timestamp
  },
  ... with similar other objects
]

这看起来不错的解决方案还是我可以做些什么来改进或者我应该切换到一些 noSQL 数据库,如 mongodb 或 cassandra?我考虑过 mongodb,但这看起来也不是很好,因为当数据呈指数增长时连接速度很慢,但相对而言,这在 sql 中不会发生。

【问题讨论】:

  • 尝试向开源学习:google.com/search?q=instant+chat+site%3Agithub.com。 “表情符号表”似乎有点矫枉过正。 Postgres 可以很好地处理 JSON 数据,只需将 emoji JSON 列添加到“聊天表”即可。 “最近 10 条消息”有点模糊,你如何处理最近对 2 个月大的消息的回复?如果它是一个宠物项目 - 根据需要尝试和重构。你会从自己的错误中学到比别人宣称的最佳实践更多的东西。特别是如果您在某个时候更改数据库引擎。
  • 您能解释一下您预期的瓶颈吗?例如,您希望哪张桌子最大,它必须承受多少记录?如果您正在考虑为最多 10K 活跃用户的应用程序,答案可能非常明显。

标签: sql database mongodb postgresql cassandra


【解决方案1】:

尽管老实说这更像是一次讨论,并且对这样的问题没有完美的答案,但我会尝试指出重建 Slack 时可能需要考虑的事项:

  1. 表情符号表: 由于@Alex Blex 在聊天软件的一开始就可以忽略不计。稍后,它们可能会被您的应用程序中的某个缓存、中间件或视图中的某个地方或任何地方注入,或者直接与您的消息一起存储。不需要在数据库端加入任何东西。
  2. 工作区: Slack 组织在工作区中,您可以在其中与同一用户一起参与。每个工作区可以有多个频道,每个频道可以有多个客人。每个用户都可以加入多个工作区(作为管理员、正式成员、单通道或多通道访客)。尝试从这个想法开始。
  3. 频道: 我会将频道措辞重构为例如对话,因为基本上(个人意见)我认为例如之间没有太大区别。一个有 10 名成员的频道和一个涉及 5 人的定向对话,除了以下事实:用户可以稍后加入(打开)频道并查看以前的消息,这对于封闭的频道和直接消息是不可能的。

现在是您实际的数据库布局问题:

  • 稍后当您开发包含各种统计信息的管理仪表板但客户端绝对不需要时,添加诸如 reply_count 或 profile_replies 之类的列会非常方便。
  • 假设您的客户端在加入/启动客户端(然后显然经常更新客户端的缓存)时执行了一个小调用以“获取工作区成员”,则无需将用户数据与消息一起存储,即使存在如果有 1000 个成员在同一个工作区中,那么应该只有几 MiB 的信息。
  • 假设您的客户通过调用“获取最近的工作区对话”(当然,您可以根据是否公开和加入进行过滤)执行相同的操作,您将拥有一个很好的频道列表,其中列出了您已经加入的频道以及最后的人你已经谈过了。
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);
...

所以基本上,每当用户加入新对话时,您对数据库的查询将是:

SELECT * FROM message WHERE workspace_id = 1234 ORDER BY created_dt DESC LIMIT 25;

当您开始向上滚动时,它将是:

SELECT * FROM message WHERE workspace_id = 1234 AND conversation_id = 1234 and id < 123456789 ORDER BY created_dt DESC LIMIT 25;

等等...如您所见,如果您另外添加类似 INDEX(如果使用分区可能会有所不同),您现在可以通过工作区和对话非常有效地选择消息:

create index idx_message_by_workspace_conversation_date
    on message (workspace_id, conversation_id, created_dt)
    where (is_deleted = false);

对于消息格式,我会使用类似于 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"
  }
}

imo 的一个非常复杂的问题是有效地确定每个用户已阅读了哪些消息。我不会详细介绍如何发送推送通知,因为这应该由您的后端应用程序完成,而不是通过轮询数据库。 使用以前从“获取最近的工作区对话”中收集的数据(类似SELECT * FROM user_conversations ORDER BY last_read_dt DESC LIMIT 25 应该这样做,在您的情况下,您必须添加 last_read_message_id 和 last_read_dt 的 Participants 表),然后您可以进行查询以获取哪些消息没有已阅读:

  • 一个返回消息的小存储函数
  • 返回这些消息的 JOIN 语句
  • 返回这些消息的 UNNEST / LATERAL 语句
  • 可能是我目前没有想到的其他事情。 :)

最后但并非最不重要的一点是,我强烈建议不要尝试重建 Slack,因为要涵盖的主题太多了,比如安全和加密、API 和集成等等......

【讨论】:

  • graphql和数据库集成会不会比较简单?还是会有大量的开销工作?我认为 graphql 会有所帮助,尤其是在客户端减少过度获取和获取不足。客户端可以在一个网络请求中提取所需的数据
猜你喜欢
  • 2016-04-27
  • 2012-10-19
  • 2021-09-26
  • 2014-02-03
  • 2015-05-23
  • 2015-12-06
  • 2014-10-01
  • 2015-12-31
  • 1970-01-01
相关资源
最近更新 更多