我们已经成功设计了我们的PostgreSQL数据库用于存储信件的结构,一年过去了,用户正在积极填充它,现在它包含 数百万条记录,然后……有些东西开始变慢。
第 1 部分:设计基础框架 - 第 2 部分:“以营利为目的”划分
事实是, 随着表大小的增加,索引的“深度”也随之增加。 - 尽管是对数。 但随着时间的推移,这会迫使服务器执行相同的读/写任务 处理多倍的数据页比一开始。
这是它来救援的地方 切片.
请注意,我们不是在谈论分片,即在不同数据库或服务器之间分配数据。 因为即使将数据划分为 一些 服务器,您将无法摆脱索引随着时间的推移而“膨胀”的问题。 显然,如果您有能力每天投入运行一台新服务器,那么您的问题将不再存在于特定数据库的层面。
我们将考虑的不是“在硬件中”实现分区的具体脚本,而是方法本身——什么以及如何应该“切成片”,以及这种愿望会导致什么。
概念
让我们再次定义我们的目标:我们希望确保今天、明天和一年后,PostgreSQL 在任何读/写操作期间读取的数据量保持大致相同。
对于任何 按时间顺序累积的数据 (消息、文档、日志、档案...)作为分区键的自然选择是 活动日期/时间。 在我们的例子中,这样的事件是 发送消息的那一刻.
请注意,用户几乎总是 只使用“最新”的 这样的数据 - 他们读取最新消息,分析最新日志,...不,当然,他们可以及时回滚,但他们很少这样做。
根据这些限制,很明显,最佳消息解决方案是 “每日”部分 - 毕竟,我们的用户几乎总是会阅读“今天”或“昨天”想到的内容。
如果我们在一天中几乎只写和读一个部分,那么这也给了我们 更有效地利用内存和磁盘 - 因为所有部分索引都很容易装入 RAM,与整个表中“大而胖”的部分索引相反。
一步步
总的来说,上面所说的一切听起来都是一笔持续的利润。 这是可以实现的,但为此我们必须努力 - 因为 分割其中一个实体的决定导致需要“看到”相关联的实体.
消息、其属性和预测
由于我们决定按日期剪切消息,因此划分依赖于它们的实体属性(附加文件、收件人列表)是有意义的,并且 也可以按消息日期.
由于我们的典型任务之一是精确查看消息寄存器(未读、传入、全部),因此将它们“吸引”到按消息日期进行分区也是合乎逻辑的。
我们将分区键(消息日期)添加到所有表:收件人、文件、注册表。 您不必将其添加到消息本身,而是使用现有的日期时间。
线程
由于多条消息只有一个主题,因此无法在同一模型中“剪切”它;您必须依赖其他东西。 在我们的例子中这是理想的 信件中第一条消息的日期 ——事实上,就是这个话题的创造时刻。
将分区键(主题日期)添加到所有表:主题、参与者。
但现在我们同时遇到两个问题:
- 我应该在哪个部分查找有关该主题的消息?
- 我应该在消息的哪个部分查找主题?
当然,我们可以继续在所有部分进行搜索,但这将是非常悲伤的,并将抵消我们所有的胜利。 因此,为了知道到底在哪里查看,我们将建立指向部分的逻辑链接/指针:
- 我们会在留言中添加 主题日期字段
- 让我们添加到主题中 消息日期设置 此对应关系(可以是单独的表,也可以是日期数组)
由于每个单独信件的消息日期列表几乎不会有任何修改(毕竟,几乎所有消息都落在相邻的 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
);
节省一大笔钱
好吧,如果我们不使用怎么办?
所以如果你是这样的话 您是否非常担心存储的数据量?,然后您可以摆脱这些“额外”字段并处理特定的表。 确实,在这种情况下,多个部分的所有选择都必须转移到应用程序端。
来源: habr.com