Thursday, February 16, 2023

Web application 生成的報表速度太慢

 最近在 Gaiwan 公司遇到客戶委任我們做一些效能改進:

  1. 客戶把 code 的 link 給我之後,他們留下的訊息是說,覺得有沒有可能,把一些 code 改成用 Transducer 來改寫,效能就會好了。
  2. 我第一時間就覺得,八成不是這個問題。一般來講,web application 這種東西,會慢,十之八九都是資料庫的查詢在慢,更何況,客戶用的資料庫還是 Datomic 。
  3. 但是,光是有直覺沒有用,還是要設法証明。我很久沒有做 profiling 了,上次做疑似是十年前,總之,基本的概念還有,技術完全沒有。花了一些時間研究工具之後,發現有兩個程式很有用。
    (3.a) jcmd => 這個可以快速找到 java program 對應的啟動 argument 與 pid
    (3.b) VisualVM => 我是用 statistical mode 去量測。它可以幫你一層又一層地向下展開,看到 x function calls y function ,且 x function 的 total time, CPU time, self time 。我用了這個之後,層層展開函數呼叫後,果然,定位出來,最耗時的地方是資料庫查詢 (Database Query)
  4. 該怎麼改進呢? 一旦找到關鍵點了,要改就有很多可能性。在眾多的改法之中,我個人覺得工程略為耗大,但是一勞永逸的改法,是把資料 sync 到資料倉儲 (Data warehouse) 裡,把本來很慢的資料庫查詢都送到資料倉儲去。由於資料倉儲在 schema 、index 上都有許多神奇的最佳化,效能的大躍進滿可以期待的。
  5. 然而,這件事的結尾並不是使用資料倉儲。
    我老闆一眨眼就找出效能低落的關鍵點:N+1 problem 。速度很慢的那一段程式碼,它在迴圈裡做 Query 。如果 Query 是那種極度單純的,完全不需要 scan 資料庫的,老實說還好,畢竟 Datomic 在 peer 端做了大量的 cache ,並不會有很大的 I/O 開銷。 而我遇到的這個案例,迴圈裡的 Query 是有做一些些 unification 的。所以解決之道,是要改變程式碼的寫法,把 Query 移到迴圈外。 修改完之後,就快了 20 倍。