頂級大廠Quora如何最佳化資料庫效能?

資料庫工作筆記發表於2023-12-04

來源:JavaEdge


Quora 的流量涉及大量閱讀而非寫入,一直致力於最佳化讀和資料量而非寫。

0  資料庫負載的主要部分

  • 讀取
  • 資料量
  • 寫入

1 最佳化讀取

1.1 不同型別的讀需要不同最佳化

① 複雜查詢,如連線、聚合等

在查詢計數已成為問題的情況下,它們在另一個表中構建了計數,以便它們可以直接讀取計數值而非計算計數。

② 大型掃描

他們使用 LIMIT 改變它或使用分頁

③ 模式與查詢之間不匹配

若:

  • 無很好的索引
  • 或索引沒有足夠的列
  • 或索引中的列順序對查詢來說不是最佳

則查詢可能很慢,可能對資料庫造成很大負載。

這種情況下,通常會修改索引以對查詢進行最佳化。有時查詢也可修改以對索引進行最佳化。如:

  • 刪除 select 子句中不必要的列(特別是索引中不存在的列)
  • 刪除 order by 子句,改為在客戶端上排序(MySQL CPU 一般比客戶端 CPU 更寶貴)
  • 若該查詢提供的功能不再重要,可完全刪除查詢

1.2 高 QPS 查詢

即使使用了最佳化的 SQL 和良好的模式,高 QPS查詢也給資料庫帶來很大負載。有時可能表示快取效率低下(甚至沒快取)。

① 低效的快取是否導致了高 QPS 查詢?

快取通常用於減少資料庫 QPS。快取鍵的選擇可以極大地影響快取的效率:

  • 若快取鍵過於具體或狹窄,可能導致資料庫出現高 QPS
  • 若快取鍵太寬泛,每次查詢都會從資料庫中拉取大量資料

② 對使用者語言表的查詢

我們有一個表跟蹤使用者使用的語言資訊。通常會查詢資料庫以檢視使用者 U 是否使用語言 L。使用(uid,language_id)作為快取鍵看起來合理。如快取未命中,將為該 uid 和 language_id 查詢資料庫表。

因此,將快取鍵更改為僅使用 uid 確實有意義,快取值將是有關使用者使用的所有語言的資訊。

以上述方式更改快取鍵,會增加從庫表中每次查詢獲取的資料量,但它將 QPS 減少超過 90%。大多數使用者只使用一或幾種語言。因此,大多數情況,新的查詢並沒有拉取比以前更多的資料,這是一個顯然的最佳化!

③ 查詢 A2A(ask to answer)表

這裡我們處理 3 個實體間的關係,即使用者(誰提問或關注問題)、問題和回答者,這比 2 個實體之間的關係更不常見。

通常產品邏輯是查詢:

  • 使用者已請求過的所有回答者,使用快取鍵 (question_id, user_id)
  • 請求過相同回答者回答一個問題的所有使用者,使用快取鍵 (question_id, answerer_id)

綜上,A2A 表的 QPS 非常高,這意味著上述快取效果並不明顯。上述兩個快取都在使用 2 個實體作為快取鍵question_id 和 user_id(可以是提問者或回答者)。

潛在快取鍵數量巨大,因為它是問題數和使用者數的乘積,其中只有很少的組合實際上在表中有資料。所以它可看作一個稀疏的資料集,有2維。

大多數問題的 A2A 請求數量相對較少,但有少數問題的 A2A 數量要多得多。因此,新增額外快取,該快取包含問題的 A2A,最多限制為 N 個,以便我們捕獲大多數問題。該快取的鍵只是 question_id。如快取列表大小小於N,我們知道快取是完整的。否則,快取不完整,我們不會使用快取。

這額外快取幫助顯著減少 A2A 表上的 QPS(在 50% 到 66% 的範圍內)。還對產品邏輯進行了其他更改,以提高效率,但 QPS 的減少大部分來自額外快取。

1.3 一維資料集中的稀疏資料

Quora 在快取方面經常遇到的另一個問題是:稀疏一維資料集。如可能需要查詢資料庫,看某問題是否需重定向到另一問題(如同一個問題被重新發布,就可能發生這種情況)。

絕大多問題不需要重定向,所以 Quora 只會獲取幾個“重定向”,而大量“不重定向”。

當他們只是快取了 question_id ,快取中就會填滿不用,只有幾個重定向。這在快取中佔用大量空間,且由於“重定向”數量如此稀疏,也會導致大量快取未命中。

相反,他們開始快取範圍。如 question id 123–127的任一問題都沒重定向,那麼他們會將該範圍快取為所有問題均為 No,而不是快取每個單獨的 question id。

這大大降低此類查詢的資料庫負載,QPS 下降 90%。

2 最佳化表佔用空間

由於以下幾個原因,表大小很重要:

  • 儲存更多資料的成本更高
  • 隨表增長,適應資料庫緩衝池的資料百分比會變小,即IO會逐漸增加,效能會逐漸下降
  • 備份和恢復時間會隨表大小線性增長。雖然備份是從 MySQL 副本完成的,但我們也會從副本讀資料。在備份期間,MySQL副本效能略有下降
  • 隨表增長,備份大小也在增長,導致備份儲存成本隨時間增長

顯然,對不需要永久儲存的資料,制定最佳保留策略有助減少表大小 —— 使用 MyRocks 減少表大小

  • 有一些表對於表所有者來說無法接受任何資料的刪除。為此研究使用 MyRocks 來減小空間使用
  • MySQL 中的表可能使用更復雜的模式和查詢。所以他們希望謹慎使用 MyRocks。作為分片專案的一部分,已對 MySQL 中最大的表進行分片,這是在 MySQL 在 Quora 的分片中記錄
  • 此表是基於自增列範圍進行分片的,與基於時間的分片接近,因為自增列值隨時間增加
  • 大多數查詢訪問最近的分片。包含 18 個月以上舊資料的較舊分片對日常業務相對不太關鍵

因此,他們決定按如下方式將較舊的分片移至 MyRocks。 有個工具可將 MySQL 表從一個 MySQL 主伺服器移動到另一個主伺服器。每個分片實際上是一個 MySQL 表。他們能夠使用該工具按如下方式將包含舊資料的 MySQL 分片轉換為 MyRocks 分片:

  • 在 MyRocks 主伺服器上使用相同的模式建立一個新的空表,但使用 RocksDB 儲存引擎
  • 使用該工具複製資料並從 MySQL 主伺服器重放binlog(二進位制日誌)到 MyRocks 主伺服器。(該工具已被修改為跳過在目標主機上建立表,因為它已經在前一步中建立過。)
  • 執行陰影讀取測試以驗證 MyRocks 分片返回的結果與 MySQL 分片的結果相同。
  • 將流量切換到 MyRocks 分片。(這類似於我們在將 MySQL 表從一個 MySQL 主伺服器移動到另一個 MySQL 主伺服器時執行的切換。源主機上的表被重新命名以停止新寫入,然後在重放趕上後,該表的流量會切換到目標主機。)
  • 對於非鍵值儲存表使用 MyRocks 是我們的一個重大舉措。 根據表的不同,空間使用量的減少也有差異。對於上面提到的第一個表,我們看到每個已移動的分片使用的空間減少了 80% 以上! 對於第二個表,我們看到每個已移動的分片使用的空間只減少了約 50-60%

3 最佳化寫入

有時複製延遲警報,因為 MySQL複製預設情況下會在副本上序列重放主伺服器上的併發寫。在主伺服器上並行寫入而在副本上序列重放寫入並不適合擴充套件寫入,特別是如果他們使用帶多核 CPU 的機器。

MySQL 提供兩種方法實現這點,如下所述。兩種方法中都需使用 slave_parallel_workers 配置並行度。

  1. slave_parallel_type=LOGICAL_CLOCK(從 MySQL 8.0.26 開始為 replica_parallel_type)
  • MySQL 5.7開始可用。即使所有表都在同一邏輯資料庫中,它也可以在副本上並行執行寫。
  1. slave_parallel_type=DATABASE(從 MySQL 8.0.26 開始為 replica_parallel_type)
  • 這需要表位於多個邏輯資料庫中才能並行執行寫
  • 增強儲存在 zk 中的資料庫配置,以跟蹤表所在的邏輯資料庫。將此資訊儲存在 zk 而非程式碼庫或靜態配置中,允許動態更改現有表的邏輯資料庫。大多數表都位於預設邏輯資料庫,因此只需要為不在預設邏輯資料庫中的表保留此資訊
  • MySQL alter table 語句可用於更改表的邏輯資料庫,如 alter table <logical_db1>.table rename <logical_db2>.mytable。它不復制資料,只是將底層 ibd 檔案從一個目錄移動到另一個目錄,速度很快。移動表後,我們還會在 zk 更新資料庫配置,以便應用程式可找到該表
  • 他們將一個表移動到其自己的邏輯資料庫並啟用並行複製。有助減少包含該表的 MySQL 副本上的複製延遲。

4 結論

學習了世界級大廠如何使用各種技術的組合來最佳化資料庫中的讀取、寫入和空間使用。你們公司如何最佳化的呢?歡迎和我一起交流。

參考:

  • https://www.percona.com/blog/scaling-mysql-a-good-problem-to-have/

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2998588/,如需轉載,請註明出處,否則將追究法律責任。

相關文章