Messenger 数据库(第 2 部分):“为了盈利”进行分区

我们已经成功设计了我们的PostgreSQL数据库用于存储信件的结构,一年过去了,用户正在积极填充它,现在它包含 数百万条记录,然后……有些东西开始变慢。

Messenger 数据库(第 2 部分):“为了盈利”进行分区
事实是, 随着表大小的增加,索引的“深度”也随之增加。 - 尽管是对数。 但随着时间的推移,这会迫使服务器执行相同的读/写任务 处理多倍的数据页比一开始。

这是它来救援的地方 切片.

请注意,我们不是在谈论分片,即在不同数据库或服务器之间分配数据。 因为即使将数据划分为 一些 服务器,您将无法摆脱索引随着时间的推移而“膨胀”的问题。 显然,如果您有能力每天投入运行一台新服务器,那么您的问题将不再存在于特定数据库的层面。

我们将考虑的不是“在硬件中”实现分区的具体脚本,而是方法本身——什么以及如何应该“切成片”,以及这种愿望会导致什么。

概念

让我们再次定义我们的目标:我们希望确保今天、明天和一年后,PostgreSQL 在任何读/写操作期间读取的数据量保持大致相同。

对于任何 按时间顺序累积的数据 (消息、文档、日志、档案...)作为分区键的自然选择是 活动日期/时间。 在我们的例子中,这样的事件是 发送消息的那一刻.

请注意,用户几乎总是 只使用“最新”的 这样的数据 - 他们读取最新消息,分析最新日志,...不,当然,他们可以及时回滚,但他们很少这样做。

根据这些限制,很明显,最佳消息解决方案是 “每日”部分 - 毕竟,我们的用户几乎总是会阅读“今天”或“昨天”想到的内容。

如果我们在一天中几乎只写和读一个部分,那么这也给了我们 更有效地利用内存和磁盘 - 因为所有部分索引都很容易装入 RAM,与整个表中“大而胖”的部分索引相反。

一步步

总的来说,上面所说的一切听起来都是一笔持续的利润。 这是可以实现的,但为此我们必须努力 - 因为 分割其中一个实体的决定导致需要“看到”相关联的实体.

消息、其属性和预测

由于我们决定按日期剪切消息,因此划分依赖于它们的实体属性(附加文件、收件人列表)是有意义的,并且 也可以按消息日期.

由于我们的典型任务之一是精确查看消息寄存器(未读、传入、全部),因此将它们“吸引”到按消息日期进行分区也是合乎逻辑的。

Messenger 数据库(第 2 部分):“为了盈利”进行分区

我们将分区键(消息日期)添加到所有表:收件人、文件、注册表。 您不必将其添加到消息本身,而是使用现有的日期时间。

线程

由于多条消息只有一个主题,因此无法在同一模型中“剪切”它;您必须依赖其他东西。 在我们的例子中这是理想的 信件中第一条消息的日期 ——事实上,就是这个话题的创造时刻。

Messenger 数据库(第 2 部分):“为了盈利”进行分区

将分区键(主题日期)添加到所有表:主题、参与者。

但现在我们同时遇到两个问题:

  • 我应该在哪个部分查找有关该主题的消息?
  • 我应该在消息的哪个部分查找主题?

当然,我们可以继续在所有部分进行搜索,但这将是非常悲伤的,并将抵消我们所有的胜利。 因此,为了知道到底在哪里查看,我们将建立指向部分的逻辑链接/指针:

  • 我们会在留言中添加 主题日期字段
  • 让我们添加到主题中 消息日期设置 此对应关系(可以是单独的表,也可以是日期数组)

Messenger 数据库(第 2 部分):“为了盈利”进行分区

由于每个单独信件的消息日期列表几乎不会有任何修改(毕竟,几乎所有消息都落在相邻的 1-2 天),因此我将重点关注此选项。

总的来说,考虑到分区,我们的数据库结构采用以下形式:

表:RU,如果您讨厌表/字段名称中的西里尔字母,最好不要看

-- секции по дате сообщения
CREATE TABLE "Сообщение_YYYYMMDD"(
  "Сообщение"
    uuid
      PRIMARY KEY
, "Тема"
    uuid
, "ДатаТемы"
    date
, "Автор"
    uuid
, "ДатаВремя" -- используем как дату
    timestamp
, "Текст"
    text
);

CREATE TABLE "Адресат_YYYYMMDD"(
  "ДатаСообщения"
    date
, "Сообщение"
    uuid
, "Персона"
    uuid
, PRIMARY KEY("Сообщение", "Персона")
);

CREATE TABLE "Файл_YYYYMMDD"(
  "ДатаСообщения"
    date
, "Файл"
    uuid
      PRIMARY KEY
, "Сообщение"
    uuid
, "BLOB"
    uuid
, "Имя"
    text
);

CREATE TABLE "РеестрСообщений_YYYYMMDD"(
  "ДатаСообщения"
    date
, "Владелец"
    uuid
, "ТипРеестра"
    smallint
, "ДатаВремя"
    timestamp
, "Сообщение"
    uuid
, PRIMARY KEY("Владелец", "ТипРеестра", "Сообщение")
);
CREATE INDEX ON "РеестрСообщений_YYYYMMDD"("Владелец", "ТипРеестра", "ДатаВремя" DESC);

-- секции по дате темы
CREATE TABLE "Тема_YYYYMMDD"(
  "ДатаТемы"
    date
, "Тема"
    uuid
      PRIMARY KEY
, "Документ"
    uuid
, "Название"
    text
);

CREATE TABLE "УчастникТемы_YYYYMMDD"(
  "ДатаТемы"
    date
, "Тема"
    uuid
, "Персона"
    uuid
, PRIMARY KEY("Тема", "Персона")
);

CREATE TABLE "ДатыСообщенийТемы_YYYYMMDD"(
  "ДатаТемы"
    date
, "Тема"
    uuid
      PRIMARY KEY
, "Дата"
    date
);

节省一大笔钱

好吧,如果我们不使用怎么办? 经典切片选项 基于字段值的分布(通过触发器和继承或 PARTITION BY),并在应用程序级别“手动”,您会注意到分区键的值已经存储在表本身的名称中。

所以如果你是这样的话 您是否非常担心存储的数据量?,然后您可以摆脱这些“额外”字段并处理特定的表。 确实,在这种情况下,多个部分的所有选择都必须转移到应用程序端。

来源: habr.com

添加评论