使用 GraphQL Cache 分頁 & Local-only field 進行效能改善的工作記錄#1

Todd
發佈於FE
閱讀時間約 9 分鐘

工作記錄#1 Performance Improve use GraphQL Cache pagination & Local-only field 

前景提要

目前公司正在進行從呼叫 API 的方式改變成使用 GraphQL 的技術轉型中,主要是為了解決前後端型別不一致或是因為後端修改 schema 時,前端並未修改到造成的問題,透過 Graphql-Codegen 的方式搭配 Apollo 使用,自動產生型別以及 hook 去使用,許多的前端相關 Cache 都還沒進行設定,mutation 的相關行為更新資料也大多數是使用 refetchQuery  的方式,而目前正在進行相關的 refactor 和效能改進,不過並不動到原本的 schema,紀錄 2 個有趣遇到的實際 case。


Case 1 Backend pagination

常見的分頁方式分為 offset based 和 cursor based,各有各的好處和適合使用的場景,在 Apollo 中也有提到 2 種 pagination best practice,不過在此次遇到的問題和官網提供的方法不太相同,所以花費了一些時間去處理,不過還是先來分析 2 種分頁方式,再來對我遇到的 case 進行處理。

Offset based pagination

raw-image

使用 offset based 的分頁方式最大好處在於使用者可以直接了當的跳去想要查詢的特別頁,並且對於後端來說是非常容易從 DB 去拉取資料的。

raw-image


但它仍然有些缺點需要去注意的:

  1. 新增與刪除頻率頻繁的資料舉例來說當使用者在 page 2 瀏覽了 5 筆資料,但在這段期間資料進行了更新,總共 insert 了新的 5 筆資料,若資料排序是由新到舊的話,就會照成使用者切到第 3 頁的同時,仍然會看到相同資料的尷尬狀況。
  2. page size 改變造成的問題假設使用者在 page 1 的 limit 是 5,也就是說使用者會看到第 1 到第 5 筆的資料,當使用者移動到第 2 頁的時候,他會瀏覽第 6 - 10 筆的資料,但若是他改變了 limt 改為 10 的話,就會變成看到第 11 - 20 筆的資料,就會錯失了 5 - 10 的資料,所以若希望不會發生這種事的話就必須額外處理跳回第 1 頁從新瀏覽。
  3. DB query performance使用 OFFSET  進行 query 的時候,在前面的資料仍然會被讀取並跳過他們,所以如果 offset N 的數量過大,也會延遲獲取資料的時間。


在 GraphQL Apollo 中提供了 offsetLimitPagination  的 helper function 可以簡單地去處理分頁的 Cache 資料,此 function 的實作方式如下:

raw-image

先從為什麼要處理分頁 Query 的 Cache 談起,GraphQL 的 query 預設也會以帶的參數當成 Cache 的 key,所以它大概會像是這樣:

raw-image

Query Cache:

raw-image

但這並不是分頁時想要儲存的 Cache 形式,而是會希望 Cache 是儲存單一個 Array,這樣的好處很明顯,當刪除一筆或是新增資料的時候,可以直接將新增或是刪除的資料對陣列進行操作,就不會照成刪除了一筆資料,但是卻只顯示同一頁的資料,本該往前補上的資料因為在不同的 Cache Key 上,所以無法自動的取得。並且如上述所提,頁面的資料可能會不相同,會導致 Cache 必須經常更新,或是有著過期的 Cache 資料,若是使用 cache-first  的資料從 Cache 獲取可能就會更不準確。


除了透過 merge  處理 query 的資料如何進行 Cache 處理,可以搭配 read  設定該如何讀取 Cache 的資料,就可以直接進行分頁:

raw-image

Cursor based pagination

cursor based 的分頁是指定某一 Row 之後的幾項 Item,SQL 會像是(若 id 是無序的話可能另外進行 timestamp 或是 id 順序的 mapping):

raw-image

使用 cursor 進行分頁的好處在於不會像是 offset based 依據需要 offset 則需要 N 筆資料的時間,並且會解決 offset 資料可能不可靠的問題,因為會指定某特定的項目,會遇到的問題在於 Client 端若不知道特定項目的 cursor,則無法跳到想要跳去的指定範圍。


Apollo 提供了 readField 的 helper function,可以透過 __reference 找到想要讀取的欄位,透過讀取到 id 就可以實現相關的處理,不過工作上遇到的問題是基於 offset based 的分頁,就沒多深入研究了。


遇上的問題

首先遇到的 Schema 像是這樣子:

raw-image

第一點遇到的問題在於此分頁的 arg 是使用 page  和 pageSize ,和官方提供的 offsetLimitPagination  helper function 不太符合,不過這是小問題,將 page 和 pageSize 計算出 offset 重寫就可以了,像是:

raw-image


第二點是它是回傳一個新的 type,而不是單獨的回傳 Item 的 list,所以若是在 Query 中進行設定反而無法進行設定 Cache,因為其實這個 Resolver 也沒有參數可以使用:

raw-image


解決方法應該是要在對於有實際傳入 arg 的欄位進行 cache 的處理,並且 FeedItemList 這個 type 並沒有可以當成 key  的欄位,所以對於此 type 應該將它的 keyField  設為 false

raw-image

並且對於 feedItems 欄位應該加上 keyArgs: ["keyword"] ,因為此 Cache 確實會因為 keyword 的不同導致 Cache 應該要更新,不過這樣仍然是有缺點的,因為我們是設置在 type 的 fields 上,在 Query 中並沒有設定該如何處理 feedItemList 這個 query,所以 Cache 會像是:

raw-image

若是有新的 Cache 要更新,這個 object 就會被直接取代:

raw-image


等於說每次有不同的 keyword 都會導致它 Cache 重新清除了,不過考量到若在繼續修改應該會花費蠻多時間的,最好方式應該還是從後端將此分頁的 type 重新進行定義會比較好,我就沒繼續對此進行優化了。


Case 2 Local-only field

前景提要

對於後端來的資料,有時候會需要前端進行整理或再次經過計算, Apollo 提供了很棒的方法,叫做 Local-only field,我們可以直接透過定義好這個 field 該如何進行運作,接下來只要有人有需要就可以直接進行相關的 query,例如:

raw-image


我定義了一個 local 的 field,可以直接知道他是不是在購物車中:

raw-image


若有其他人也需要知道,直接在他的 query 中去 query 這個欄位就可以了,達成了封裝又透過組合的方式進行利用:

raw-image


若是有使用 codegne 的需要額外定義 client schema 告知 codegen 該 gen 出什麼 type,並且需要在 codegen 的 config 中額外的設定 client schema 的路徑:

raw-image
raw-image


遇到的問題 Tree

有一個資料結構像是如下:

raw-image


它是一個樹狀的結構,而 descendantTree  則是攤平的所有 node ,裡面包括了所有的孩子、子孫、子子孫....直到 leaf。也就是說當我進行 query,帶有 descendantTree 時,是可以重新組建成樹狀結構的。

raw-image


在之前為了顯示 Node 的 Path,或是透過 type 進行篩選計算,都需要在前端建立 Tree,如圖若想找到 root 到紅點的 path 或是黃色的 subtree 藍點某一值加起來是多少都需要建完後又再遍歷、篩選、計算,其中可能又會一些公式的不同需要從新建樹又計算的,都算是蠻麻煩的。

raw-image


而這可以透過 local only field 來解決,並且會提升效能(減少建 Tree)並且若有需要的使用者只需要加上各個 field 就可以了,以想要知道 node 經過的 path 為例來說,可以透過讀取自己本身的 parentId 去找到自己的父親,而父親也遲須依照此方法,並將找到的父親 nodePath 直接進行 concat 就可以了,直接省略了建 Tree,並且可以直接透過 key value 的方式找到父親(O(1)),大概會像是這樣:

raw-image


若有其他人需要使用到 nodePath 的時候則只需要在 query 的時候新增這個 field 即可:

raw-image


而其他像是需要計算或是 filter 的值也都可以透過這種方式建立,透過這樣的組合並將邏輯放在一起統一進行管理,有需要就新增 query field,可能可以提升了效能也提升了管理和方便再次重複使用。


有其他方式或是任何的錯誤感謝告知。

1會員
3內容數
FE Developer
留言0
查看全部
發表第一個留言支持創作者!
Hello Todd 的其他內容
你可能也想看
Golang - Gin #33: 在Gin中使用GraphQL訂閱實時數據更新在今天的應用程序中變得越來越重要。GraphQL訂閱提供了一種高效的方式來實現這一目標。 在這篇文章裡,我們將探討如何在Gin框架與GraphQL結合下,實現數據的實時更新,或者說,實現所謂的“訂閱”功能。
Thumbnail
avatar
KH Huang
2023-10-08
Golang - Gin #24: 使用Gin實現GraphQL API隨著Web應用的發展,前端和後端的需求也在變得越來越複雜。RESTful APIs已經不再滿足當前的需求,而GraphQL作為一個新興的數據查詢語言,提供了更靈活的查詢能力。在這篇文章中,我們將探討如何在Gin中實現GraphQL API,為你的應用帶來現代化的數據查詢。
Thumbnail
avatar
KH Huang
2023-09-29
使用PROCESS macro for SPSS 進行調節式中介分析(moderated mediation)PROCESS macro for SPSS 可以用非常簡單方式學會調節中介模式。本文將介紹四種類型的變項,並解釋調節式中介的公式,還有如何操作最4.0版本的PROCESS macro for SPSS。文末也會附上所有所有Process模型圖例,提供給讀者方便分析~
Thumbnail
avatar
Dr. Rover
2023-05-15
使用PROCESS macro for SPSS 進行調節模式分析PROCESS macro for SPSS 可以用非常簡單方式使用調節分析。本文將介紹三種類型的變項,還有如何操作最4.2版本的PROCESS macro for SPSS進行調節模式。文末也會附上所有所有Process模型圖例,提供給讀者方便分析~
Thumbnail
avatar
Dr. Rover
2023-04-16
使用PROCESS macro for SPSS 進行中介模式分析 PROCESS macro for SPSS 可以用非常簡單方式進中介模式。本文將介紹三種類型的變項,還有如何操作最4.0版本的PROCESS macro for SPSS。文末也會附上所有所有Process模型圖例,提供給讀者方便分析~
Thumbnail
avatar
Dr. Rover
2023-03-15
使用Potato Media寫作平台一年半來的心得及收入報告Potato Media雖然和方格子及Matters同樣歸類為寫作平台,同樣強調將內容變現,前者卻與後面兩者完全不同,當然,所獲得的收入報酬也不會一樣,更清楚一點來說,連獲得收益的方式也大不相同。
Thumbnail
avatar
宋雨桐Love Queen
2023-03-11
使用SPSS做信度分析我們將介紹各種類型的信度和統計方法,包含Cohen Kappa 係數、組內相關係數、α係數的SPSS教學。信度的可以使用不同的評估方法來評估。信度對於確定評分標準或量表的一致性和穩定度至關重要。
Thumbnail
avatar
Dr. Rover
2023-01-31
使用SPSS進行羅吉斯迴歸如果依變項並非連續變項時,就可以改用羅吉斯迴歸。接下來本文將介紹勝算、勝算比、計算範例、二元/順序/多項式羅吉斯迴歸分析範例和SPSS操作方法。
Thumbnail
avatar
Dr. Rover
2023-01-31
使用SPSS進行卡方檢定(交叉表)通常我們對於類別變項就直接看敘述統計大小,但如果我們想要用檢定確定兩者差距是達到統計顯著,就要用卡方檢定(Chi-square test)是一種統計學方法,獨立性考驗用於檢驗兩個類別變項各組別之間是否有顯著關聯。本文將介紹卡方檢定並介紹上機操作和事後比較方法。
Thumbnail
avatar
Dr. Rover
2023-01-30
avatar
YUKI
2022-11-04