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

讀書時間:Meltdown 的攻擊方式

Meltdown 的論文可以在「Meltdown (PDF)」這邊看到。這個漏洞在 Intel 的 CPU 上影響最大,而在 AMD 是不受影響的。其他平台有零星的消息,不過不像 Intel 是這十五年來所有的 CPU 都中獎... (從 Pentium 4 以及之後的所有 CPU)

Meltdown 是基於這些前提,而達到記憶體任意位置的 memory dump:

  • 支援 µOP 方式的 out-of-order execution 以及當失敗時的 rollback 機制。
  • 因為 cache 機制造成的 side channel information leak。
  • 在 out-of-order execution 時對記憶體存取的 permission check 失效。

out-of-order execution 在大學時的計算機組織應該都會提到,不過我印象中當時只講「在確認不相干的指令才會有 out-of-order」。而現代 CPU 做的更深入,包括了兩個部份:

  • 第一個是 µOP 方式,將每個 assembly 拆成更細的 micro-operation,後面的 out-of-order execution 是對 µOP 做。
  • 第二個是可以先執行下去,如果發現搞錯了再 rollback。

像是下面的 access() 理論上不應該被執行到,但現代的 out-of-order execution 會讓 CPU 有機會先跑後面的指令,最後發現不該被執行到後,再將 register 與 memory 的資料 rollback 回來:

而 Meltdown 把後面不應該執行到 code 放上這段程式碼 (這是 Intel syntax assembly):

其中 mov al, byte [rcx] 應該要做記憶體檢查,確認使用者是否有權限存取那個位置。但這邊因為連記憶體檢查也拆成 µOP 平行跑,而產生 race condition:

Meltdown is some form of race condition between the fetch of a memory address and the corresponding permission check for this address.

而這導致後面這段不該被執行到的程式碼會先讀到資料放進 al register 裡。然後再去存取某個記憶體位置造成某塊記憶體位置被讀到 cache 裡。

造成 cache 內的資料改變後,就可以透過 FLUSH+RELOAD 技巧 (side channel) 而得知這段程式碼讀了哪一塊資料 (參考之前寫的「Meltdown 與 Spectre 都有用到的 FLUSH+RELOAD」),於是就能夠推出 al 的值...

而 Meltdown 在 mov al, byte [rcx] 這邊之所以可以成立,另外一個需要突破的地方是 [rcx]。這邊 [rcx] 存取時就算沒有權限檢查,在 virtual address 轉成 physical address 時應該會遇到問題?

原因是 LinuxOS X 上有 direct-physical map 的機制,會把整塊 physical memory 對應到 virtual memory 的固定位置上,這些位置不會再發給 user space 使用,所以是通的:

On Linux and OS X, this is done via a direct-physical map, i.e., the entire physical memory is directly mapped to a pre-defined virtual address (cf. Figure 2).

而在 Windows 上則是比較複雜,但大部分的 physical memory 都有對應到 kernel address space,而每個 process 裡面也都還是有完整的 kernel address space (只是受到權限控制),所以 Meltdown 的攻擊仍然有效:

Instead of a direct-physical map, Windows maintains a multiple so-called paged pools, non-paged pools, and the system cache. These pools are virtual memory regions in the kernel address space mapping physical pages to virtual addresses which are either required to remain in the memory (non-paged pool) or can be removed from the memory because a copy is already stored on the disk (paged pool). The system cache further contains mappings of all file-backed pages. Combined, these memory pools will typically map a large fraction of the physical memory into the kernel address space of every process.

這也是 workaround patch「Kernel page-table isolation」的原理 (看名字大概就知道方向了),藉由將 kernel 與 user 的區塊拆開來打掉 Meltdown 的攻擊途徑。

而 AMD 的硬體則是因為 mov al, byte [rcx] 這邊權限的檢查並沒有放進 out-of-order execution,所以就避開了 Meltdown 攻擊中很重要的一環。

Meltdown 與 Spectre 都有用到的 FLUSH+RELOAD

MeltdownSpectre 攻擊裡都有用到的 FLUSH+RELOAD 技巧。這個技巧是出自於 2013 年的「Flush+Reload: a High Resolution, Low Noise, L3 Cache Side-Channel Attack」。當時還因此對 GnuPG 發了一個 CVE-2013-4242

FLUSH+RELOAD 是希望透過 shared memory & cache 得到 side channel information,藉此突破安全機制。

論文裡面提到兩個攻擊模式,一種是在同一個 OS 裡面 (same-OS),另外一種是在同一台機器,但是是兩個不同的 VM (cross-VM)。攻擊的前提是要拿到與 GnuPG process 相同的 shared memory。兩個環境的作法都是透過 mmap() GnuPG 的執行檔以取得 shared memory。

在 same-OS 的情況下會使用同一個 process:

To achieve sharing, the spy mmaps the victim’s executable file into the spy’s virtual address space. As the Linux loader maps executable files into the process when executing them, the spy and the victim share the memory image of the mapped file.

在 cross-VM 的情況下會因為 hypervisor 會 dedup 而產生 shared memory:

For the cross-VM scenario we used two different hypervisors: VMware ESXi 5.1 on the HP machine and Centos 6.5 with KVM on the Dell machine. In each hypervisor we created two virtual machines, one for the victim and the other for the spy. The virtual machines run CentOS 6.5 Linux. In this scenario, the spy mmaps a copy of the victim’s executable file. Sharing is achieved through the page de-duplication mechanisms of the hypervisors.

接下來就能夠利用 cache 表演了。基本原理是「存取某一塊記憶體內容,然後計算花了多久取得,就能知道這次存取是從 L1、L2、L3 還是記憶體取得」。所以 FLUSH+RELOAD 就設計了三個步驟:

  • During the first phase, the monitored memory line is flushed from the cache hierarchy.
  • The spy, then, waits to allow the victim time to access the memory line before the third phase.
  • In the third phase, the spy reloads the memory line, measuring the time to load it.

先 flush 掉要觀察的記憶體位置 (用 clflush),然後等待一小段時間,接著掃記憶體區塊,透過時間得知有哪些被存取過 (就會比較快)。這邊跟 cache 架構有關,你不能想要偷看超過 cache 大小的量 (這樣會被 purge 出去),所以通常是盯著關鍵的部份就好。

接著是要搞 GnuPG,先看他在使用 RSA private key 計算的程式碼:

而依照這段程式碼挑好位置觀察後,就開始攻擊收資訊。隨著時間變化就可以看到這樣的資訊:

然後可以觀察出執行的順序:

於是就能夠依照執行順序推敲出 RSA key 了,而實際測試的成果是這樣,在一次的 decrypt 或是 sign 就把 RSA key 還原的差不多了 (96.7%):

We demonstrate the efficacy of the FLUSH+RELOAD attack by using it to extract the private encryption keys from a victim program running GnuPG 1.4.13. We tested the attack both between two unrelated processes in a single operating system and between processes running in separate virtual machines. On average, the attack is able to recover 96.7% of the bits of the secret key by observing a single signature or decryption round.

知道了這個方法後,看 Meltdown 或是 Spectre 才會知道他們用 FLUSH+RELOAD 的原因... (因為在 Meltdown 與 Spectre 裡面就只有帶過去)