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),並在應用程式層級“手動”,您會注意到分區鍵的值已儲存在表本身的名稱中。

所以如果你是這樣的話 您是否非常擔心儲存的資料量?,然後您可以擺脫這些“額外”字段並處理特定的表。 確實,在這種情況下,多個部分的所有選擇都必須轉移到應用程式端。

來源: www.habr.com

添加評論