Uber 的 MySQL 5.7 升級到 8.0 的過程

一開始是在 Reddit 上看到的:「Upgrading Uber’s MySQL Fleet to version 8.0 (uber.com)」,然後發現 Hacker News 上的討論比較多:「Upgrading Uber's MySQL Fleet (uber.com)」。原文在「Upgrading Uber’s MySQL Fleet to version 8.0」這邊,是八月的文章,但看起來是最近被拿出來聊。

主要原因應該就是第一個提到的項目,MySQL 5.7 的 EoL 時間在去年的 2023/10,另外從文章裡的圖表可以看到,整個升級計畫應該是拉的蠻長的,在整理效能資料的圖片上可以看到 2023/04 升級完成的效能比較。

升級計畫看起來跟 Percona 有合作,這邊提到了處理升級後產生 performance regression 的部分:

Upgrading to MySQL 8.0 brought not only new features, but also some unexpected tweaks in query execution plans for certain clusters. This resulted in increased latencies and resource consumption, potentially impacting user experience. This happened for the cluster which powers all the dashboards running at Uber. To address this issue, we collaborated with Percona, identified a patch fix, and successfully implemented it for the affected clusters. The resolution ensured the restoration of optimized query performance and resource efficiency in alignment with the upgraded MySQL version.

所以應該是跑 Percona 包的 MySQL,但這讓我好奇了一點,印象中 Percona 應該是有延伸支援的計畫,如果有的話 Uber 未必會想要升級?

翻了 Percona 的支援文件,的確是還是有在繼續維護 5.7 版,而且現在 (2024/10) 還掛在 Active 狀態,而非 Extended Support (ES) 狀態:「Percona Release Lifecycle Overview」。

這邊看到個小插曲,MySQL 5.5 版已經放掉了,但 MySQL 5.6 版還沒放掉,進入了 ES 狀態。

我在猜是不是當初 Uber 做升級決定時 Percona 還沒宣佈會繼續支援 MySQL 5.7?不然大多數的情況都是能拖著就拖著...

Anyway,升級到 8.0 畢竟是累積了一包 major version 的改善,Uber 就挑了一些成功案例中的成功案例,像是很漂亮的 lock 大幅下降的情況:

還有提出某些 query 的時間大幅下降的 case:

如果要猜到底是什麼原因要升級,我覺得比較像是當初因為 EoL schedule + compliance 的需求,加上當初 Percona 又還沒公開會繼續支援,所以就安排了升級的計畫。

從「Business account privacy and security」這邊可以看到至少有 ISO 27001PCI DSS 兩套 compliance 要維護:

We maintain ISO 27001 and PCI DSS 3.2.1 certifications.

也許過個十年後會有人出來講?

Yelp 將一組 PostgreSQL 換成 MySQL

X (Twitter) 上看到 Yelp 將一組 PostgreSQL 換成 MySQL 的消息,貼文的作者是 Percona 家的老大 Peter Zaitsev,他自己也提到這是這個年頭蠻少看到的 PostgreSQL 轉 MySQL 案例:

原文在「Migrating in-place from PostgreSQL to MySQL」這邊。

通常這種轉換比較不會是軟體本身的考量,這次的情況是這組 PostgreSQL 是 2013 年併購進來的公司 Seatme 用的:

The Yelp Reservations service (yelp_res) is the service that powers reservations on Yelp. It was acquired along with Seatme in 2013, and is a Django service and webapp. It powers the reservation backend and logic for Yelp Guest Manager, our iPad app for restaurants, and handles diner and partner flows that create reservations.

而 Yelp 內的主力不在 PostgreSQL 上:

The DB used is PostgreSQL which is not used anywhere else at Yelp, which meant that only a small rotation of long-tenured employees knew Postgres well enough to do outage response. This caused issues in maintenance, visibility, and outage response times.

所以原因算是交代的很清楚了,主要是維護成本的問題,而後面就是這次轉換的技術方式,看了一下他們描述的 PostgreSQL 沒有用到太特殊的功能,所以沒有遇到真的太複雜的問題 (像是一堆 store procedure 或是 trigger,甚至掛進外部的 Python?),所以看起來還算能轉。

算是目前業界比較少見的 PostgreSQL 轉 MySQL 的 case,是可以看看沒錯...

Percona 的 Telemetry Package

Percona 開始搞 Telemetry (收集資料) 了,翻公告才發現已經一陣子了:「Percona Is Introducing Telemetry Mechanisms Into MySQL, PostgreSQL, and MongoDB」。

關掉的方法:

sudo systemctl disable --now percona-telemetry-agent

另外也是可以考慮原版的 MySQLMariaDB

Google 提出的 GoogleSQL (Pipe 版本的 SQL 改良)

看到「SQL Has Problems. We Can Fix Them: Pipe Syntax In SQL」這個研究投稿,PDF 檔案在 1004848.pdf 這邊。

Google 提出了 GoogleSQL 改善本來 SQL 的可讀性問題,另外也對 SQL optimizer 更有幫助。

直接拿 PDF 裡面的例子來說明,把本來是這樣的 SQL:

SELECT c_count, COUNT(*) AS custdist
FROM
  ( SELECT c_custkey, COUNT(o_orderkey) c_count
    FROM customer
    LEFT OUTER JOIN orders ON c_custkey = o_custkey
         AND o_comment NOT LIKE '%unusual%packages%'
    GROUP BY c_custkey
  ) AS c_orders
GROUP BY c_count
ORDER BY custdist DESC, c_count DESC;

變成這樣的:

FROM customer
|> LEFT OUTER JOIN orders ON c_custkey = o_custkey
AND o_comment NOT LIKE '%unusual%packages%'
|> AGGREGATE COUNT(o_orderkey) c_count
GROUP BY c_custkey
|> AGGREGATE COUNT(*) AS custdist
GROUP BY c_count
|> ORDER BY custdist DESC, c_count DESC;

可以看到這個語法除了變好讀以外,也指示了 SQL optimizer 怎麼去過濾與組合資料。

依照 Google 的說明,GoogleSQL 已經在許多 Google 的系統上實作了:

We’ve implemented pipe syntax in GoogleSQL, the SQL dialect and implementation shared across all1 SQL systems at Google including F1, BigQuery, Spanner and Procella, and the open-source release, ZetaSQL. GoogleSQL is a shared, reusable component, enabling many systems to share the same SQL dialect. This shared component allowed implementing pipe syntax in one place and then enabling it across many products.

裡面提到的 ZetaSQL 可以在 pipe-syntax.md 這邊看到。

我記得其他家也有類似的東西,PRQL 也是在解決類似的問題,不過語法走向不太一樣。

可以看看怎麼收斂...

用 Google Calendar 當作範例,設計資料庫的結構

Hacker News 上看到「Database Design for Google Calendar: A Tutorial (databasedesignbook.com)」這個,原文在「Database Design fo Google Calendar: a tutorial」這,這邊的資料庫指的是關聯式資料庫 (RDBMS),像是常見的 MySQL 或是 PostgreSQL

而有趣的點是在 Hacker News 的討論,不過還是先帶一下原文的內容。

原文主要就是怎麼設計以及怎麼下 SQL query 會比較好,另外有些「作者經驗」說明為什麼這樣設計,不過這部份很吃場景,所以當作故事聽過去比較好,自己遇到的時候需要通盤考慮;另外有些地方提到的是 trade off,沒有最佳解。

然後回到 Hacker News 上面,在 id=41044011 這邊算給你看,幹嘛用 RDBMS 設計一堆架構:

A random event from my calendar serializes to 740 bytes of iCalendar. An extremely busy calendar containing one event every 15 minutes 9am-5pm 7 days per week only contains 11680 entries for a full year, which works out to around 8MB.

Would suggest instead of designing a schema at all, a calendar is a good example of a problem that might be far better implemented as a scan. Optimizing an iCalendar parser to traverse a range of dumped events at GB/sec-like throughputs would mean the above worst-case calendar could be scanned in single-digit milliseconds.

Since optimizing a parser is a much simpler problem to solve once than changing or adding to a bad data model after it has many users, and that the very first task involving your new data model is probably to write an iCalendar importer/exporter anyway, I think this would be a really great trade off.

因為每個 entry 也不到 1KB (740 bytes),就算是超級忙的 calendar,每週七天的 9am~5pm 都是每 15mins 一個會議的情況下,全滿也才 8MB... XDDDDDDDD

另外幫他補充說明,他這邊省略掉的是 calendar 接近於 share-nothing 架構,所以每個人直接拆開來獨立存放很容易 scale,你可以用個 scalable key-value solution 存 JSON 就好。

不過文章的原作者主要的目的是找一個大家熟悉但又有點複雜性的東西,示範 database schema 設計的考慮,我不會直接說文章原作者錯啦...

但的確一開始看到文章不會想到暴力解還蠻... 暴力的?XDDD

Amazon DocumentDB (with MongoDB compatibility) 支援壓縮

看到「Amazon DocumentDB announces improvements to document compression」這則公告提到 Amazon DocumentDB 5.0 支援壓縮了:

These compression benefits are now supported in Amazon DocumentDB 5.0 instance-based clusters in all regions where Amazon DocumentDB is available.

官方宣稱可以壓七倍:

Compressed documents in Amazon DocumentDB can be up to 7 times smaller than uncompressed documents, leading to lower storage costs, I/O costs, and improved query performance.

在「Managing collection-level document compression」這份文件裡面則是提到演算法是 LZ4

Amazon DocumentDB uses the LZ4 compression algorithm to compress documents.

因為大家都猜 DocumentDB 後面是 PostgreSQL,所以我就查了一下 PostgreSQL 這邊的資料,可以從 PostgreSQL 14.0 (2021/09/30) 的 Release Notes 看到支援 LZ4:

Add ability to use LZ4 compression on TOAST data (Dilip Kumar)

This can be set at the column level, or set as a default via server parameter default_toast_compression. The server must be compiled with --with-lz4 to support this feature. The default setting is still pglz.

而 DocumentDB 則是 2019 年一月的時候出的,大概是 DocumentDB 5.0 用到的 PostgreSQL 比較新所以可以支援了...

在 PostgreSQL 裡,當 UUID 當作 Primary Key 時要怎麼處理

繼續清 tab,在「PostgreSQL and UUID as Primary Key (maciejwalkowiak.com)」這邊看到的,原文是討論 PostgreSQL 要怎麼處理 PK 是 UUID 的情況:「PostgreSQL and UUID as primary key」。

文章開頭作者就說了,這篇不是要戰 PK 要不要用 UUID,而是已經決定要用了 (i.e. 通常不是你決定的),在接手以後要怎麼用比較好:

Considering the size of UUID it is questionable if it is a right choice, but often it is not up to us to decide.

This article does not focus on "if UUID is the right format for a key", but how to use UUID as a primary key with PostgreSQL efficiently.

首先是 PostgreSQL 從 8.3 版 (2008 年) 就支援 UUID 的資料型態了,這點從 release note 可以看到:「PostgreSQL 8.3.0」,所以要當 PK 的話沒什麼道理不考慮他。

UUID 的空間上就是 128-bit data (16 bytes),相比於 TEXT 會省蠻多的,尤其 PK 常常會被其他表格 reference 到 (像是 foreign key) 會在其他表格也省下來:

Table that uses text is 54% larger and the index size 85% larger.

第二點則是考慮到 UUID 本身的特性,以前的 UUID 因為是亂數生成的 (通常會用 UUIDv4),對寫入 B-tree 類的資料結構不是很有效率,改用 UUIDv7 (差不多是這兩年陸陸續續發展出來的規格) 會得益於與 timestamp 有關,對 B-tree 寫入的效率會好很多:

Random UUIDs are not a good fit for a B-tree indexes - and B-tree index is the only available index type for a primary key.

B-tree indexes work the best with ordered values - like auto-incremented or time sorted columns.

UUID - even though always looks similar - comes in multiple variants. Java's UUID.randomUUID() - returns UUID v4 - which is a pseudo-random value. For us the more interesting one is UUID v7 - which produces time-sorted values. It means that each time new UUID v7 is generated, a greater value it has. And that makes it a good fit for B-Tree index.

作者的測試可以看到寫入速度與 UUIDv4 相比比快不少:

BUT we can clearly see, that inserting UUID v7 is ~2x faster and inserting regular UUID v4.

總結來說,當 PK 已經決定是 UUID 後,主要就是這兩個重點可以注意的,當然 Hacker News 上更熱鬧的是兩派人馬在吵要用 integer 類的 SERIAL/BIGSERIAL 還是用 UUID,那又是另外一個話題了...

如果早個二十年前對 memory size 斤斤計較的情況下,答案鐵定是 integer 類的,但年代不同了...?

直接在 library 層將 MongoDB 用法轉換成 PostgreSQL 底層的 Pongo

看到這個「Mongo but on Postgres and with strong consistency benefits (github.com/event-driven-io)」算是另外一種用 PostgreSQL 取代 MongoDB 的嘗試,先前其他的方案是 proxy server 的方式實作 (像是 FerretDB),也就是 TCP 裡面傳的東西還是 MongoDB protocol,然後 proxy server 會轉譯成 PostgreSQL 的 SQL 語法。

這個作法的好處是不用管既有 application 是什麼程式語言開發的,另外改動比較少 (改個連線資訊 + 然後把目前還不支援的功能改寫),但缺點是多了一組 service 要維護 (如果是 HA 的話又還要設定 failover 或是 load balancer 的機制)。

Pongo 的作法則是移到 library 這邊做掉,所以就有程式語言的限制了:這個專案是用 TypeScript 開發,所以會是 JavaScript + TypeScript 生態系的方案。

不過好處就很明顯了,少了一組 service 要維護 (如果包括 HA 機制的話可能是兩組或三組),另外因為轉譯的部分在 application 端處理,沒有了 proxy server 也等於少了一個可能的 bottleneck。

這幾天上 Hacker News 後看 commit 頗熱鬧,從 Releases 頁可以看到連續出新版本。

不過... 不考慮直接用 PostgreSQL 嗎?

Peter Zaitsev 抱怨 Oracle 對 MySQL 的態度

Percona 家的老大 Peter Zaitsev 寫了一篇紋章抱怨 OracleMySQL 的態度:「Is Oracle Finally Killing MySQL?」。

其實大家都有感覺,2009 年 Oracle 買下 Sun (當時擁有 MySQL 的公司) 後的前幾年還有些動作,後面整個就停住了,這也導致了市場上可以看到明顯的變化,不少人都開始關注並且使用 PostgreSQL

隔壁 MariaDB 透過 SPAC 弄的灰頭土臉後縮編不少,自己弄的 engine 還是跟 InnoDB 有一段差距,功能上也沒有比較優秀的地方...

雲端降低管理成本也是一個讓 PostgreSQL 興起的推手,如果在地端的話,MySQL 的管理工具還是成熟不少 (畢竟就是時間累積出來的),但在雲端上面就接包的好好的了...

MySQL 8.4.0 出版

這是 LTS 版本,可以預期後續 Percona 也會準備對應的 distribution 使用。

先查了一下 MySQL 8.0 的 EoL 日期,目前是訂在 2026/04/30,大約還有兩年的時間可以準備。

回來看 8.4,從「What Is New in MySQL 8.4 since MySQL 8.0」這邊可以看到官方整理與 8.0 的差異。

InnoDB 這邊有不少預設參數調整,算是配合現在硬體愈來愈好而調整的數字。

在「MySQL Replication: tagged GTIDs」這邊有提到 replication 時用到的 GTID format 有更新,多了 tag 的欄位。看起來在 upgrade 時會相容舊格式,但要 rollback 或是 downgrade 時得注意到這邊的問題...

然後在「Replication SQL syntax」這邊有提到移除掉很多舊語法,像是 CHANGE MASTER TORESET MASTER 這種語法都被改成新的語法了,要重新背。

整體看起來沒有太多變化?