MySQL InnoDB 的 OPTIMIZE TABLE 的 Lock

Backend Twhttps://www.facebook.com/groups/616369245163622/posts/2467225396744655/ 這邊看到:

先大概回答一下假設,DELETE 後的空間是可以被同一個表格重複使用的,所以應該是還好,不過離峰時間跑一下 OPTIMIZE TABLE 也沒什麼關係就是了。

裡面提到的「13.7.2.4 OPTIMIZE TABLE Statement」(MySQL 5.7 文件) 以及「13.7.2.4 OPTIMIZE TABLE Statement」(MySQL 5.6 文件) 都有講到目前比較新的版本都已經是 Online DDL 了:(這邊抓 5.6 的文件,有支援的版本資訊)

Prior to Mysql 5.6.17, OPTIMIZE TABLE does not use online DDL. Consequently, concurrent DML (INSERT, UPDATE, DELETE) is not permitted on a table while OPTIMIZE TABLE is running, and secondary indexes are not created as efficiently.

As of MySQL 5.6.17, OPTIMIZE TABLE uses online DDL for regular and partitioned InnoDB tables, which reduces downtime for concurrent DML operations. The table rebuild triggered by OPTIMIZE TABLE is completed in place. An exclusive table lock is only taken briefly during the prepare phase and the commit phase of the operation. During the prepare phase, metadata is updated and an intermediate table is created. During the commit phase, table metadata changes are committed.

文件上有提到會有一小段 lock 的時間,不過一般來說應該不會造成太大問題。

這邊要講的是早期的經典工具 pt-online-schema-change (pt-osc),這是使用 TRIGGER-based 的方式在跑,他的範例就直接提供了一個不需要 Online DDL 支援的版本:

Change sakila.actor to InnoDB, effectively performing OPTIMIZE TABLE in a non-blocking fashion because it is already an InnoDB table:

pt-online-schema-change --alter "ENGINE=InnoDB" D=sakila,t=actor

這在早期的時候還蠻常被拿出來用的,如果還在維護一些舊系統的話還蠻推薦的...

Let's Encrypt 升級資料庫伺服器 (AMD YES?)

Let's Encrypt 升級了 MariaDB 資料庫的伺服器 (跑 InnoDB),特地寫了一篇文章出來講:「The Next Gen Database Servers Powering Let's Encrypt」。

CPU 的部份從本來的 2x Intel Xeon E5-2650 (Total 24 cores / 48 threads) 換成了 2x AMD EPYC 7542 (Total 64 cores / 128 threads),這點在本來就是 CPU 滿載的情境下改善很大:

而本來的瓶頸一解決,也使得 API 的 latency 直接降下去:

回頭看一下架構,可以看到他們提到沒有使用分散式的資料庫,而是單台 database 硬撐,驗證了即使到了 Let's Encrypt 這種規模,以暴制暴還是很有效的:

We run the CA against a single database in order to minimize complexity. Minimizing complexity is good for security, reliability, and reducing maintenance burden. We have a number of replicas of the database active at any given time, and we direct some read operations to replica database servers to reduce load on the primary.

除了 CPU 暴力外,2TB RAM 與 24 顆 NVMe SSD 的搞法也是很讚的,擺明就是用記憶體拼 cache 的量,以及用大量的 NVMe SSD 疊 IOPS。

然後硬體還在成長,看起來暴力解應該會變成以後的基本答案了...

Percona 對 MongoDB 的建議

看到「5 Things DBAs Should Know Before Deploying MongoDB」這篇,裡面給了五個建議,其中第五點頗有趣:

5) Whenever Possible, Working Set < RAM

As with any database, fitting your data into RAM will allow for faster reads than from disk. MongoDB is no different. Knowing how much data MongoDB has to read in for your queries can help you determine how much RAM you should allocate to your database.

這樣的設計邏輯很奇怪啊,你不要扯其他 database 啊,你們家主力的 InnoDB 一直都沒有推薦要 Working Set < RAM 啊,反過來才是用 InnoDB 的常態吧,而且在 PostgreSQL 上也是這樣吧 XDDD

現在上面的文章真的是挑著看了... XD

MyRocks/MariaDB 的 tuning 過程

看起來應該是找 Percona 的人幫忙轉移到 MyRocks 上,然後整理出來的成功案例:「The Road Story of a MyRocks/MariaDB Migration」。

看起來是跑在獨立機器上,而不是雲端的虛擬機上,所以不是想 scale up 就可以把硬體規格拉上去 (說不定記憶體插槽已經滿了之類的...):

Replicas run on bare metal servers, usually Dual Xeon E5 v3 or v4, with 192 GB to 384 GB of RAM.

這次遇到的主要的問題是發現效能跟不上。另外在文章裡面沒寫到,但可以猜到的是,他們目前不打算改架構,而是想要藉由改善資料庫的效能來解決問題:

The servers were close to their limits and were slow to catch up with replication after a maintenance period

後面可以看到不少過程,主要是重新編一份 MariaDB,讓 MyRocks 支援 Zstandard (MyRocks 支援 Zstandard,不過 MariaDB 內的 MyRocks 不知道為什麼關掉了...),這點大幅降低了空間的佔用。

另外是遇到 OOM 問題,在改用 jemalloc 解決記憶體用量的問題後就解決了 (這個在使用 InnoDB 的時候也算是標配了)。

不過在「Increased Read Load Over Time」那段還是看到了 workaround:

The read load was still rising a bit but at a much smaller pace. Instead of hours, it was days. That’s kind of expected given the workload and we were already planning for periodic manual compactions.

目前看起來 MyRocks 的強項主要是在省資源,但缺點就是有不少眉眉角角得小心處理。這樣的話,一般應該還是會先用 InnoDB,真的搞大了再考慮要不要換過去...

MySQL (InnoDB) 的內部狀態

Percona 老大 Peter Zaitsev 在「MySQL – A Series of Bad Design Decisions」這篇裡提到了他認為 MySQL 設計上的問題,不過裡面也提到了不少有用的指令,平常可以先熟悉一下輸出,等真的有狀況的時候才會想起來可以用這些指令。

首先是最經典的 SHOW ENGINE INNODB STATUS,算是很多文件上面都會提到的指令。可以看 InnoDB 當下的情況,藉以猜測內部現在是怎麼卡住...

另外一個是 SHOW ENGINE INNODB MUTEX,就如同 Peter Zaitsev 所提到的,這個指令想辦法抓出最重要的資訊,但不要像 SHOW ENGINE INNODB STATUS 給了那麼多。

另外當然就是 INFORMATION_SCHEMA,他甚至希望 SHOW ENGINE INNODB 系列的指令應該要被整合進去,這樣才能用 SELECT 相關的指令整理... (因為 ORDER BY 以及蠻多的指令沒辦法在 SHOW ... 上面用)

MySQL 的 binlog 對效能的影響

Percona 的 CTO Vadim Tkachenko 在比較 InnoDB 與 MyRocks 時意外發現了 binlog 會影響不少效能穩定性,再加上 MySQL 8.0 有改變 binlog 相關的預設值,所以他後續花了不少時間測試,寫了兩篇關於 binlog 對於 MySQL 效能的影響:「How Binary Logs (and Filesystems) Affect MySQL Performance」與「How Binary Logs Affect MySQL 8.0 Performance」。

第一篇是想要知道在 Percona Server 5.7 上開 binlog 的影響,做出來後可以看到明顯的效能波動 (因為 binlog 導致 flush 時效能暴跌):

而其中的 workaround 就是調整 flush 參數,讓他比較頻繁的小量寫入,而不是突然要寫很多資料。這樣一來對平均效能也許比較差,但對前端應用衝擊會比較小:

在測試可以看到 sync_binlog=1000 是個妥協點。各單位要自己去找出適合的數字:

The strict setting also comes with noted performance penalties. I will also test sync_binlog=1000 and sync_binlog=10000, which means perform synchronous writes of binary logs every 1000 and 10000 transactions, respectively.

另外有測試 ext4 與 XFS 是否有影響,就測試的結果看起來 ext4 比 XFS 好一些,但差距有限:

第二篇則是拿 MySQL 8.0 與 Percona Server 5.7 比較,可以發現在 MySQL 8.0 開啟 binlog 時有時會有不少的效能損失:

It seems that binary logs have quite an effect MySQL 8.0, and we see up to a 30% performance penalty as opposed to the 13% for Percona Server for MySQL 5.7.

雖是推銷一下自家產品在這塊還不錯 XD

MySQL 8.0 的 innodb_dedicated_server

Percona 介紹了 MySQL 8.0 將會推出的 innodb_dedicated_server 參數:「New MySQL 8.0 innodb_dedicated_server Variable Optimizes InnoDB from the Get-Go」,Oracle 官方的文件在「15.6.13 Enabling Automatic Configuration for a Dedicated MySQL Server」這邊可以翻到。

這是針對整台機器完全給 MySQL 用的情況所設計的參數。在這種情況下,可以透過 RAM 的大小以及一些簡單的公式,得到還算堪用的系統參數...

依照說明,可以看到系統會依照記憶體的大小自動計算出 innodb_buffer_pool_sizeinnodb_log_file_size 這兩個參數,並且把 innodb_flush_method 設為 O_DIRECT_NO_FSYNC (如果所在平台有支援這個值)。

不過看了一下公式,依照經驗可以設的更積極一點... 像是 Percona 文章裡提到的,當記憶體夠大時,其實可以考慮從 80% 開始調整大小 (innodb_buffer_pool_size):

For InnoDB buffer pool size (based on this article), consider allocating 80% of physical RAM for starters. You can increase it to as large as needed and possible, as long as the system doesn’t swap on the production workload.

innodb_log_file_size 則應該要分析寫入的 pattern 而不是直接看 RAM 大小。有些機器雖然很大台但幾乎沒有寫入的量,照著公式的值就偏大很多:

For InnoDB log file size, it should be able to handle one hour of writes to allow InnoDB to optimize writing the redo log to disk. You can calculate an estimate by following the steps here, which samples one minute worth of writes to the redo log. You could also get a better estimate from hourly log file usage with Percona Monitoring and Management (PMM) graphs.

不過基本上 tune 出來的值還算堪用,對於剛入手的人頗有幫助。

EnterpriseDB 打算推出的 zheap,想要解 VACUUM 問題...

前天被問到「DO or UNDO - there is no VACUUM」這篇,回家後仔細看一看再翻了一些資料,看起來是要往 InnoDB 的解法靠...

PostgreSQL 與 InnoDB 都是透過 MVCC 的概念實做 transaction 之間的互動,但兩者實際的作法不太一樣。其中帶來一個明顯的差異就是 PostgreSQL 需要 VACUUM。這點在同一篇作者八年前 (2011) 的文章就有提過兩者的差異以及優缺點:「MySQL vs. PostgreSQL, Part 2: VACUUM vs. Purge」。

UPDATE 時,InnoDB 會把新資料寫到表格內,然後把可能會被 rollback 的舊資料放到表格外:

In InnoDB, only the most recent version of an updated row is retained in the table itself. Old versions of updated rows are moved to the rollback segment, while deleted row versions are left in place and marked for future cleanup. Thus, purge must get rid of any deleted rows from the table itself, and clear out any old versions of updated rows from the rollback segment.

而被 DELETE 清除的資料則是由 purge thread 處理:

All the information necessary to find the deleted records that might need to be purged is also written to the rollback segment, so it's quite easy to find the rows that need to be cleaned out; and the old versions of the updated records are all in the rollback segment itself, so those are easy to find, too.

所以可以在 InnoDB 看到 purge thread 相關的設定:「MySQL :: MySQL 5.7 Reference Manual :: 14.6.11 Configuring InnoDB Purge Scheduling」,負責處理這些東西。

而在 PostgreSQL 的作法則是反過來,舊的資料放在原來地方,新資料另外存:

PostgreSQL takes a completely different approach. There is no rollback tablespace, or anything similar. When a row is updated, the old version is left in place; the new version is simply written into the table along with it.

新舊資料的位置其實還好,主要是因為沒有類似的地方可以記錄哪些要清:

Lacking a centralized record of what must be purged, PostgreSQL's VACUUM has historically needed to scan the entire table to look for records that might require cleanup.

這也使得 PostgreSQL 裡需要 autovacuum 之類的程序去掃,或是手動跑 vacuum。而在去年 (2017) 的文章裡也有提到目前還是類似的情況:「MVCC and VACUUM」。

而在今年 (2018) 的文章裡,EnterpriseDB 就提出了 zheap 的想法,在 UPDATE 時寫到 table 裡,把可能被 rollback 的資料放到 undo log 裡。其實就是把 InnoDB 那套方法拿過來用,只是整篇都沒提到而已 XD:

That brings me to the design which EnterpriseDB is proposing. We are working to build a new table storage format for PostgreSQL, which we’re calling zheap. In a zheap, whenever possible, we handle an UPDATE by moving the old row version to an undo log, and putting the new row version in the place previously occupied by the old one. If the transaction aborts, we retrieve the old row version from undo and put it back in the original location; if a concurrent transaction needs to see the old row version, it can find it in undo. Of course, this doesn’t work when the block is full and the row is getting wider, and there are some other problem cases as well, but it covers many useful cases. In the typical case, therefore, even bulk updates do not force a zheap to grow. Instead, the undo grows. When a transaction commits, all row versions that will become dead are in the undo, not the zheap.

不過馬上就會想到問題,如果要改善問題,不是個找地方記錄哪些位置要回收就好了嗎?順便改變方法是為了避免 fragment 嗎?

等著看之後變成什麼樣子吧...

InnoDB 的 MVCC 繁忙時的效能問題

Facebook 上看到 Percona 的人修正了 InnoDB 的 MVCC 在繁忙時會有 O(n^2) 的效能問題:

MySQL 官方的 bug tracking system 是「InnoDB's MVCC has O(N^2) behaviors」這個,可以看到給的重製範例是在 transaction 內大量塞 INSERT 進去後,另外一個 transaction 使用 secondary index 就會受到影響。

裡面也有提到「Secondary index updates make consistent reads do O(N^2) undo page lookups」,雖然修正了,但看起來跟當時實做的規劃有關?所以導致許多地方都是 O(n^2)...

這個 bug 感覺是批次作業的行為?因為批次作業可能會用 transaction 包起來,一次寫入萬筆資料後再 COMMIT 進去。而這個行為很有機會觸發這個 bug,導致影響到線上的服務...

Percona 比較 MySQL 與 MariaDB 預設值的差異

Percona 的人花了些時間整理 MySQL 5.7 與 MariaDB 10.2 在預設值上的差異:「MySQL and MariaDB Default Configuration Differences」。

整體可以感覺到 MariaDB 10.2 相較於 MySQL 5.7 還是頗偏 MyISAM 的設計,可能跟 Monty (Michael Widenius) 的偏好有關吧... 不過技術面上來說,MariaDB 10.2 是基於 5.5 分支出來一路改出來的,當時的 InnoDB 跟現在的版本比起來的確沒那麼強...

不過這畢竟只是預設值,看過留個印象就好...