讓 git diff 可以直接顯示 SQLite3 裡面的差異

從「Tracking SQLite Database Changes in Git」這邊看到的,然後作者 Simon Willison 又是從 Lobste.rs 的「Tracking SQLite Database Changes in Git databases」這邊看到的,而原文在「Tracking SQLite Database Changes in Git」。

一般 SQLite 檔案的 diff 會出現這樣:

diff --git a/a.sqlite3 b/a.sqlite3
index a4a8cfa..714f34a 100644
Binary files a/a.sqlite3 and b/a.sqlite3 differ

作者想要透過 sqlite3 的指令加工,讓 git-diff 的演算法可以展現出像是這樣的指令:

diff --git a/a.sqlite3 b/a.sqlite3
index a4a8cfa..714f34a 100644
--- a/a.sqlite3
+++ b/a.sqlite3
@@ -3,4 +3,5 @@ BEGIN TRANSACTION;
 CREATE TABLE tbl (id SERIAL, username TEXT, password TEXT, created_at INT, updated_at INT);
 INSERT INTO tbl VALUES(NULL,'gslin','$1$yRgoNPev$nOc5Hpr5JZAYISbHjp7LA/',0,0);
 INSERT INTO tbl VALUES(NULL,'dk','$1$yRgoNPev$nOc5Hpr5JZAYISbHjp7LA/',0,0);
+INSERT INTO tbl VALUES(NULL,'darkkiller','$1$yRgoNPev$nOc5Hpr5JZAYISbHjp7LA/',0,0);
 COMMIT;

方法是先設定 diff 工具部分,這個可以放到 ~/.gitconfig 裡面:

[diff "sqlite3"]
    binary = true
    textconv = "echo '.dbconfig trusted_schema no\n.dump' | sqlite3"

這邊跟原文不太一樣,主要是參考了 SQLite 官方網站上「Defense Against The Dark Arts」這邊的「Untrusted SQLite Database Files」部分,增加了 .dbconfig trusted_schema no 的設定加減擋一下...

然後我依照「Where should I place my global 'gitattributes' file?」這邊的問題與解答,把 attributes 設定放在 ~/.config/git/attributes 裡面:

*.sqlite diff=sqlite3
*.sqlite3 diff=sqlite3

這樣就會對這個使用者所有的 git repository 都生效。

作者原文提到的方法也可以用,不過主要是在單一 repository 裡面設定,針對 *.db 這類只有在 repository 內才會知道規則的告訴 git 要怎麼認。

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

前幾天看到的東西,不確定是不是在 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),但先寫下來,之後如果有用到的時候可以回頭看看進展...

Git 的 Diff 演算法

Lobsters Daily 上看到「When to Use Each of the Git Diff Algorithms」這篇 2020 的文章 (Lobsters 這邊常冒出一些舊文章),講 Git 的 diff 演算法。

文章裡面題到了四個演算法,看了一下 git-diff 的 manpage 也還是這四個:

--diff-algorithm={patience|minimal|histogram|myers}

另外看 manpage 時還有看到 --anchored 的用法,看起來是用來提示 Git 產生比較好懂的 diff 結果:

--anchored=<text>

Generate a diff using the "anchored diff" algorithm.

This option may be specified more than once.

If a line exists in both the source and destination, exists only once, and starts with this text, this algorithm attempts to prevent it from appearing as a deletion or addition in the output. It uses the "patience diff" algorithm internally.

回頭翻了一下自己的 config repository,看起來 2017 年年底的時候我就加了「Use histogram diff algorithm.」,然後過幾天換成「Use patience algorithm.」,就一直用到現在...

這次改用 minimal 跑一陣子看看好了...

在 Gmail 裡面展開 GitHub 信件連結的 Userscript

從以前用 Subversion 就有習慣用 e-mail 收 diff log (內建的 hook 就有這個功能)。後來有了 Gmail 就更方便了,畢竟搜尋是 Google 的強項,另外一方面 Gmail 也是目前少數可以完全用鍵盤操作的 e-mail client。

現在用 GitGitHub 平台讓這件事情麻煩不少,得透過 webhook + API 自己包出來,目前折衷的方法是收 GitHub 的通知信 (但是沒有 diff log),然後寫個 Userscript,在按下 i 之後會把 GitHub 通知信裡的連結全部點開:「open-github-links-in-gmail」。

寫的很簡單... 最花時間反而是因為 hotkey 都被吃掉了,要測出哪個鍵還沒被用掉。

看檔案改變的過程

像是這樣的東西:

專案在「pomber/github-history」這邊,目前只支援 GitHub 平台。在選好檔案後,只要把本來網址上的 github.com 改成 github-history.netlify.com 就可以切過去操作了。

主要是看起來頗有趣的,實用性我覺得有點低 XD

comm 的用法...

最近在 Twitter 上看到不少 shell 指令的說明,這則 tweet 是講 comm 這個指令:

1 是只有第一個檔案有的內容,2 是只有第二個檔案有的內容,而 3 是兩者都有的內容,而且檔案內容需要排序過。

當你 -1 時表示幹掉 1 的部份,-2 代表幹掉 2 的部份,-3 代表幹掉 3 的部份,然後可以疊起來用... 不過平常還是用 diff 比較多,每次看到 comm 的說明都是玩過再熟悉一下,然後就丟著 XD

Google 推出比較兩個 Docker Image 的 container-diff

Google 放出來的工具,可以比較兩個 Docker Image 的內容:「Introducing container-diff, a tool for quickly comparing container images」,GitHub 的連結在「GoogleCloudPlatform/container-diff」這邊。

不是單純的 binary diff,而是分析裡面的內容。像是安裝套件的差異,或是 pipnpm 的差異:

主要是開發者會用到,可以觀察不同 container 之間的差異。

Google 再次改善 Android 的 APK 更新,讓下載的量更小

Google 的人再次更新了演算法,將下載的量再次減少,從本來的 47% 降到 65%:「Saving Data: Reducing the size of App Updates by 65%」。

今年七月的時候,更新演算法導入了 bsdiff,使得本來要抓整包 APK 的量,變成抓 diff 的部份,這使得下載的流量降了 47%:「Improvements for smaller app downloads on Google Play」。

Using bsdiff, we were able to reduce the size of app updates on average by 47% compared to the full APK size.

現在則改成不直接對 APK 做 diff,而是對未壓縮的檔案做,再把差異包起來,則可以降到 65%:

Today, we're excited to share a new approach that goes further — File-by-File patching. App Updates using File-by-File patching are, on average, 65% smaller than the full app, and in some cases more than 90% smaller.

主要的原因在於 APK 的壓縮使用的 DEFLATE 演算法對於變更非常敏感,改變一個字元就會讓後續整串都改變,導致差異很大而跑 diff algorithm 的效果不好:

用 File-by-File 的好處主要來自於是對未壓縮的檔案比較差異,這代表沒有變動的檔案完全不會進來攪和,而對 binary 檔案的效果也比較好 (大部份的程式碼還是一樣)。不過這對於已經有壓縮的圖片的效果就比較差了,這也是 APK 一般肥大常見的原因。

有兩件事情值得注意的,一個是 Google 的人為了使用者體驗,只有在 auto update 時才會走 File-by-File 的更新,主要原因是 File-by-File 的解開速度慢不少:

For now, we are limiting the use of this new patching technology to auto-updates only, i.e. the updates that take place in the background, usually at night when your phone is plugged into power and you're not likely to be using it. This ensures that users won't have to wait any longer than usual for an update to finish when manually updating an app.

另外一個是,這個新方式讓 Google 每天省下 6PB 的流量,如果流量都是平均打散的話,大約是 600Gbps:

The savings, compared to our previous approach, add up to 6 petabytes of user data saved per day!

這種規模改善起來很有感覺 XDDD

Facebook 備份 MySQL 資料並且確認正確性的方法

Facebook 再多花了一些篇幅數對於 MySQL 資料備份以及確認正確性的方法:「Continuous MySQL backup validation: Restoring backups」。

首先是 Continuous Restore Tier (CRT) 這塊,可以看到他們在這塊很仰賴 HDFS 當作備份的第一層基地,包括了 Full logical backups (用 mysqldump)、Differential (diff) backups 以及 Binary log (binlog) backups (stream 進 HDFS)。

另外上了 GTID,對於後續的處理會比較方便:

All of our database servers also use global transaction IDs (GTIDs), which gives us another layer of control when replaying transactions from binlog backups.

在 CRT 這塊可以看到其實是拿現成的工具堆起來,不同單位會因為規模而有不同的作法。真正的重點反而在 ORC Restore Coordinator (ORC) 這塊,可以看到 Facebook 開發了大量的程式將回復這件事情自動化處理:

在收到回復的需求後,可以看到 Peon 會從 HDFS 拉資料出來,並且用 binlog replay 回去:

Peons contain all relevant logic for retrieving backups from HDFS, loading them into their local MySQL instance, and rolling them forward to a certain point in time by replaying binlogs. Each restore job a peon works on goes through these five stages[.]

也是因為 Facebook 對 MySQL 的用量大到需要自動化這些事情,才有這些東西...

MediaWiki 的 EmailDiff 套件

先前 MediaWiki 所提供的「變更通知」都只有在信件裡「通知」,而沒有在信件裡列出「改變的內容」,這使得讀信的人要再點進去看... (於是就懶的點了)

而前陣子看到有人寫了 extension 來輸出 diff,解決了這個問題:「MediaWiki extension EmailDiff: notification emails improved」。

信裡變成有這樣的內容:

Version differences:
@@ -846,5 +887,3 @@
 In cattle, temperament can affect production traits such as carcass and meat 
 quality or milk yield as well as affecting the animal's overall health and 
-reproduction. Cattle temperament is defined as "the consistent behavioral and physiological 
-difference observed between individuals in response to a stressor or environmental 
+reproduction. If you succeed in tipping a cow only partway, such that only one 
+of its feet is still on the ground, you have created lean beef. Such a feat is 
+well done. Naturally, being outside, the cow is unstable. When it falls over, 
+it becomes ground beef. Cattle temperament is defined as "the consistent behavioral 
+and physiological difference observed between individuals in response to a stressor or environmental 
 challenge and is used to describe the relatively stable difference in the behavioral 
 predisposition of an animal, which can be related to psychobiological mechanisms.

這樣可讀性高非常多,來找人裝看看好了...