追 MediaWiki 頁面很慢的問題

我自己有個 wiki 拿來堆一些資料,前陣子覺得 Live 這頁很慢,實際測試發現要跑五秒多,想說到底是什麼鬼...

先從 MediaWiki 的各種 optimization 設定開始看,像是 Manual:Cache 這邊提到的東西,一路看下來看起來都已經設好最佳化了。

只好拿 profiling 工具來翻,在 PHP 上面用的是 Xdebug,裝了以後希望是特殊的情況才跑 profiling,所以參考了「Profiling」這邊的說明,在 php.ini 裡面這樣設定:

xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.trigger_value=StartProfileForMe

接著依照說明,你要找個地方把這個值傳進去:

Xdebug's profiler will only start when either the environment variable XDEBUG_TRIGGER is set to StartProfileForMe, the GET or POST variable XDEBUG_TRIGGER is set to StartProfileForMe, or when the cookie XDEBUG_TRIGGER has the value StartProfileForMe.

我是在網址列上直接用 ?XDEBUG_TRIGGER=StartProfileForMe 觸發,預設值會把 profiling 結果丟到 /tmp 下面,接下來就是把 callgrind 檔案視覺化了。

首先是把 xdebug 的 callgrind 格式輸出檔案轉成 DOT 格式,這邊參考「Interpreting callgrind data」的說明,先裝 gprof2dot,這個軟體在 PyPI 上有,所以可以用 pipx 裝,接著把 callgrind 轉完後再轉成 PNG 檔案。

我把圖放到最後面 (點開可以看大圖),第一張是 Live 這頁的 profiling,第二張是 Hosting 這頁的。

我注意到在 Live 這頁 php::mysqli 這邊被呼叫了幾千次,這邊就算是本地端的資料庫,幾千次的 latency 累積起來還是很驚人,畢竟是跨 process 之間的溝通,而且已知是 ping-pong 要等待的情況。

接著去翻是什麼造成的,看起來跟 selectRow 有關,但線索到這邊就斷掉了,上面的 User->load 看起來並沒有 loop 導致大量呼叫 selectRow

但至少有個方向,從網路上沒什麼討論看起來,應該不會是本體的效能問題,而是 extension 造成的,從這個角度開始追與帳號 & 權限有關的 extension,第一發懷疑是 Extension:AccessControl 造成的,結果果然沒錯... 拔掉後頁面的速度就從 5.1 秒左右降到 1.5 秒左右了。

沒有實際追程式碼,但猜測是 wiki 裡面用的 Template 造成 AccessControl 都會去檢查權限,加上這邊沒有上 cache,造成頁面上 Template 一多效能很差...

最後,這是 Live 頁的 profiling graph:

這是 Hosting 頁的 profiling graph:

USB PD 的誘騙器

先在 HN 上看到「USB Power Delivery for Hobby Projects (flameeyes.blog)」這則,原文是「USB Power Delivery For Hobby Projects」,裡面提到了很多想要從 USB-PD 裡面取出電流的搞法,不過在 HN 的 id=38057797 裡面有提到這搞的有點複雜,可以直接買 USB-PD 的誘騙器輸出對應的電壓:

Most of the rest seems to be over-engineering. You can get a ready-made "PD Trigger" board for literally $1 [0], which will allow you to select the desired voltage using jumpers. It even has support for the non-standard (although still common) 12V profile. Or, if you really want to do it using software, grab a $6 one from Adafruit [1] which comes with a tutorial and Arduino library.

其中的 [0] 是「PD/QC/AFC fast charge decoy trigger support 5V 9V 12V 15V 20V fixed voltage output」這個,可以直接調整 switch S1 S2 S3 選擇輸出的電壓:

不過不知道遇到沒有支援的電壓輸出時會怎麼樣 (像是有些變壓器只支援 5V 與 9V),我猜就是輸出 5V 而且不足電流,然後導致後面的設備不會動?另外比較大的問題就是不知道最大的電流與保護 XDDD

而 [1] 則是「Adafruit USB Type C Power Delivery Dummy Breakout - I2C or Fixed - HUSB238」,除了可以自己改線路設定電壓與最大電流外 (看起來直接一坨錫連過去,或是劃開預設的 5V 1A 的設定導線),也可以用 I²C 動態設定 decoy trigger:

這個看起來至少有個上限擋著?

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 做事。

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

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

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

Amazon RDS 上 PostgreSQL 的不停機升級

Hacker News Daily 上看到「Zero downtime Postgres migration, done right」這篇,講 PostgreSQL 9.5 不停機的前提下升級到 12.5 的方式,而且是在 Amazon RDS 上:

We have successfully used this process to migrate our Postgres databases from version 9.5 to 12.5 on RDS, but the process isn’t restricted to RDS only, and does not depend on anything AWS specific.

然後整個重點就在一張圖:

其實幾個 open source database 在這塊的基本概念都類似,用 replication 的技巧升級。

這邊作者選的是用 Bucardo 同步資料,然後舊的與新的 replication 都是雙向的,這樣在切換應用程式的時候就比較不會有時間差的問題。

這邊值得說的是,PostgreSQL 10 (2017 年十月出) 之後因為有了 logical replication,這種不停機持續性的 replication 選擇就變多了,不一定要用 trigger-based replication。作者這邊應該是因為 PostgreSQL 9.5 的關係,所以需要挑了 Bucardo。

另外一個重點是,如果你可以允許短時間的停機 (十分鐘之類的),那就可以改用單向的 replication 升級。因為你可以先停掉舊的資料庫,確保所有的資料都已經更新到新的資料庫,再把應用程式切換到新的資料庫上。

而這套方法如同作者說的,不限於 AWS 家的產品,其他家也可以使用類似的方法,在傳統實體機房也可以這樣做。

另外在 Hacker News 上的討論「Zero downtime Postgres migration, done right (theblueground.com)」很正常的又在戰 MySQL 在這塊的成熟度好太多,看看就好 XDDD

GitHub 發展出來的 ALTER TABLE 方式

GitHub 解釋了他們在 MySQL 上 ALTER TABLE 的方式:「gh-ost: GitHub's online schema migration tool for MySQL」。

GitHub 的舊方式是使用 pt-online-schema-change,會遇到的問題有幾個,其中看起來只有 Non pausability 這個是真正的痛點:

Non pausability: when load on the master turns high, you wish to throttle or suspend your pending migration. However a trigger-based solution cannot truly do so. While it may suspend the row-copy operation, it cannot suspend the triggers. Removal of the triggers results in data loss. Thus, the triggers must keep working throughout the migration. On busy servers, we have seen that even as the online operation throttles, the master is brought down by the load of the triggers.

當開始後,多出來的 trigger 是沒有辦法停下來的 (停下來就代表要全部重來),而且會影響線上服務。

新的方式則是用 replication 做,多一台機器出來跑,等結束後再切換,而中間有任何過程也都很好處理:

這方法手筆比較大,不過對於系統已經有規模的組織來說不是問題... 看起來以後可以朝這個方向研究 XD

DynamoDB Streams...

去年 (2014) 十一月時 AWS 推出了 DynamoDB Streams,像是 RDBMS 裡 trigger 的東西,不過當時還沒這麼方便,而且也是 preview 階段:「Sneak Preview – DynamoDB Streams」。

Once you enable it for a DynamoDB table, all changes (puts, updates, and deletes) made to the table are tracked on a rolling 24-hour basis.

而這個功能現在總算是開放讓一般人使用了,這次可以配合 AWS Lambda 一起使用,官方用了「DynamoDB Streams + Lambda = Database Triggers」的說明來解釋:「DynamoDB Update – Triggers (Streams + Lambda) + Cross-Region Replication App」。

另外這次也推出了 Cross-Region DynamoDB Replication,其實就是透過組合拳串起來:

This app runs on AWS Elastic Beanstalk and makes use of the EC2 Container Service, all launched via a AWS CloudFormation template.

關於 RDBMS 的 Schema Migration...

在「NoSQL 大腸花」這份投影片裡面的 Page 12 有提到關於 RDBMS 的 Schema Migration:

以目前 open source 的兩個專案,MySQLPostgreSQL 來看,裡面提到的 lock 應該都不是問題...

首先是 MySQL 的部份,真的量大的網站都應該是往 InnoDB 投靠,而 pt-online-schema-change 在這個領域則是處理的很好。

Facebook 的 Mark Callaghan 曾經在 2010 年寫過一篇關於 InnoDB 的 online schema change 的原理:「Online Schema Change for MySQL」,主要是利用 Trigger 的機制,用七個步驟架構出沒有 downtime 的 online scheme change。

就算不考慮 pt-online-schema-change 這種工具,在 MySQL 5.6 開始,就有愈來愈多 ALTER TABLE 的行為是不會影響到 read/write 了:「Avoiding MySQL ALTER table downtime」。

而 PostgreSQL 的情況也差不多,常見的 ALTER TABLE (新增與刪除 column 與 index) 也都不會影響 read/write。

這些在 Stack Overflow 上有不少討論:「ALTER TABLE without locking the table?」。

MySQL 在 RDBMS 領域裡比起來的確是不怎樣,不過沒有這麼糟糕啊...

從 MySQL (單機) 轉到 Galera Cluster 的前置作業...

codership (Galera Cluster 背後的公司) 剛剛發了一篇文章,說明將 MySQL 轉換到 Galera Cluster 有哪些事情要先處理:「5 Tips for migrating your MySQL server to a Galera Cluster」。

純粹技術上的事情大致上是這樣:

  • 先轉到 InnoDB
  • 每個 Table 都加上 Primary Key。
  • 檢查 Event,確認在 Galera Cluster 裡面會怎麼跑,或是直接拆到 cron server 跑...

另外幾點不是技術上的問題,而是 policy 應該規劃的事情... 把事情列出來,多隻眼睛檢查後再一步一步照表操課。

PS:對於 Galera Cluster 不熟的人可以先去看官方網站以及 Percona 的說明,看不懂就不要用,這樣會比較安全...