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

Todd-avatar-img
發佈於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,可能可以提升了效能也提升了管理和方便再次重複使用。


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

avatar-img
2會員
5內容數
FE Developer
留言
avatar-img
留言分享你的想法!

































































Hello Todd 的其他內容
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
你可能也想看
Google News 追蹤
Thumbnail
這篇文章主要是介紹了SQL查詢效能調校的方法,針對索引最佳化做了整理和分享,並提供了一些注意事項和建議。
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
透過零售業的數位轉型,消費者期待獲得更多元的服務體驗。API 技術在電商、庫存管理和訂單處理等方面發揮關鍵作用,幫助企業提升效率並擴大營運範圍。API 管理平台為企業帶來高彈性、安全的 API 策略,加速數位轉型,提高企業韌性。昕力資訊的 API 管理平台為企業提供強力支持,助力產業進步。
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
這邊統整了所有過去發表過關於 QUERY 函式的教學分享,希望可以方便你按照順序閱讀和練習。 QUERY 可以用來查詢、篩選、聚集、排序資料,還可以做張簡易的資料透視表,是我在 Google 試算表上做數據分析、製作報告、製作儀表板時最常用的函式之一,既方便又好用,誠心推薦!
Thumbnail
這篇文章主要是介紹了SQL查詢效能調校的方法,針對索引最佳化做了整理和分享,並提供了一些注意事項和建議。
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
透過零售業的數位轉型,消費者期待獲得更多元的服務體驗。API 技術在電商、庫存管理和訂單處理等方面發揮關鍵作用,幫助企業提升效率並擴大營運範圍。API 管理平台為企業帶來高彈性、安全的 API 策略,加速數位轉型,提高企業韌性。昕力資訊的 API 管理平台為企業提供強力支持,助力產業進步。
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
這邊統整了所有過去發表過關於 QUERY 函式的教學分享,希望可以方便你按照順序閱讀和練習。 QUERY 可以用來查詢、篩選、聚集、排序資料,還可以做張簡易的資料透視表,是我在 Google 試算表上做數據分析、製作報告、製作儀表板時最常用的函式之一,既方便又好用,誠心推薦!