Percona XtraDB Cluster 5.7 (Galera Cluster)

Percona 放出 PXC 5.7 了:「Percona XtraDB Cluster 5.7.14-26.17 GA is now available」。

主要的效能提昇還是基於 MySQL 5.7 上面,不過要注意的是有些預設值有改變 (尤其是 5.6 -> 5.7 最有名的 sync_binlog 設定),如果當初沒有特別指定的話,效能會掉不少,這點 Percona 整理了不少資料出來,可以翻一下 Percona 網站得到結果。

不過整體上改善了不少,在 5.6 需要一些手動設定的值以確保在 heavy loading 時不會拖垮系統效能 (也就是常說的「卡 query」),在 5.7 變得自動化了。像是 innodb_thread_concurrency 之前的建議都是設在 CPU logical threads 數量以確保當 threads 數量破千時還是有慢慢消化的能力 (讓 DBA 可以不用介入),在 5.7 不需要設定就跑得不錯了。

MySQL 5.7 中 InnoDB 的 innodb_page_size 在 SSD 硬碟上對效能的巨大影響

Percona 的「Small innodb_page_size as a performance boost for SSD」這篇文章裡提到了 MySQL 5.7 的 innodb_page_size 在 SSD 上對效能的差異,主要是這張圖的解釋:

先講一下標示的部份,有三個產品線 (都是 Samsung 的 SSD),中間的 sam850 是消費級的 SSD 硬碟 (所以不是本次重點),而 sam863 是企業級 SATA SSD,pm1725 則是企業級 PCI-e SSD。下方的 BP 指的是 Buffer Pool 大小,單位是 GB。左邊是速度,數字愈大愈好。

InnoDB 預設 16KB 的 page size,配合 SSD 大多都是 4KB 的 block size 後,效能的提昇非常巨大 (70% 的提昇),雖然既有的 InnoDB 要換過去會花不少功夫,但作者還是很建議評估:

I think a 70% performance gain is too significant to ignore, even if manipulating innodb_page_size requires extra work. I think it is worthwhile to evaluate if using different innodb_page_size settings help a fast SSD under your workload.

不過 comment 有不少額外重要的資訊。

有提到 Galera Cluster 目前有 bug,無法使用 4KB page size,可以在「Restarting a cluster with innodb_page_size=4096 segfaults」這邊看到 bug report。

另外有提到,使用 InnoDB Compression 的前提下,4KB 也許不是個好主意,用 8KB 也許是個方向:

You’re right – if you’re using Innodb Compression 4K base page is unlikely to be the good choice. Though we do not see Innodb compression (any of them) being used too frequently.

If using compression 8K base page size with 4K compressed page size might be good idea if 2x compression is routinely reached

不過我覺得應該還是有幫助才對 (可能提昇不高,但想了一下應該不會有負面的影響),之後有機會再測試看看吧 :o

Percona 宣佈把 Galera Cluster 納入 Enterprise 與 Premium Support 內

Percona 的「MySQL High Availability: The Road Ahead for Percona and XtraDB Cluster」這篇文章的前面講了很多東西,最主要就是要宣佈把 Galera Cluster 納入 Enterprise/Premium Support:

Today we’re taking the next natural step – we will no longer require customers to purchase Percona XtraDB Cluster as a separate add-on. Percona will include support for XtraDB Cluster and other Galera-based replication technologies in our Enterprise and Premier support levels, as well as our Percona Care and Managed Services subscriptions.

先前是分開賣的,現在包進去了。

繼續學 Isolation...

在「測試 MariaDB 上 Galera Cluster 的 Isolation」這邊與「從頭學一次 Isolation level 的 REPEATABLE-READ 與 SERIALIZABLE」對應了 Stripe 的「Call me Maybe: MariaDB Galera Cluster」以及 Percona 的「Clarification on “Call me Maybe: MariaDB Galera Cluster”」這兩篇文章,兩邊在空中交火...

而「Call me Maybe: Percona XtraDB Cluster」這篇是目前最新的一篇 (文章日期有點怪怪的,不應該是 2015/09/04)。

畢竟 Galera Cluster 是目前現在在用的產品,雖然跑起來也沒什麼大問題,但預先多了解一點總是比較保險。而要參與這場 Isolation 戰爭,又花了不少時間看幾份資料。(包括重頭讀的)

第一個當然是 1992 年發表的 SQL-92:「ISO/IEC 9075:1992, Database Language SQL」,在 SQL-92 定義了四個 Isolation 的層級,分別是 SERIALIZABLEREPEATABLE READREAD COMMITTEDREAD UNCOMMITTED

另外一篇常被拿出來討論的是 1995 年由微軟帶頭發表的「A Critique of ANSI SQL Isolation Levels」,主要是提出 Snapshot Isolation 的定義。

後來讀到覺得真的很怪的時候到 Stack Exchange 上的 Database Administrators 問:「Does SQL92's REPEATABLE-READ preclude Write Skew (A5B)?」,確認了我的想法。

這篇算是讀書心得吧,留給自己用的 :p

首先是 REPEATABLE READ 的定義是透過「現象」定義,只要符合這些條件就可以被稱作 REPEATABLE READ,這包括了保證不會出現 P1 ("Dirty read"):

1) P1 ("Dirty read"): SQL-transaction T1 modifies a row. SQL-transaction T2 then reads that row before T1 performs a COMMIT. If T1 then performs a ROLLBACK, T2 will have read a row that was never committed and that may thus be considered to have never existed.

以及保證不會出現 P2 ("Non-repeatable read"):

2) P2 ("Non-repeatable read"): SQL-transaction T1 reads a row. SQL-transaction T2 then modifies or deletes that row and performs a COMMIT. If T1 then attempts to reread the row, it may receive the modified value or discover that the row has been deleted.

並且保證 Atomicity 以及 no updates will be lost:

The four isolation levels guarantee that each SQL-transaction will be executed completely or not at all, and that no updates will be lost.

只要做到這些條件,就可以說符合 SQL-92 的 REPEATABLE READ 要求。


另外一方面,在「A Critique of ANSI SQL Isolation Levels」定義的 SQL-92 認為 REPEATABLE READ 是透過 locking 機制實作的 (整篇的寫法的確是朝這個方向走),並且基於 locking mechanism 來推論後續的行為,所以這篇 paper 裡面很多說明是帶著這個大前提的。

而當時已經有資料庫是使用 MVCC 架構在實作,可以透過 read snapshot 的方式找出另外一條路。

舉例來說,InnoDB 對 REPEATABLE READ 實作的方式是透過 snapshot 實作:(出自「MySQL :: MySQL 5.6 Reference Manual :: 13.3.6 SET TRANSACTION Syntax」)

For consistent reads, there is an important difference from the READ COMMITTED isolation level: All consistent reads within the same transaction read the snapshot established by the first read. This convention means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other.

這樣避開了 P1、P2 (甚至多做避開到了 P3,不過這邊不重要),並且 Atomicity。而 Update Lost 問題則再透過 lock 機制避免。所以 InnoDB 的 REPEATABLE READ 其實比 SQL-92 的要求多做到了很多事情...


到這邊再回頭看原來的討論文章,才會發現 Isolation 裡有滿滿的地雷可以採 XD

從頭學一次 Isolation level 的 REPEATABLE-READ 與 SERIALIZABLE

在「測試 MariaDB 上 Galera Cluster 的 Isolation」這篇提到了 StripeKyle Kingsbury 寫了「Call me Maybe: MariaDB Galera Cluster」這篇文章,在討論 Galera Cluster 的 Isolation 問題。

Percona 的 CTO Vadim Tkachenko 寫了回應的文章:「Clarification on “Call me Maybe: MariaDB Galera Cluster”」。

看完後再跑去翻了一些資料,首先是 SQL 92 對 Isolation 的原始定義,尤其是對 REPEATABLE-READ 的定義。

REPEATABLE-READ 是透過 P2 ("Non-repeatable read") 來定義的,只要保證不會發生 P2 就是 REPEATABLE-READ 了:

P2 ("Non-repeatable read"): SQL-transaction T1 reads a row. SQL-transaction T2 then modifies or deletes that row and performs a COMMIT. If T1 then attempts to reread the row, it may receive the modified value or discover that the row has been deleted.

也就是說,T1 讀過的 row 只要在 T1 transaction 的期間內都保持一樣就可以。由於沒有多餘其他保證,所以有機會產生 P3 ("Phantom") 問題:

P3 ("Phantom"): SQL-transaction T1 reads the set of rows N that satisfy some . SQL-transaction T2 then executes SQL-statements that generate one or more rows that satisfy the used by SQL-transaction T1. If SQL-transaction T1 then repeats the initial read with the same <search condition>, it obtains a different collection of rows.

SERIALIZABLEREPEATABLE-READ 的定義方式不同,並不是避免 P3 就可以叫做 SERIALIZABLE。SQL 92 定義說明必須與序列執行的結果一樣才能叫做 SERIALIZABLE

The execution of concurrent SQL-transactions at isolation level SERIALIZABLE is guaranteed to be serializable. A serializable execution is defined to be an execution of the operations of concurrently executing SQL-transactions that produces the same effect as some serial execution of those same SQL-transactions. A serial execution is one in which each SQL-transaction executes to completion before the next SQL-transaction begins.

所以依照定義,SERIALIZABLE 一定可以避免 P3 發生,但避免了 P3 不代表做到 SERIALIZABLE

另外一份資料是「MySQL :: MySQL 5.6 Reference Manual :: 13.3.6 SET TRANSACTION Syntax」對於 MySQL 5.6 中 InnoDB 實作 REPEATABLE-READ 的細節:

This is the default isolation level for InnoDB. For consistent reads, there is an important difference from the READ COMMITTED isolation level: All consistent reads within the same transaction read the snapshot established by the first read. This convention means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other. See Section 14.2.2.2, “Consistent Nonlocking Reads”.

InnoDB 的 REPEATABLE-READ 會在第一次讀取時建立一份 snapshot,所以 MySQL 的 REPEATABLE-READ 有達到不產生 P2 的要求,但也因為 snapshot 的關係而不會產生 P3,不過也就僅此而已,寫入的部份還是沒有達到 SERIALIZABLE 的要求。

回到原來的文章所整理出來的測試資料 (我稍微排版過),在這兩個不同的 transaction 同時發生時:(不同人都轉帳給 id = 8)

BEGIN; -- T1
SELECT balance FROM accounts WHERE account_id = 5;
SELECT balance FROM accounts WHERE account_id = 8;
UPDATE account SET balance = 75 WHERE account_id = 5;
UPDATE account SET balance = 125 WHERE account_id = 8;
COMMIT;
BEGIN; -- T2
SELECT balance FROM accounts WHERE account_id = 6;
SELECT balance FROM accounts WHERE account_id = 8;
UPDATE account SET balance = 80 WHERE account_id = 6;
UPDATE account SET balance = 120 WHERE account_id = 8;
COMMIT;

SERIALIZABLE 保證執行結果會與某種循序的結果相符 (可能是 T1 -> T2,也可能是 T2 -> T1)。但 REPEATABLE-READ 只保證在 transaction 裡面看到的 row 不會變化。

在 MySQL 裡,如果你想要跟 MySQL 告知「變更的結果會收到某些欄位的值的影響」,你至少要用 LOCK IN SHARE MODE 來做 (或是用 FOR UPDATE 會更清楚表示出意思),也就是:

BEGIN; -- T1
SELECT balance FROM accounts WHERE account_id = 5 LOCK IN SHARE MODE;
SELECT balance FROM accounts WHERE account_id = 8 LOCK IN SHARE MODE;
UPDATE account SET balance = 75 WHERE account_id = 5;
UPDATE account SET balance = 125 WHERE account_id = 8;
COMMIT;
BEGIN; -- T2
SELECT balance FROM accounts WHERE account_id = 6 LOCK IN SHARE MODE;
SELECT balance FROM accounts WHERE account_id = 8 LOCK IN SHARE MODE;
UPDATE account SET balance = 80 WHERE account_id = 6;
UPDATE account SET balance = 120 WHERE account_id = 8;
COMMIT;

這樣至少會有一個 transaction 會出現 deadlock 訊息,於是就會確保是正確的結果:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

值得一提的是,Percona 文章提到的第二種解法是有問題的:

BEGIN;
SELECT balance FROM accounts WHERE account_id = 5;
SELECT balance FROM accounts WHERE account_id = 8;
UPDATE account SET balance = balance - 25 WHERE account_id = 5;
UPDATE account SET balance = balance + 25 WHERE account_id = 8;
COMMIT;

這樣「總和」會因為保證原子性 (atomicity) 所以不會產生問題,但 balance 可能會產生出負數。

所以結論是 Kyle Kingsbury 那篇文章測試的方法本來就有問題,在 SELECT 時少了 LOCK IN SHARE MODE 或是 FOR UPDATE 的要求。

測試 MariaDB 上 Galera Cluster 的 Isolation

雖然人還在大阪員旅,不過看到這篇很精彩的測試還是先寫下來:「Call me Maybe: MariaDB Galera Cluster」,作者在 Stripe 花了很多時間在測試各種資料庫的資料正確性。

由於 PerconaPercona XtraDB Cluster (PXC) 是基於 Galera Cluster 改出來的,再加上敝公司裡面大量使用 PXC,決定還是趕緊花時間看完整篇文章確認到底講了什麼。

先講目前看到的結論:

  • 作者認為 Galera Cluster 官方宣稱的 SNAPSHOT-ISOLATION (SI) > REPEATABLE-READ (RR) 是有問題的講法,在學理上 SI 與 RR 都低於 SERIALIZABLE (1SR),也都高於 READ-COMMITTED (RC),但兩者之前沒有直接包含。
  • Galera Cluster 的測試結果看起來沒有做到 SI 的要求,不過測試中看起來有達到 READ-UNCOMMITTED (RU),在 RC 的測試中也沒找到問題,不過作者還是懷疑系統有達到 RC 的要求。

另外從其他討論串看起來,當所有人寫入都到同一台就沒有這些狀況,可以達到 1SR 的要求,這也是目前還蠻常見的系統設計,所以這次的問題看起來沒有這麼大。

後面也提到裝 MySQL Cluster 裝了好幾個禮拜還是裝不起來的事情:

Galera is easy to install–I spent weeks trying to set up MySQL Cluster to no avail, and got Galera Cluster running in a matter of hours.

有種,不是很意外的感覺...

Anyway,原文下面的 comment 可以看到 Baron Schwartz 也跑出來討論,另外在 GitHub 上有開始有討論了:「Snapshot Isolation may not be as isolated as one would like」,看起來直接戳破 Galera Cluster 支援 SI 的宣傳詞了,接下來拉板凳繼續看戲吧... XD

Galera Cluster (Percona XtraDB Cluster) 同步速度的改善

Percona 的「State Snapshot Transfer (SST) at a glance」這篇給了 Galera Cluster (也就是 Percona XtraDB Cluster) 在同步速度的改善方案,整篇文章一步一步改善,從 51 分鐘降到 18 分鐘。

劃幾個重點。

首先是同步時的設定可以放到系統 my.cnf[sst] 內,像是這樣:

[sst]
inno-apply-opts="--use-memory=20G"

其中改善最大的也就是 --use-memory,依照作者測試的數據,光這步就從 51 分鐘降到 30 分鐘。不過這邊要小心本來就有跑的 mysqld,如果 OOM 就慘了...

再來講的是 wsrep_slave_threads,不同於上面的 sst only 設定,這是在一般性的 tuning (平常在跑的參數,放在 [mysqld] 內),改完後速度 30 分鐘再提升到 25:32。

然後是壓縮的方式改用支援多 CPU 的 pigz

[sst]
inno-apply-opts="--use-memory=20G"
compressor="pigz"
decompressor="pigz -d"

很明顯是個 shell command 類的設定,所以如果真的想要測的話,可以再從 -1 測到 -9,作者在這邊沒測,不過效果也很明顯了,從 25:32 降到 21 分鐘。

最後一個大的改變是建議有專門同步的節點 (Donor node),這個節點上不會有 SQL query 來影響效能,這樣可以讓 pigz 的效率更高:

Since node2 is not getting application traffic, moving into the Donor state and doing an expensive SST with pigz compression should be relatively safe for the rest of the cluster (in this case, node1).

時間最後降到 18:33。

Galera Cluster (Percona XtraDB Cluster) 對寫入熱點的處理方式

Percona 的「Optimizing Percona XtraDB Cluster for write hotspots」這篇說明了在 Galera Cluster 上發生多個 transaction 衝突時的處理方式。

不過解法是早就已經知道的方法,也就是把寫入都丟到同一台上處理:

So what are your options with PXC when the workload has write hotspots? The most obvious one is to write on a single node: then you will have the same locking model as with a standalone InnoDB server. Performance will not be as good as with a standalone server as you will have to pay for synchronous replication, but you will avoid the very expensive write conflicts.

講解的文章,對之後 troubleshooting 與 performance tuning 有幫助...

Percona XtraDB Cluster 5.6 與 GTIDs 的結合

Percona 的 Fernando Laudares 在「How to setup a PXC cluster with GTIDs (and have async slaves replicating from it!)」這篇裡提到了 Percona XtraDB Cluster 5.6 與 GTIDs 的配合方式。

傳統的 replication 的 binlog 的表示方式是 filename + position,這個是大家已經很熟悉的方式。

digraph {
    M1 [label="Master 1"];
    M2 [label="Master 2"];

    S1 [label="Slave 1"];
    S2 [label="Slave 2"];
    S3 [label="Slave 3"];
    S4 [label="Slave 4"];

    { rank=same; M1; M2; };

    { rank=same; S1; S2; S3; S4; };
    S1 -> S2 -> S3-> S4 [style=invis];

    M1 -> M2;
    M2 -> M1;

    M1 -> {S1, S2, S3, S4};
    M2 -> {S1, S2, S3, S4} [color=gray];
}

優點是很簡單 (很好理解,也很容易設定管理),但缺點是 Slave 的 Master 跳動時 (從 Master 1 跳到 Master 2),Slave 可能會漏資料。

原因是出自於 MMM 之類的工具無法知道 Master 1 最後一筆寫入的資料對應到 Master 2 的哪個 binlog 位置。

MySQL 5.6 之後多了新的模式,叫做 GTIDs (Global Transaction IDs),把本來 binlog 的 filename + position 改良一下,變成 source_id + transaction_id 的形式。

source_id 是每台機器固定的值,目前是用 UUID 代表,而 transaction_id 則是遞增的流水號。所以也很好理解:「source_id 這台機器的第 n 個 transaction」。

這樣就可以多出很多偵測機制,降低問題發生的機率。

而 PXC 5.6 在這個領域則是因為已經有了自己的 cluster 機制,所以整個 cluster 會共用一組 UUID。這樣就可以避免混用機制而產生複雜的問題。

所以就變成三台 Master 互相切換 (通常 PXC 都是三台以上的奇數機器):

或是其中兩台互相切換 (第三台只跑 garbd 而沒有實際資料時):

我們家只剩下一組 cluster 是 PXC 5.5 (最大的一組),其他都是 PXC 5.6 了。應該要找人 deploy 這個機制降低 Master 跳機時的人力操作成本了...

多資料中心的備援機制

100% 的 uptime 有點誇張啦,不過看到這篇文章有些啟發:「Multi-Home the Data Center for 100% uptime」。

這張圖裡面,如果 DC1 的 DB master 掛掉,那麼 DC2 也只能進入唯讀狀態?甚至有些應用連唯讀狀態都不能進...

不過想到 Percona XtraDB Cluster 放三個機房的方法,不過如果只放三台的話會有其他的風險就是了,本粗一點變成六台 (每個機房都兩台) 也是個方法 :o