Percona XtraDB Cluster (PXC) 的感想

看到「Percona XtraDB Cluster 是 MySQL 的叢集與分散式解決方案」這篇,裡面提到了 Percona 包的 Galera Cluster,叫 Percona XtraDB Cluster

Percona 算是把 Galera Cluster 包的比較好的 distribution,是還蠻建議直接用他們家的版本。另外我記得 MariaDB 也有包一個版本,叫做 MariaDB Galera Cluster

這篇算是很早期使用 PXC 的人的一些感想:(大概是 2012 年導入,當年雲端也還沒流行,在地端上面自己建,對應的 MySQL 底層還是 5.5 的年代)

Percona XtraDB Cluster 建議至少三台

Galera Cluster 的三台可以是兩台有資料的,加上一台沒有資料,這台沒資料的只負責投票組成 quorum,不需要到三台都是大機器,而且這樣的配置也比較單純一點。

另外兩台雖然都可以當 writer 寫入,但實務上會建議都集中在一台寫,這樣可以大幅降低跨機器時產生的 lock contention。

基於上面這個因素,將兩台有資料的機器,一台做 writer,另外一台做 reader 算是常見的架構,然後把可以接受些許 replication lag 的應用 (像是什麼 BI 專用 DB server) 用傳統的 MySQL logical replication 掛出去 (標準的 master-slave 架構,或是後來政治改名為 source-replica 架構),不要直接參與 Galera Cluster 協定。

(MySQL 5.5 的時候還得自己處理當 master/source 切換時 replication binlog position 的問題,現在有 GUID 後會好一些)

除了 Galera Cluster 外,另外一種方式 (也是比較傳統的方式) 是 active-standby 的方式跑 DRBD:因為 DRBD 可以在兩台機器的 block 層做 mirror,所以切換的時候另外一台機器只要跑 journaling filesystem recovery (像是當年比較流行的 XFS 或是後來主力的 ext4) + InnoDB recovery 就可以跑起來。

DRBD 的老方法架構很單純,維護成本也很低,但缺點就是 recovery 的時間會高一些:在 crash 的 case 下可以做到十分鐘的 downtime 切換 (在傳統磁頭硬碟組成的 RAID),而 Galera Cluster 因為等於是 hot-standby,蠻容易就可以做到小於 30 秒。

另外在切換後 warmup 的時間上,Galera Cluster 也是因為 hot-standby 大勝:DRBD 這邊的情境等於是 cold start,資料庫內還有很多東西還沒進到 InnoDB buffer,對應的 SQL query 還不會快。

相比起來 Galera Cluster 看起來是個好東西,但後面運作的機制複雜不少 (而且需要有人維護),公司如果有專門的 DBOps 會比較好...

不過現在 SSD 變成主流的情況,讀取速度與 random access 的效率都快很多,這使得 DRBD 切換的成本低很多了,很有機會整個 downtime (切換 + warmup) 是五分鐘內搞定,如果這個時間是可以接受的,用 Galera Cluster 的優點可能就沒那麼高了...

MySQL 改變發佈的方式,推出 LTS 版本

看到 Percona 寫的「LTS and Innovation Releases for Percona Server for MySQL」這篇,才注意到 Oracle 宣佈了 MySQLLTS 版本:「Introducing MySQL Innovation and Long-Term Support (LTS) versions」。

這次的改變產生了 Innovation 版本與 LTS 版本:

We're transitioning to the new MySQL versioning model with our upcoming versions. MySQL database version 8.1.0 will be our first Innovation release, and 8.0.34+ will transition to only bug fixes until 8.0 End-Of-Life (EOL) scheduled for April-2026. Approximately one year from now, MySQL version 8.x will eventually become LTS which will provide ample time for users to migrate from 8.0.x to the 8.x LTS version.

從這段的解釋看起來是在講從舊的發佈模式到新的發佈模式的轉換期特例,MySQL 8.0.34+ (應該是指 8.0 的後續這條,從 8.0.34 開始) 會支援到 2026 年四月,大約是再兩年半多;而 8.x (應該是指 >8.0 的這條?) 會在距今一年後 (大約是 2024 年年中?) 成為 LTS 版本。

接著的段落拿了一些範例說明:

In practice, in this transition period, if you want the latest features, improvements, and all bug fixes for your MySQL databases, use the Innovation release track (eg., 8.1.x, 8.2.x, 8.3.x, etc.). If you need only bug fixes for your MySQL database, use 8.0.x releases (eg., 8.0.35, 8.0.36, 8.0.37, etc.). In both cases, you should plan to update your MySQL databases quarterly accordingly to Oracle Critical Patch Updates (CPU) calendar. When 8.x becomes LTS, you can plan, test, and migrate from the 8.0.x bug fix track to the LTS release track (ex., from 8.0.37 to 8.4.1).

看起來 8.0 是轉換期的特殊待遇,看起來有點像是 LTS 的 security update only?然後是官方給的這張圖,要注意這張圖下面有提到這張圖只是示意,實際發生的時間點可能會有改變,不過也可以看出來在 8.0 的地位比較特別:

Note that this is an example, there is no commitment that the version numbering will be exactly as presented.

而 LTS 的頻率會是兩年一版,支援 5+3 年,而 8.0 會走四年半:

About every 2 years, a minor version will be designated as Long Term Supported release. This version will have a 5 year premier and 3 year extended support, the same as the previously supported releases. This is similar to MySQL 5.7 and previous releases.

The LTS will also be the last version of the major release. The next (Innovation) release will increase it's major version. For example if MySQL 8.4.0 is the 8.x LTS release, then MySQL 9.0 will be the next Innovation release.

另外後面有提到他們會確保 LTS 在升級時會用到的方式,看了一下沒有太多意外,跟之前在 5.x 年代的感覺差不多。

倒是降級這件事情他們也提出方案,這點就蠻值得看一下了... 然後這邊可以看到 async replication 果然超萬用的。

基本上就是 release cycle 改變的公告,現在這個階段繼續黏在 8.0 上面應該就可以了,後續等第一個 LTS 的消息。

Meta (Facebook) 把 MySQL replication 丟上自製的 Raft 系統

看到「Building and deploying MySQL Raft at Meta」這篇,在講 Meta (Facebook) 把 MySQL 的 replication 架構換成自己用 Raft 的系統。

舊的系統是走 MySQL 的 semisync replication:

Previously, our replication solution used the MySQL semisynchronous (semisync) replication protocol.

其中 semisync replication 是在 MySQL 5.5 加入的功能,在至少一個遠端收到 replication log 後才傳回成功 (可以設定數量):「Semisynchronous Replication」。

Semisynchronous replication falls between asynchronous and fully synchronous replication. The source waits until at least one replica has received and logged the events (the required number of replicas is configurable), and then commits the transaction.

然後舊的系統是透過一包 Python 軟體在管理這些機器的各種 failover 操作:

The control plane operations (e.g., promotions, failover, and membership change) would be the responsibility of a set of Python daemons (henceforth called automation).

這個方法常遇到的問題是切換 primary server (以前叫做 master server) 時有可能會因為 binlog position 接不起來而失敗。

所以後來 MySQL 導入了 GTID,可以緩解這個問題,但還是有可能會發生不同的 secondary server (以前叫做 slave server) 會有不一樣的資料。

而在 Meta 改出來的架構裡面,把 replication data 直接寫到一個用 Raft 同步的系統,同步到其他的 secondary server 上面:

In MySQL Raft:

  • Primary writes to binlog via Raft, and Raft sends binlog to followers/replicas.
  • Replicas/followers receive in binlog and apply the transactions to the engine. An apply log is created during apply.
  • Binlog is the replicated log from the Raft point of view.

是個一般單位不太會遇到的架構,而且可以預期其他公司的人遇到類似問題應該也不會用這個方法解...

MySQL 5.7 的支援只到今年十月 (Oct 2023)

剛剛翻資料才看到 OracleMySQL 5.7 的支援原來只剩下半年了,預定在 2023 年十月中止:「Oracle Technology Products - Oracle Lifetime Support Policy」。

隔壁棚 Percona 包的 Percona Server for MySQL 5.7 可以從「Percona Release Lifecycle Overview」這邊查,看起來也設定一樣的時間 (2023 年十月),但不確定會不會宣佈延長,至少提供 security fix 之類的。

一直沒注意,突然發現只剩下半年...

Etsy 使用 Vitess 的過程

Etsy 寫了三偏關於使用 Vitess 解決資料庫效能問題的文章:「Scaling Etsy Payments with Vitess: Part 1 – The Data Model」、「Scaling Etsy Payments with Vitess: Part 2 – The “Seamless” Migration」、「Scaling Etsy Payments with Vitess: Part 3 – Reducing Cutover Risk」。

Vitess 是 YouTube 團隊開發出來的東西,試著透過一層 proxy 解決後端 MySQL 資料庫在 sharding 後查詢邏輯的問題。

有一些地方的資訊整理出來:

首先是現代暴力解的能耐,從維基百科可以查到 Etsy 在 2015 年就上市了,但到了 2020 年年底撞到 vertically scaling 的天花板 (這邊是指 GCP 的上限),可以看到現在的暴力法可以撐超久... 如果再多考慮到實體機房的話應該可以找到更大台的機器。

第二個是 Etsy 在 2020 年年底開始從資料庫搬資料,一路到 2022 年五月,算起來差不多搬了一年半,總共轉移了 4 個 database 到 Vitess 的 cluster 上,共 23 張表格與 40B rows。

第三個是利用 Vindexes 這個技術降低 sharding 時所帶來的限制。這個之前沒研究過:

A Vindex provides a way to map a column value to a keyspace ID.

從「Older Version Docs」這邊翻舊版的文件,發現 5.0+ 都有,再往 GitHub 上面的資料翻,看起來從 2016 年的版本就有了,不過當時看起來還一直在擴充:「Vitess v2.0.0-rc.1」。

回來看現在的功能,有 primary vindex 的設計:

The Primary Vindex for a table is analogous to a database primary key. Every sharded table must have one defined. A Primary Vindex must be unique: given an input value, it must produce a single keyspace ID.

然後是 secondary vindex(es) 的設計,指到 keyspace id(s),然後這個資訊會被用在 routing 上:

Secondary Vindexes are additional vindexes against other columns of a table offering optimizations for WHERE clauses that do not use the Primary Vindex. Secondary Vindexes return a single or a limited set of keyspace IDs which will allow VTGate to only target shards where the relevant data is present. In the absence of a Secondary Vindex, VTGate would have to send the query to all shards (called a scatter query).

It is important to note that Secondary Vindexes are only used for making routing decisions. The underlying database shards will most likely need traditional indexes on those same columns, to allow efficient retrieval from the table on the underlying MySQL instances.

然後是 functional vindex 與 lookup vindex,前者用演算法定義 keyspace id,後者讓你查:

A Functional Vindex is a vindex where the column value to keyspace ID mapping is pre-established, typically through an algorithmic function. In contrast, a Lookup Vindex is a vindex that provides the ability to create an association between a value and a keyspace ID, and recall it later when needed. Lookup Vindexes are sometimes also informally referred to as cross-shard indexes.

然後 lookup vindex 還有對 consistent hashing 的支援:

Consistent lookup vindexes use an alternate approach that makes use of careful locking and transaction sequences to guarantee consistency without using 2PC. This gives the best of both worlds, with the benefit of a consistent cross-shard vindex without paying the price of 2PC. To read more about what makes a consistent lookup vindex different from a standard lookup vindex read our consistent lookup vindexes design documentation.

這樣整體看起來,Vitess 把所有常見的 sharding 方式都包進去了,如果以後真的遇到這個量的話,也不需要自己在 application 或是 library 做一堆事情了...

WordPress 打算要支援 SQLite 作為後端資料庫

目前 WordPress 只有支援 MySQL,而昨天在 Hacker News 上看到 WordPress 有打算要支援 SQLite 作為後端資料庫的消息:「WordPress testing official SQLite Support (github.com/wordpress)」,原文在 GitHub 上:「Implement new experimental SQLite integration module」。

理論上對使用者會更方便,但對 extension 開發者會麻煩一些 (或是直接標不支援?),尤其是用到 MySQL 特有的語法就要注意了。

實質上 PHP + MySQL hosting 其實蠻常見的,這個作法有多少幫助就不知道了。

但突然想到,如果做一個 read-only 版本的 WordPress 站台,然後把 SQLite 的讀取部份改用 sql.js 之類的計畫,再把一堆 server side rendering 的部份變成 client side rendering,好像有機會可以整包直接上 GitHub Pages 之類的服務?雖然這樣有點拖褲子放屁...

PostgreSQL 上對應 pt-online-schema-change 的工具 pg-osc

翻資料的時候翻到「pg-osc: Zero downtime schema changes in PostgreSQL」這篇文章,可以在 PostgreSQL 上做到類似 pt-online-schema-change 的事情,這邊先提一下 pt-online-schema-change 的說明:

ALTER tables without locking them.

不管是 MySQL 還是 PostgreSQL,都會遇到 ALTER TABLE 常常會 lock 的問題,這點主要的影響就是 db migration。

在 dev 環境的機器應該沒什麼問題,資料量都不大,應該是很快就可以跑完;但在 stage 環境時就會開始有狀況了 (假設是從 production 複製過來的資料,表格的大小可能偏大),但應該還是可以用 downtime 換,慢慢跑,花幾個小時把 db migration 跑完。

可是到了 production 環境時就不太能這樣搞了,這也是一般不太建議在 production 環境裡用現成的 db migration 工具,尤其當資料量偏大的時候。

解這個問題的方法就是透過繞路的方式,不要直接動原來的 table:基本的想法是開一個新的 table,然後一直從舊的 table 搬資料到新的 table 上 (包括應用程式下指令寫到舊的 table 上的資料),直到最後用一個短暫的 lock 機制來切換 table。

在 MySQL 的世界裡比較有名的是 Percona 的 pt-online-schema-change (trigger-based) 以及 GitHubgh-ost (replication-based),另外找資料的時候有發現 Facebook 也有丟 OnlineSchemaChange (trigger-based) 出來。

在 PostgreSQL 的世界裡似乎是 pg_repack 這個方案,用了 trigger-based 的方式處理,但之前沒有注意到,是翻 pg-osc 的時候被提到才知道有這個工具。

而這次提到的 pg-osc 則是 2022 年才出的軟體,也是 trigger-based 的方式:

pg-osc uses the concept of shadow tables to perform schema changes. At a high level, it creates a shadow table that looks structurally the same as the primary table, performs the schema change on the shadow table (avoiding any locks since nothing is using this table), copies contents from the primary table to the shadow table and swaps the table names in the end while preserving all changes to the primary table using triggers (via audit table).

另外從 PostgreSQL 的 wiki 上看到「Change management tools and techniques」這頁,裡面看到「Metagration: Logical PostgreSQL Migration」這個工具,看起來好像是 replication-based 的方案,不過還是有用到一些 trigger 做事。

這些方案都先記錄起來好了...

比對兩個表格 (可以是不同的資料庫) 的內容,指出差異處

前幾天看到的東西,不確定是不是在 Hacker News 上,反正在 tab 上幾天了... 但還是附上 Hacker News 的連結:「Show HN: Data Diff – compare tables of any size across databases」,專案的位置在 GitHub 上的 datafold/data-diff

這是用 Python 寫的工具,安裝可以透過 pip 直接裝,所以也可以用 pipx 之類的工具獨立起來跑。

data-diff 會先拆成多個區塊,然後透過 checksum 的機制判斷兩邊的資料是否相同,不同的部份再取 bisection 分開下去找 (或是更多份,在 Technical Explanation 這個段落有寫到)。

在「Common use-cases」這段有提到幾個常見的使用情境,像是在自動化的環境下可以當作異常監控的工具:

Alerting and maintaining data integrity SLOs. You can create and monitor your SLO of e.g. 99.999% data integrity, and alert your team when data is missing.

另外在 troubleshooting 的情境下當然也很有幫助,可以先確認資料是否有問題,以及資料的哪邊出問題:

Debugging complex data pipelines. When data gets lost in pipelines that may span a half-dozen systems, without verifying each intermediate datastore it's extremely difficult to track down where a row got lost.

這個工具讓我想到 Percona Toolkit 裡面的 pt-table-checksum,不過 pt-table-checksum 只能處理 MySQL replication 的情境,data-diff 看起來通用多了:

目前完整測試過的是 MySQLPostgreSQLSnowflake,其他的有實做但還沒完整測試過。

看起來還在開發 (後面是商業公司 Datafold),但先寫下來,之後如果有用到的時候可以回頭看看進展...

翻一下 Linux container 的各種 overhead

想要查一下 Linux 下跑 container 的 overhead,發現大多都是 2014~2016 左右的文章,而且基本上都是 Docker,好像沒什麼新資料,但還是整理整理...

首先是「What is the runtime performance cost of a Docker container?」這篇,裡面的答案有提到 CPU、Memory 以及 I/O 看起來 overhead 都不高,主要是網路的 latency 增加不少:

看起來大約是 40µs 的增加 (0.04ms),這個量級雖然看起來很小,但對於本來就是透過 Ethernet 溝通的的應用來說,平常可能都是 <1ms 了,0.04ms 的增加可能還是有影響 (像是 TCP 的 3-way handshake)。

另外一篇是 Percona 的「Measuring Percona Server Docker CPU/network overhead」,不過這邊是測 CPU bound 的方式,沒有碰到 heavy I/O:

可以看到網路層的變化造成 tps 的變化,也符合在 Stack Overflow 上面找到的文章。

Oracle 官方的「MySQL with Docker - Performance characteristics」這篇則是測到 I/O bound 的應用,畢竟資料庫軟體會用到很多一般 I/O 測試不會用到的 flag,像是 InnoDB 大家通常都會啟用 O_DIRECT

For these tests, we used a custom configuration file. We first deliberately set the buffer pool size to around 10% of the total database size in order to increase I/O-bound load. The database size was 2358MB, so we set our buffer pool size to 256MB. We then increased the buffer size to 16384MB to see what happens when Docker isn’t bound by I/O load.

文章後面有列出數字,可以看到 I/O bound 的應用似乎沒有什麼影響,而 network bound 的時候可以看到效能的下降。

不過得注意這些資料都是六年前的資料了,沒有什麼新資料可以看做應該是沒什麼改變,但畢竟不是 100% 確定的事情...

這個月 GitHub 的不穩問題,都是 mysql1 這個 cluster 的鍋...

GitHub 針對了這個月的四次 downtime 說明,大致上都跟 mysql1 這組 cluster 有關:「An update on recent service disruptions」,這是 Keith Ballinger 發的文章,找了一下掛的頭銜是 SVP of Engineering at GitHub。

文章裡提到的 mysql1 在「Partitioning GitHub’s relational databases to handle scale」這邊可以看到一些資訊 (我在「GitHub 的 MySQL 架構與數字」這邊也有提到),基本上有 ProxySQL + Vitess 兩套方案在 scale,但可以看出來主資料庫本身還是有很大的 loading 在上面跑。

這次的問題是 mysql1 看起來這次遇到了效能上的瓶頸,不過還是沒找到原因,這可以從這幾次的說明看出來,從第一次的 outage:

The incident appeared to be related to peak load combined with poor query performance for specific sets of circumstances.

第二次的:

The following day, we saw the same peak traffic pattern and load on mysql1. We were not able to pinpoint and address the query performance issues before this peak, and we decided to proactively failover before the issue escalated.

第三次的:

While we had reduced load seen in the previous incidents, we were not fully confident in the mitigations.

In this third incident, we enabled memory profiling on our database proxy in order to look more closely at the performance characteristics during peak load.

到最近第四次的:

In order to reduce load, we throttled webhook traffic and will continue to use that as a mitigation to prevent future recurrence during peak load times as we continue to investigate further mitigations.

可以看到基本上還沒完,之後再遇到問題時應該還是會把 webhook traffic 拿出來開刀...