Ruby 2.4 中 Hash Table 的效能改善

前幾天 Ruby 推出了 2.4.0 (Ruby 2.4.0 Released),其中特別被拿出來提的:「Introduce hash table improvement (by Vladimir Makarov)」。

討論串很長而且歷時很久,但可以看出來方向是提高 CPU cache 效率:

Modern processors have several levels of cache. Usually,the CPU reads one or a few lines of the cache from memory (or another level of cache). So CPU is much faster at reading data stored close to each other. The current implementation of Ruby hash tables does not fit well to modern processor cache organization, which requires better data locality for faster program speed.

中間還有拿 Redmine 當作測試項目... XD

MySQL GTID Replication 的惡搞修復

Percona 的「Database Daily Ops Series: GTID Replication」這篇在講當 MySQL 的 GTID Replication 爛掉時可能的修法,算是頗惡搞的方法,修好後還是要跑 pt-table-checksum 確認兩邊的資料是否一致,如果有狀況的話還是得拿出 pt-table-sync 同步。

第一招是用 pt-slave-restart,跳過會造成問題 SQL,讓他強制同步 (唔):

This passes the master’s UUID and it skips all global transactions breaking replication on a specific slave server[.]

第二招是 mysqlslavetrx,也是類似的作法,只是拿的是 MySQL 官方的工具來惡搞...

第三招是 Inject a Fake Transaction,其實就是手動自己做 XDDD

所以不管是哪招,做完後還是要記得跑 pt-table-{checksum,sync} 收尾,不然還是會爛掉...

GitHub 發展出來的 ALTER TABLE 方式

GitHub 解釋了他們在 MySQL 上 ALTER TABLE 的方式:「gh-ost: GitHub's online schema migration tool for MySQL」。

GitHub 的舊方式是使用 pt-online-schema-change,會遇到的問題有幾個,其中看起來只有 Non pausability 這個是真正的痛點:

Non pausability: when load on the master turns high, you wish to throttle or suspend your pending migration. However a trigger-based solution cannot truly do so. While it may suspend the row-copy operation, it cannot suspend the triggers. Removal of the triggers results in data loss. Thus, the triggers must keep working throughout the migration. On busy servers, we have seen that even as the online operation throttles, the master is brought down by the load of the triggers.

當開始後,多出來的 trigger 是沒有辦法停下來的 (停下來就代表要全部重來),而且會影響線上服務。

新的方式則是用 replication 做,多一台機器出來跑,等結束後再切換,而中間有任何過程也都很好處理:

這方法手筆比較大,不過對於系統已經有規模的組織來說不是問題... 看起來以後可以朝這個方向研究 XD

PostgreSQL 對 Vacuum 效能的改善

在「No More Full-Table Vacuums」這邊提到了 PostgreSQL 在 vacuum 時效能的大幅改善,尤其是大型資料庫在 vacuum 時需要對整個表格從頭到尾掃一次以確保 transaction id 的正確性:

Current releases of PostgreSQL need to read every page in the database at least once every 2 billion write transactions (less, with default settings) to verify that there are no old transaction IDs on that page which require "freezing".

這動作在資料量大的機器上就會吃大量資源導致各種討厭的現象:

All of a sudden, when the number of transaction IDs that have been consumed crosses some threshold, autovacuum begins processing one or more tables, reading every page. This consumes much more I/O bandwidth, and exerts much more cache pressure on the system, than a standard vacuum, which reads only recently-modified page.

而作者送了 patch 改成只會讀還沒搞定的部份:

Instead of whole-table vacuums, we now have aggressive vacuums, which will read every page in the table that isn't already known to be entirely frozen.

要注意的是,agreesive vacuum 相較於 vacuum 會多吃很多資源,但可以打散掉 (有點像一次大 GC 導致 lag 與多次 minor GC 讓程式反應時間變得比較順暢的比較):

An aggressive vacuum still figures to read more data than a regular vacuum, possibly a lot more. But at least it won't read the data that hasn't been touched since the last aggressive vacuum, and that's a big improvement.

這個功能預定在 PostgreSQL 9.6 出現,不知道會不會變 default...

PostgreSQL 9.5 將會有 Parallel Sequential Scan

在「Parallel Sequential Scan is Committed!」這邊看到 PostgreSQL 9.5 (還沒出) 將會有 Parallel Sequential Scan 的功能。

文章的作者直接拿了一個大家超常用的惡搞來示範,也就是經典的 LIKE '%word%'

rhaas=# \timing
Timing is on.
rhaas=# select * from pgbench_accounts where filler like '%a%';
 aid | bid | abalance | filler
-----+-----+----------+--------
(0 rows)

Time: 743.061 ms
rhaas=# set max_parallel_degree = 4;
SET
Time: 0.270 ms
rhaas=# select * from pgbench_accounts where filler like '%a%';
 aid | bid | abalance | filler
-----+-----+----------+--------
(0 rows)

Time: 213.412 ms

這功能真不錯 XD

MySQL 的 Index 設計技巧

Percona 的「Indexing 101: Optimizing MySQL queries on a single table」這篇講了最基本的 index 設計技巧,雖然文章裡沒提到,但最好是需要 B-treeB+ tree 的背景知識。

MySQL 的 query 大致分成幾個階段。先決定要使用哪些 index (或是完全不用),然後透過 index 抓出符合條件資料 (或是 table scan),最後再細部過濾。

以文章裡提到的「Multiple inequalities」範例裡這樣的 SQL query 來討論:

SELECT * FROM t WHERE c > 100 and b < 10 and d = 'xyz'

如果 index 是 (d, c),需要在透過這組 index 抓出資料後再過濾 b < 10 的條件。而如果 index 是 (d, b),需要在取出資料後再過濾 c > 100 的條件。也就是 B+ tree 做不到的事情,就要另外 post-processing。

另外也有提到 covering index 對效能提昇的原理,不過這就有點屬於怪招了...

利用 data attribute 與 attr() 的 Pure CSS Responsive Table

查了 MDN 的說明,原來 attr() 在 IE8 就可以用了...

在這篇文章看到純 CSS 的 Responsive Table 技巧:「Responsive Tables in Pure CSS」,目標是把這樣的表格:

在寬度較小時自動變成這樣的形式:

用了 data attribute 與 attr(),再加上 before pseudo element。

innodb_file_per_table 對於 CREATE TABLE 與 DROP TABLE 的速度

雖然平常應該不會常常用到 CREATE TABLEDROP TABLE,不過還是很有趣的 benchmark:「Is MySQL’s innodb_file_per_table slowing you down?」。

重點在這段:

  • With innodb_file_per_table=ON
    • Schema and table creation = 1m54.852s
    • Schema drops = 1m21.682s
  • With innodb_file_per_table=OFF
  • Schema and table creation = 0m59.968s
  • Schema drops = 0m54.870s

不過作者測試時沒有用 ENGINE=COMPRESSED (必須在 innodb_file_per_table 打開時才支援,而且這也是選擇打開 innodb_file_per_table 的重要因素),不知道壓縮開起來以後會差多少...

不過就算再怎麼慢,相較於 CREATE TABLEDROP TABLE 的效能,還是比較計較壓縮換來的 I/O 效能。(尤其是資料量超過記憶體大小時)

關於 RDBMS 的 Schema Migration...

在「NoSQL 大腸花」這份投影片裡面的 Page 12 有提到關於 RDBMS 的 Schema Migration:

以目前 open source 的兩個專案,MySQLPostgreSQL 來看,裡面提到的 lock 應該都不是問題...

首先是 MySQL 的部份,真的量大的網站都應該是往 InnoDB 投靠,而 pt-online-schema-change 在這個領域則是處理的很好。

Facebook 的 Mark Callaghan 曾經在 2010 年寫過一篇關於 InnoDB 的 online schema change 的原理:「Online Schema Change for MySQL」,主要是利用 Trigger 的機制,用七個步驟架構出沒有 downtime 的 online scheme change。

就算不考慮 pt-online-schema-change 這種工具,在 MySQL 5.6 開始,就有愈來愈多 ALTER TABLE 的行為是不會影響到 read/write 了:「Avoiding MySQL ALTER table downtime」。

而 PostgreSQL 的情況也差不多,常見的 ALTER TABLE (新增與刪除 column 與 index) 也都不會影響 read/write。

這些在 Stack Overflow 上有不少討論:「ALTER TABLE without locking the table?」。

MySQL 在 RDBMS 領域裡比起來的確是不怎樣,不過沒有這麼糟糕啊...

利用 pt-online-schema-change 同步 master 與 slave 的資料

在「Syncing MySQL slave table with pt-online-schema-change」這篇看到 master 與 slave 的資料不同步時,強制性同步的方法:

pt-online-schema-change --alter 'ENGINE=INNODB' D=dbname,t=tblname

由於 pt-online-schema-change 的作法是建一個新的表格,然後把舊表格的資料寫過去,而這些行為會被 replicate 到新機器上,於是就同步了...

這招有趣 XDDD