Try   HackMD

Lecture 7 - Cloud Function的延伸應用

tags: GCP Firestore Cloud Function Python


一、前言

Cloud Function 的入門與部署 Firestore的監聽 中,已經學會使用 Cloud Function 來做到基礎的布置和監聽。這個章節主要是用來介紹筆者在專案製作的過程中有遇到那些問題,還有我是如何解決這些問題的過程。

如果遇到一些好用的用法,也會補充在這裡。


二、問題集

問題一:平行運算

(一)平行運算

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
多場遊戲進行時,產生必須讓 A 遊戲結束時 B 遊戲才會開始進行的問題

我一開始是寫了一個 function ,它主要運作的邏輯就是找出 db.collection('col_name') 路徑中最新的一個 match_key,程式如下 。

# get last generate doc id def get_last_doc_id(col_ref, order_key): query_get = col_ref.order_by(order_key, direction=firestore.Query.DESCENDING).limit(1).stream() for doc in query_get: return doc.id

一開始,當 A 遊戲 和 B 遊戲 創建的時間相隔超過 5 秒時,兩場遊戲幾乎都能順利的進行平行運算,此時我就想,會不會是 A 遊戲 的程式還沒成功執行 B 遊戲的程式就已經開始了,此時我的思維是「如何減少遊戲觸發後執行的時間?」

我有的解決方式可能有:

  1. 增進程式碼效率
  2. 添加異步協程

除此之外,參考了官方文件,我發現 Cloud Function 存在一個名為 「Cold Start」 的問題。

(二)Cold Start

Cold Start 並不是 Cloud Function 裡面存在的專有名詞,而是存在於各式各樣的電腦都有這個問題。

Cold Start
計算機的電源關掉後,經過一段時間再打開電源,使計算機重新起動。冷起動會使計算機的工作被重新設定,而記憶體內之資料也會被清除。

換句話說,由於 Cold Start 會讓工作被重新設定、清除記憶體內的資料,所以在重新啟動時,執行的時間會 大大的超過 已經暖機狀態的執行時間。

可以參考 這份資料 ,在文章中它將 Cold Start 優化縮短了大量的時間。

最後,我參考了 官方文件 ,再搭配了異步協程撰寫了下方的程式碼,讓兩場遊戲創建時間相隔縮短到 1~2 秒以內也可成功執行。

# TODO async class ParallelExecution: def __init__(self, data, context): self.data = data self.context = context self.instance_var = self.heavy_computation() # Placeholder def heavy_computation(self): return time.time() def light_computation(self): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start async def scope_demo(self, data, context): # Per-function scope # This computation runs every time this function is called function_var = self.light_computation() return 'Instance: {}; function: {}'.format(self.instance_var, function_var) async def main(self): # Schedule scope_demo() to run soon concurrently with "main()". task = asyncio.create_task(self.scope_demo(self.data, self.context)) # "task" can now be used to cancel "scope_demo()", # or can simply be awaited to wait until it is complete: await task # 等待一會(等待返回再去執行)

不過假設兩場遊戲之間只要隔 1 秒,那 100 場遊戲的話也意味第 1 場遊戲和第100場遊戲中間至少要間隔 99 秒,這完全是不合理的!所以還是得寫一個 log 檔,看真正的問題到底出在哪。

不過很棒的是,在 GCP 的後台可以直接監控 log 檔,如下圖點擊後就可觀看,程式中所有 print 的資訊也會表現在裡面。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

在花了幾天的時間 debug 後,我發現真正的問題是 Firestore 本身存在的一個限制— 每秒新增 document 次數

(三)每秒新增 document 次數限制

官方文件 中明確提到

Firestore 每秒寫入次數為 1 次

也就代表說,在我兩場遊戲同時創建時,由於每秒寫入次數最多1次的關係,兩者本身創建的時間就會差距 1 秒,此時如果我 Cloud Function 早就觸發兩次了,那我在 (一)平行運算 中提到說,我抓 document 邏輯是以「Clodu Function 觸發時的最後一個match_key」,就不可行了。
(因為 B 遊戲的 Cloud Function 觸發後能抓到的最後一個 key 是 A 遊戲的 key)

(四)最終解決方案

最後,參考了 這份 文件,找到 Cloud Function 可以直接抓取觸發條件的 document id,程式碼如下,這樣就能解決各種時差問題了,因為本身執行的 match 一定就是被觸發的那場 match 。

def hello_firestore(data, context): # 要優化 db = firestore.client() trigger_resource = context.resource last_match_key = trigger_resource.split('/')[-1] # match0001 print('%s start!!!' % last_match_key) # match0001 start!!!
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
參考資料
1. https://medium.com/@duhroach/improving-cloud-function-cold-start-time-2eb6f5700f6
2. https://cloud.google.com/functions/docs/bestpractices/tips
3. https://firebase.google.com/docs/firestore/quotas
4. https://cloud.google.com/functions/docs/calling/cloud-firestore?hl=zh-TW